Added support for phrases in templates.

The language of the end-user is now tracked and presented in the Analytics Manager.
Profile owners now get alerts when someone posts on their profiles.

The login page is now transpiled, estimated to be sixty times faster.
The registration page is now transpiled, estimated to be sixty times faster.
The IP Search page is now transpiled, estimated to be sixty times faster.
The error pages are now transpiled, estimated to be sixty times faster.
The login page now uses phrases.
The registration page now uses phrases.
IP Search now uses phrases.
Renamed the ip-search template to ip_search.
Alerts are now held in an alertbox container div.
Added ids for the main container divs for the account manager sections.
Added an id to the main container for the topic list template.
Added an id to the main container for the forum list template.
Added an id to the main container for the forum template.
Added an avatar box CSS class for the avatar box in the account manager's avatar page.
Did a bit of renaming for a future refactor in the routes counter.
Did a bit of renaming for a future refactor in the operating system counter.
A notice is shown to the user now when their account is inactive.
The account activation status is now fetched by the user store.
We now track Slackbot.
You can now prepend strings to the start of router.DumpRequest request dumps to avoid tearing these bits of contextual data away from the bodies.
.action file extensions are now seen as suspicious by the router.
Moved routeWebsockets to common.RouteWebsockets for now.
Moved routeCreateReplySubmit to routes.CreateReplySubmit.
Moved alert.go into common.
Moved the WebSockets logic into common.
Escape strings a little earlier in the analytics routes and use integers instead of strings where possible.
We now show a success notification when you update a user via the User Manager.
Split the configuration properties off from CTemplateSet into CTemplateConfig.
Renamed some of the properties of CTemplateSet to make them easier to understand.
Removed some obsolete properties from CTemplateSet.
Did a bit of spring cleaning in the template transpiler to cut down on unneccessary lines and to reduce duplication.
Fixed a double else bug in ranges over maps in the template transpiler.
Split the minifiers off the main template transpilation file into their own file.
Refactored some of the routes which rely on alerts to use shared functions rather than having unique implementations in the routes themselves.

All Themes Except Cosora:
Refactored the opt nodes to make it easier to roll out bulk moderation.

Shadow:
Improved the notice CSS.
Tweaked the sticky border colour.

Cosora:
The theme JS file now uses strict mode.
Notices are shunted under rowhead with JS now, although this change might be reverted soon.
Added CSS for notices.
Fixed the padding under the avatar box in the account manager avatar page.

Schema:
Added the viewchunks_langs table.
This commit is contained in:
Azareal 2018-03-08 03:59:47 +00:00
parent 8ecc637ab9
commit 5a8b994877
65 changed files with 2701 additions and 1345 deletions

164
alerts.go
View File

@ -1,164 +0,0 @@
/*
*
* Gosora Alerts System
* Copyright Azareal 2017 - 2018
*
*/
package main
import (
"errors"
"log"
"strconv"
"strings"
"./common"
)
// These notes are for me, don't worry about it too much ^_^
/*
"You received a friend invite from {user}"
"{x}{mentioned you on}{user}{'s profile}"
"{x}{mentioned you in}{topic}"
"{x}{likes}{you}"
"{x}{liked}{your topic}{topic}"
"{x}{liked}{your post on}{user}{'s profile}" todo
"{x}{liked}{your post in}{topic}"
"{x}{replied to}{your post in}{topic}" todo
"{x}{replied to}{topic}"
"{x}{replied to}{your topic}{topic}"
"{x}{created a new topic}{topic}"
*/
func buildAlert(asid int, event string, elementType string, actorID int, targetUserID int, elementID int, user common.User /* The current user */) (string, error) {
var targetUser *common.User
actor, err := common.Users.Get(actorID)
if err != nil {
return "", errors.New("Unable to find the actor")
}
/*if elementType != "forum" {
targetUser, err = users.Get(targetUser_id)
if err != nil {
LocalErrorJS("Unable to find the target user",w,r)
return
}
}*/
if event == "friend_invite" {
return `{"msg":"You received a friend invite from {0}","sub":["` + actor.Name + `"],"path":"` + actor.Link + `","avatar":"` + strings.Replace(actor.Avatar, "/", "\\/", -1) + `","asid":"` + strconv.Itoa(asid) + `"}`, nil
}
var act, postAct, url, area string
var startFrag, endFrag string
switch elementType {
case "forum":
if event == "reply" {
act = "created a new topic"
topic, err := common.Topics.Get(elementID)
if err != nil {
common.DebugLogf("Unable to find linked topic %d", elementID)
return "", errors.New("Unable to find the linked topic")
}
url = topic.Link
area = topic.Title
// Store the forum ID in the targetUser column instead of making a new one? o.O
// Add an additional column for extra information later on when we add the ability to link directly to posts. We don't need the forum data for now...
} else {
act = "did something in a forum"
}
case "topic":
topic, err := common.Topics.Get(elementID)
if err != nil {
common.DebugLogf("Unable to find linked topic %d", elementID)
return "", errors.New("Unable to find the linked topic")
}
url = topic.Link
area = topic.Title
if targetUserID == user.ID {
postAct = " your topic"
}
case "user":
targetUser, err = common.Users.Get(elementID)
if err != nil {
common.DebugLogf("Unable to find target user %d", elementID)
return "", errors.New("Unable to find the target user")
}
area = targetUser.Name
endFrag = "'s profile"
url = targetUser.Link
case "post":
topic, err := common.TopicByReplyID(elementID)
if err != nil {
return "", errors.New("Unable to find the linked reply or parent topic")
}
url = topic.Link
area = topic.Title
if targetUserID == user.ID {
postAct = " your post in"
}
default:
return "", errors.New("Invalid elementType")
}
switch event {
case "like":
if elementType == "user" {
act = "likes"
endFrag = ""
if targetUser.ID == user.ID {
area = "you"
}
} else {
act = "liked"
}
case "mention":
if elementType == "user" {
act = "mentioned you on"
} else {
act = "mentioned you in"
postAct = ""
}
case "reply":
act = "replied to"
}
return `{"msg":"{0} ` + startFrag + act + postAct + ` {1}` + endFrag + `","sub":["` + actor.Name + `","` + area + `"],"path":"` + url + `","avatar":"` + actor.Avatar + `","asid":"` + strconv.Itoa(asid) + `"}`, nil
}
func notifyWatchers(asid int64) {
rows, err := stmts.getWatchers.Query(asid)
if err != nil && err != ErrNoRows {
log.Fatal(err.Error())
return
}
defer rows.Close()
var uid int
var uids []int
for rows.Next() {
err := rows.Scan(&uid)
if err != nil {
log.Fatal(err.Error())
return
}
uids = append(uids, uid)
}
err = rows.Err()
if err != nil {
log.Fatal(err.Error())
return
}
var actorID, targetUserID, elementID int
var event, elementType string
err = stmts.getActivityEntry.QueryRow(asid).Scan(&actorID, &targetUserID, &event, &elementType, &elementID)
if err != nil && err != ErrNoRows {
log.Fatal(err.Error())
return
}
_ = wsHub.pushAlerts(uids, int(asid), event, elementType, actorID, targetUserID, elementID)
}

243
common/alerts.go Normal file
View File

@ -0,0 +1,243 @@
/*
*
* Gosora Alerts System
* Copyright Azareal 2017 - 2018
*
*/
package common
import (
"database/sql"
"errors"
"log"
"strconv"
"strings"
"../query_gen/lib"
)
type AlertStmts struct {
addActivity *sql.Stmt
notifyWatchers *sql.Stmt
notifyOne *sql.Stmt
getWatchers *sql.Stmt
getActivityEntry *sql.Stmt
}
var alertStmts AlertStmts
// TODO: Move these statements into some sort of activity abstraction
// TODO: Rewrite the alerts logic
func init() {
DbInits.Add(func(acc *qgen.Accumulator) error {
alertStmts = AlertStmts{
addActivity: acc.Insert("activity_stream").Columns("actor, targetUser, event, elementType, elementID").Fields("?,?,?,?,?").Prepare(),
notifyWatchers: acc.SimpleInsertInnerJoin(
qgen.DBInsert{"activity_stream_matches", "watcher, asid", ""},
qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""},
),
notifyOne: acc.Insert("activity_stream_matches").Columns("watcher, asid").Fields("?,?").Prepare(),
getWatchers: acc.SimpleInnerJoin("activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""),
getActivityEntry: acc.Select("activity_stream").Columns("actor, targetUser, event, elementType, elementID").Where("asid = ?").Prepare(),
}
return acc.FirstError()
})
}
// These notes are for me, don't worry about it too much ^_^
/*
"You received a friend invite from {user}"
"{x}{mentioned you on}{user}{'s profile}"
"{x}{mentioned you in}{topic}"
"{x}{likes}{you}"
"{x}{liked}{your topic}{topic}"
"{x}{liked}{your post on}{user}{'s profile}" todo
"{x}{liked}{your post in}{topic}"
"{x}{replied to}{your post in}{topic}" todo
"{x}{replied to}{topic}"
"{x}{replied to}{your topic}{topic}"
"{x}{created a new topic}{topic}"
*/
func BuildAlert(asid int, event string, elementType string, actorID int, targetUserID int, elementID int, user User /* The current user */) (string, error) {
var targetUser *User
actor, err := Users.Get(actorID)
if err != nil {
return "", errors.New("Unable to find the actor")
}
/*if elementType != "forum" {
targetUser, err = users.Get(targetUser_id)
if err != nil {
LocalErrorJS("Unable to find the target user",w,r)
return
}
}*/
if event == "friend_invite" {
return `{"msg":"You received a friend invite from {0}","sub":["` + actor.Name + `"],"path":"` + actor.Link + `","avatar":"` + strings.Replace(actor.Avatar, "/", "\\/", -1) + `","asid":"` + strconv.Itoa(asid) + `"}`, nil
}
var act, postAct, url, area string
var startFrag, endFrag string
switch elementType {
case "forum":
if event == "reply" {
act = "created a new topic"
topic, err := Topics.Get(elementID)
if err != nil {
DebugLogf("Unable to find linked topic %d", elementID)
return "", errors.New("Unable to find the linked topic")
}
url = topic.Link
area = topic.Title
// Store the forum ID in the targetUser column instead of making a new one? o.O
// Add an additional column for extra information later on when we add the ability to link directly to posts. We don't need the forum data for now...
} else {
act = "did something in a forum"
}
case "topic":
topic, err := Topics.Get(elementID)
if err != nil {
DebugLogf("Unable to find linked topic %d", elementID)
return "", errors.New("Unable to find the linked topic")
}
url = topic.Link
area = topic.Title
if targetUserID == user.ID {
postAct = " your topic"
}
case "user":
targetUser, err = Users.Get(elementID)
if err != nil {
DebugLogf("Unable to find target user %d", elementID)
return "", errors.New("Unable to find the target user")
}
area = targetUser.Name
endFrag = "'s profile"
url = targetUser.Link
case "post":
topic, err := TopicByReplyID(elementID)
if err != nil {
return "", errors.New("Unable to find the linked reply or parent topic")
}
url = topic.Link
area = topic.Title
if targetUserID == user.ID {
postAct = " your post in"
}
default:
return "", errors.New("Invalid elementType")
}
switch event {
case "like":
if elementType == "user" {
act = "likes"
endFrag = ""
if targetUser.ID == user.ID {
area = "you"
}
} else {
act = "liked"
}
case "mention":
if elementType == "user" {
act = "mentioned you on"
} else {
act = "mentioned you in"
postAct = ""
}
case "reply":
act = "replied to"
}
return `{"msg":"{0} ` + startFrag + act + postAct + ` {1}` + endFrag + `","sub":["` + actor.Name + `","` + area + `"],"path":"` + url + `","avatar":"` + actor.Avatar + `","asid":"` + strconv.Itoa(asid) + `"}`, nil
}
func AddActivityAndNotifyAll(actor int, targetUser int, event string, elementType string, elementID int) error {
res, err := alertStmts.addActivity.Exec(actor, targetUser, event, elementType, elementID)
if err != nil {
return err
}
lastID, err := res.LastInsertId()
if err != nil {
return err
}
return NotifyWatchers(lastID)
}
func AddActivityAndNotifyTarget(actor int, targetUser int, event string, elementType string, elementID int) error {
res, err := alertStmts.addActivity.Exec(actor, targetUser, event, elementType, elementID)
if err != nil {
return err
}
lastID, err := res.LastInsertId()
if err != nil {
return err
}
err = NotifyOne(targetUser, lastID)
if err != nil {
return err
}
// Live alerts, if the target is online and WebSockets is enabled
_ = WsHub.pushAlert(targetUser, int(lastID), event, elementType, actor, targetUser, elementID)
return nil
}
func NotifyOne(watcher int, asid int64) error {
_, err := alertStmts.notifyOne.Exec(watcher, asid)
return err
}
func NotifyWatchers(asid int64) error {
_, err := alertStmts.notifyWatchers.Exec(asid)
if err != nil {
return err
}
// Alert the subscribers about this without blocking us from doing something else
if EnableWebsockets {
go notifyWatchers(asid)
}
return nil
}
func notifyWatchers(asid int64) {
rows, err := alertStmts.getWatchers.Query(asid)
if err != nil && err != ErrNoRows {
log.Fatal(err.Error())
return
}
defer rows.Close()
var uid int
var uids []int
for rows.Next() {
err := rows.Scan(&uid)
if err != nil {
log.Fatal(err.Error())
return
}
uids = append(uids, uid)
}
err = rows.Err()
if err != nil {
log.Fatal(err.Error())
return
}
var actorID, targetUserID, elementID int
var event, elementType string
err = alertStmts.getActivityEntry.QueryRow(asid).Scan(&actorID, &targetUserID, &event, &elementType, &elementID)
if err != nil && err != ErrNoRows {
log.Fatal(err.Error())
return
}
_ = WsHub.pushAlerts(uids, int(asid), event, elementType, actorID, targetUserID, elementID)
}

166
common/counters/langs.go Normal file
View File

@ -0,0 +1,166 @@
package counters
import "database/sql"
import ".."
import "../../query_gen/lib"
var LangViewCounter *DefaultLangViewCounter
var langCodes = []string{
"unknown",
"af",
"ar",
"az",
"be",
"bg",
"bs",
"ca",
"cs",
"cy",
"da",
"de",
"dv",
"el",
"en",
"eo",
"es",
"et",
"eu",
"fa",
"fi",
"fo",
"fr",
"gl",
"gu",
"he",
"hi",
"hr",
"hu",
"hy",
"id",
"is",
"it",
"ja",
"ka",
"kk",
"kn",
"ko",
"kok",
"ky",
"lt",
"lv",
"mi",
"mk",
"mn",
"mr",
"ms",
"mt",
"nb",
"nl",
"nn",
"ns",
"pa",
"pl",
"ps",
"pt",
"qu",
"ro",
"ru",
"sa",
"se",
"sk",
"sl",
"sq",
"sr",
"sv",
"sw",
"syr",
"ta",
"te",
"th",
"tl",
"tn",
"tr",
"tt",
"ts",
"uk",
"ur",
"uz",
"vi",
"xh",
"zh",
"zu",
}
type DefaultLangViewCounter struct {
buckets []*RWMutexCounterBucket //[OSID]count
codesToIndices map[string]int
insert *sql.Stmt
}
func NewDefaultLangViewCounter() (*DefaultLangViewCounter, error) {
acc := qgen.Builder.Accumulator()
var langBuckets = make([]*RWMutexCounterBucket, len(langCodes))
for bucketID, _ := range langBuckets {
langBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}
}
var codesToIndices = make(map[string]int)
for index, code := range langCodes {
codesToIndices[code] = index
}
counter := &DefaultLangViewCounter{
buckets: langBuckets,
codesToIndices: codesToIndices,
insert: acc.Insert("viewchunks_langs").Columns("count, createdAt, lang").Fields("?,UTC_TIMESTAMP(),?").Prepare(),
}
common.AddScheduledFifteenMinuteTask(counter.Tick)
//common.AddScheduledSecondTask(counter.Tick)
common.AddShutdownTask(counter.Tick)
return counter, acc.FirstError()
}
func (counter *DefaultLangViewCounter) Tick() error {
for id, bucket := range counter.buckets {
var count int
bucket.RLock()
count = bucket.counter
bucket.counter = 0 // TODO: Add a SetZero method to reduce the amount of duplicate code between the OS and agent counters?
bucket.RUnlock()
err := counter.insertChunk(count, id) // TODO: Bulk insert for speed?
if err != nil {
return err
}
}
return nil
}
func (counter *DefaultLangViewCounter) insertChunk(count int, id int) error {
if count == 0 {
return nil
}
var langCode = langCodes[id]
common.DebugLogf("Inserting a viewchunk with a count of %d for lang %s (%d)", count, langCode, id)
_, err := counter.insert.Exec(count, langCode)
return err
}
func (counter *DefaultLangViewCounter) Bump(langCode string) {
id, ok := counter.codesToIndices[langCode]
if !ok {
// TODO: Tell the caller that the code's invalid
id = 0 // Unknown
}
// TODO: Test this check
common.DebugDetail("counter.buckets[", id, "]: ", counter.buckets[id])
if len(counter.buckets) <= id || id < 0 {
return
}
counter.buckets[id].Lock()
counter.buckets[id].counter++
counter.buckets[id].Unlock()
}

View File

@ -8,8 +8,8 @@ var RouteViewCounter *DefaultRouteViewCounter
// TODO: Make this lockless? // TODO: Make this lockless?
type DefaultRouteViewCounter struct { type DefaultRouteViewCounter struct {
routeBuckets []*RWMutexCounterBucket //[RouteID]count buckets []*RWMutexCounterBucket //[RouteID]count
insert *sql.Stmt insert *sql.Stmt
} }
func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) { func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) {
@ -19,8 +19,8 @@ func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) {
routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}
} }
counter := &DefaultRouteViewCounter{ counter := &DefaultRouteViewCounter{
routeBuckets: routeBuckets, buckets: routeBuckets,
insert: acc.Insert("viewchunks").Columns("count, createdAt, route").Fields("?,UTC_TIMESTAMP(),?").Prepare(), insert: acc.Insert("viewchunks").Columns("count, createdAt, route").Fields("?,UTC_TIMESTAMP(),?").Prepare(),
} }
common.AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second common.AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second
//common.AddScheduledSecondTask(counter.Tick) //common.AddScheduledSecondTask(counter.Tick)
@ -29,7 +29,7 @@ func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) {
} }
func (counter *DefaultRouteViewCounter) Tick() error { func (counter *DefaultRouteViewCounter) Tick() error {
for routeID, routeBucket := range counter.routeBuckets { for routeID, routeBucket := range counter.buckets {
var count int var count int
routeBucket.RLock() routeBucket.RLock()
count = routeBucket.counter count = routeBucket.counter
@ -56,11 +56,11 @@ func (counter *DefaultRouteViewCounter) insertChunk(count int, route int) error
func (counter *DefaultRouteViewCounter) Bump(route int) { func (counter *DefaultRouteViewCounter) Bump(route int) {
// TODO: Test this check // TODO: Test this check
common.DebugDetail("counter.routeBuckets[", route, "]: ", counter.routeBuckets[route]) common.DebugDetail("counter.buckets[", route, "]: ", counter.buckets[route])
if len(counter.routeBuckets) <= route || route < 0 { if len(counter.buckets) <= route || route < 0 {
return return
} }
counter.routeBuckets[route].Lock() counter.buckets[route].Lock()
counter.routeBuckets[route].counter++ counter.buckets[route].counter++
counter.routeBuckets[route].Unlock() counter.buckets[route].Unlock()
} }

View File

@ -7,8 +7,8 @@ import "../../query_gen/lib"
var OSViewCounter *DefaultOSViewCounter var OSViewCounter *DefaultOSViewCounter
type DefaultOSViewCounter struct { type DefaultOSViewCounter struct {
osBuckets []*RWMutexCounterBucket //[OSID]count buckets []*RWMutexCounterBucket //[OSID]count
insert *sql.Stmt insert *sql.Stmt
} }
func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) { func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) {
@ -18,8 +18,8 @@ func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) {
osBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} osBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}
} }
counter := &DefaultOSViewCounter{ counter := &DefaultOSViewCounter{
osBuckets: osBuckets, buckets: osBuckets,
insert: acc.Insert("viewchunks_systems").Columns("count, createdAt, system").Fields("?,UTC_TIMESTAMP(),?").Prepare(), insert: acc.Insert("viewchunks_systems").Columns("count, createdAt, system").Fields("?,UTC_TIMESTAMP(),?").Prepare(),
} }
common.AddScheduledFifteenMinuteTask(counter.Tick) common.AddScheduledFifteenMinuteTask(counter.Tick)
//common.AddScheduledSecondTask(counter.Tick) //common.AddScheduledSecondTask(counter.Tick)
@ -28,14 +28,14 @@ func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) {
} }
func (counter *DefaultOSViewCounter) Tick() error { func (counter *DefaultOSViewCounter) Tick() error {
for osID, osBucket := range counter.osBuckets { for id, bucket := range counter.buckets {
var count int var count int
osBucket.RLock() bucket.RLock()
count = osBucket.counter count = bucket.counter
osBucket.counter = 0 // TODO: Add a SetZero method to reduce the amount of duplicate code between the OS and agent counters? bucket.counter = 0 // TODO: Add a SetZero method to reduce the amount of duplicate code between the OS and agent counters?
osBucket.RUnlock() bucket.RUnlock()
err := counter.insertChunk(count, osID) // TODO: Bulk insert for speed? err := counter.insertChunk(count, id) // TODO: Bulk insert for speed?
if err != nil { if err != nil {
return err return err
} }
@ -53,13 +53,13 @@ func (counter *DefaultOSViewCounter) insertChunk(count int, os int) error {
return err return err
} }
func (counter *DefaultOSViewCounter) Bump(os int) { func (counter *DefaultOSViewCounter) Bump(id int) {
// TODO: Test this check // TODO: Test this check
common.DebugDetail("counter.osBuckets[", os, "]: ", counter.osBuckets[os]) common.DebugDetail("counter.buckets[", id, "]: ", counter.buckets[id])
if len(counter.osBuckets) <= os || os < 0 { if len(counter.buckets) <= id || id < 0 {
return return
} }
counter.osBuckets[os].Lock() counter.buckets[id].Lock()
counter.osBuckets[os].counter++ counter.buckets[id].counter++
counter.osBuckets[os].Unlock() counter.buckets[id].Unlock()
} }

View File

@ -290,7 +290,7 @@ func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi Page) {
if RunPreRenderHook("pre_render_error", w, r, &pi.CurrentUser, &pi) { if RunPreRenderHook("pre_render_error", w, r, &pi.CurrentUser, &pi) {
return return
} }
err := Templates.ExecuteTemplate(w, "error.html", pi) err := RunThemeTemplate(pi.Header.Theme.Name, "error", pi, w)
if err != nil { if err != nil {
LogError(err) LogError(err)
} }

View File

@ -1,12 +1,12 @@
// +build no_ws // +build no_ws
package main package common
import "errors" import "errors"
import "net/http" import "net/http"
// TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it? // TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it?
var enableWebsockets = false // Put this in caps for consistency with the other constants? var EnableWebsockets = false // Put this in caps for consistency with the other constants?
var wsHub WSHub var wsHub WSHub
var errWsNouser = errors.New("This user isn't connected via WebSockets") var errWsNouser = errors.New("This user isn't connected via WebSockets")
@ -38,5 +38,5 @@ func (hub *WSHub) pushAlerts(_ []int, _ int, _ string, _ string, _ int, _ int, _
return errWsNouser return errWsNouser
} }
func routeWebsockets(_ http.ResponseWriter, _ *http.Request, _ User) { func RouteWebsockets(_ http.ResponseWriter, _ *http.Request, _ User) {
} }

View File

@ -44,12 +44,17 @@ type LanguagePack struct {
Accounts map[string]string // TODO: Apply these phrases in the software proper Accounts map[string]string // TODO: Apply these phrases in the software proper
UserAgents map[string]string UserAgents map[string]string
OperatingSystems map[string]string OperatingSystems map[string]string
HumanLanguages map[string]string
Errors map[string]map[string]string // map[category]map[name]value Errors map[string]map[string]string // map[category]map[name]value
PageTitles map[string]string PageTitles map[string]string
TmplPhrases map[string]string
TmplIndicesToPhrases [][][]byte // [tmplID][index]phrase
} }
// TODO: Add the ability to edit language JSON files from the Control Panel and automatically scan the files for changes // TODO: Add the ability to edit language JSON files from the Control Panel and automatically scan the files for changes
var langPacks sync.Map // nolint it is used var langPacks sync.Map // nolint it is used
var langTmplIndicesToNames [][]string // [tmplID][index]phraseName
func InitPhrases() error { func InitPhrases() error {
log.Print("Loading the language packs") log.Print("Loading the language packs")
@ -75,6 +80,15 @@ func InitPhrases() error {
return err return err
} }
langPack.TmplIndicesToPhrases = make([][][]byte, len(langTmplIndicesToNames))
for tmplID, phraseNames := range langTmplIndicesToNames {
var phraseSet = make([][]byte, len(phraseNames))
for index, phraseName := range phraseNames {
phraseSet[index] = []byte(langPack.TmplPhrases[phraseName])
}
langPack.TmplIndicesToPhrases[tmplID] = phraseSet
}
log.Print("Adding the '" + langPack.Name + "' language pack") log.Print("Adding the '" + langPack.Name + "' language pack")
langPacks.Store(langPack.Name, &langPack) langPacks.Store(langPack.Name, &langPack)
langPackCount++ langPackCount++
@ -170,6 +184,14 @@ func GetOSPhrase(name string) (string, bool) {
return res, true return res, true
} }
func GetHumanLangPhrase(name string) (string, bool) {
res, ok := currentLangPack.Load().(*LanguagePack).HumanLanguages[name]
if !ok {
return "", false
}
return res, true
}
// TODO: Does comma ok work with multi-dimensional maps? // TODO: Does comma ok work with multi-dimensional maps?
func GetErrorPhrase(category string, name string) string { func GetErrorPhrase(category string, name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).Errors[category][name] res, ok := currentLangPack.Load().(*LanguagePack).Errors[category][name]
@ -187,6 +209,14 @@ func GetTitlePhrase(name string) string {
return res return res
} }
func GetTmplPhrase(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).TmplPhrases[name]
if !ok {
return getPhrasePlaceholder()
}
return res
}
func getPhrasePlaceholder() string { func getPhrasePlaceholder() string {
return "{name}" return "{name}"
} }
@ -213,3 +243,14 @@ func ChangeLanguagePack(name string) (exists bool) {
currentLangPack.Store(pack) currentLangPack.Store(pack)
return true return true
} }
// Template Transpiler Stuff
func RegisterTmplPhraseNames(phraseNames []string) (tmplID int) {
langTmplIndicesToNames = append(langTmplIndicesToNames, phraseNames)
return len(langTmplIndicesToNames) - 1
}
func GetTmplPhrasesBytes(tmplID int) [][]byte {
return currentLangPack.Load().(*LanguagePack).TmplIndicesToPhrases[tmplID]
}

View File

@ -217,6 +217,9 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *
if user.IsBanned { if user.IsBanned {
headerVars.NoticeList = append(headerVars.NoticeList, "Your account has been suspended. Some of your permissions may have been revoked.") headerVars.NoticeList = append(headerVars.NoticeList, "Your account has been suspended. Some of your permissions may have been revoked.")
} }
if user.Loggedin && !user.Active {
headerVars.NoticeList = append(headerVars.NoticeList, "Your account hasn't been activated yet. Some features may remain unavailable until it is.")
}
if len(theme.Resources) > 0 { if len(theme.Resources) > 0 {
rlist := theme.Resources rlist := theme.Resources

View File

@ -22,6 +22,7 @@ type CTmpl struct {
Imports []string Imports []string
} }
// TODO: Stop duplicating these bits of code
// nolint // nolint
func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) error { func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topic"] mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topic"]
@ -80,18 +81,57 @@ var Template_create_topic_handle func(CreateTopicPage, http.ResponseWriter) erro
return Templates.ExecuteTemplate(w, mapping+".html", pi) return Templates.ExecuteTemplate(w, mapping+".html", pi)
} }
// nolint
var Template_login_handle func(Page, http.ResponseWriter) error = func(pi Page, w http.ResponseWriter) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["login"]
if !ok {
mapping = "login"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_register_handle func(Page, http.ResponseWriter) error = func(pi Page, w http.ResponseWriter) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["register"]
if !ok {
mapping = "register"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_error_handle func(Page, http.ResponseWriter) error = func(pi Page, w http.ResponseWriter) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["error"]
if !ok {
mapping = "error"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_ip_search_handle func(IPSearchPage, http.ResponseWriter) error = func(pi IPSearchPage, w http.ResponseWriter) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["ip_search"]
if !ok {
mapping = "ip_search"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// ? - Add template hooks? // ? - Add template hooks?
func compileTemplates() error { func compileTemplates() error {
var config tmpl.CTemplateConfig
config.Minify = Config.MinifyTemplates
config.SuperDebug = Dev.TemplateDebug
var c tmpl.CTemplateSet var c tmpl.CTemplateSet
c.Minify(Config.MinifyTemplates) c.SetConfig(config)
c.SuperDebug(Dev.TemplateDebug)
// Schemas to train the template compiler on what to expect // Schemas to train the template compiler on what to expect
// TODO: Add support for interface{}s // TODO: Add support for interface{}s
user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0} user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, BuildAvatar(62, ""), "", "", "", "", 0, 0, "0.0.0.0.0", 0}
// TODO: Do a more accurate level calculation for this? // TODO: Do a more accurate level calculation for this?
user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", "", "", "", "", 58, 1000, "127.0.0.1", 0} user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, BuildAvatar(1, ""), "", "", "", "", 58, 1000, "127.0.0.1", 0}
user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", "", "", "", "", 42, 900, "::1", 0} user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, BuildAvatar(2, ""), "", "", "", "", 42, 900, "::1", 0}
headerVars := &HeaderVars{ headerVars := &HeaderVars{
Site: Site, Site: Site,
Settings: SettingBox.Load().(SettingMap), Settings: SettingBox.Load().(SettingMap),
@ -168,12 +208,37 @@ func compileTemplates() error {
return err return err
} }
// Let plugins register their own templates loginPage := Page{"Login Page", user, headerVars, tList, nil}
if Dev.DebugMode { loginTmpl, err := c.Compile("login.html", "templates/", "common.Page", loginPage, varList)
log.Print("Registering the templates for the plugins") if err != nil {
return err
} }
c.SkipHandles(true)
registerPage := Page{"Registration Page", user, headerVars, tList, nil}
registerTmpl, err := c.Compile("register.html", "templates/", "common.Page", registerPage, varList)
if err != nil {
return err
}
errorPage := Page{"Error", user, headerVars, tList, "A problem has occurred in the system."}
errorTmpl, err := c.Compile("error.html", "templates/", "common.Page", errorPage, varList)
if err != nil {
return err
}
var ipUserList = make(map[int]*User)
ipUserList[1] = &user2
ipSearchPage := IPSearchPage{"IP Search", user2, headerVars, ipUserList, "::1"}
ipSearchTmpl, err := c.Compile("ip_search.html", "templates/", "common.IPSearchPage", ipSearchPage, varList)
if err != nil {
return err
}
// Let plugins register their own templates
DebugLog("Registering the templates for the plugins")
config = c.GetConfig()
config.SkipHandles = true
c.SetConfig(config)
for _, tmplfunc := range PrebuildTmplList { for _, tmplfunc := range PrebuildTmplList {
tmplItem := tmplfunc(user, headerVars) tmplItem := tmplfunc(user, headerVars)
varList = make(map[string]tmpl.VarItem) varList = make(map[string]tmpl.VarItem)
@ -191,6 +256,10 @@ func compileTemplates() error {
go writeTemplate("forums", forumsTmpl) go writeTemplate("forums", forumsTmpl)
go writeTemplate("topics", topicsTmpl) go writeTemplate("topics", topicsTmpl)
go writeTemplate("forum", forumTmpl) go writeTemplate("forum", forumTmpl)
go writeTemplate("login", loginTmpl)
go writeTemplate("register", registerTmpl)
go writeTemplate("ip_search", ipSearchTmpl)
go writeTemplate("error", errorTmpl)
go func() { go func() {
err := writeFile("./template_list.go", "package main\n\n// nolint\n"+c.FragOut) err := writeFile("./template_list.go", "package main\n\n// nolint\n"+c.FragOut)
if err != nil { if err != nil {
@ -279,10 +348,16 @@ func InitTemplates() error {
return template.HTML(BuildWidget(dock.(string), headerVarInt.(*HeaderVars))) return template.HTML(BuildWidget(dock.(string), headerVarInt.(*HeaderVars)))
} }
// The interpreted templates... fmap["lang"] = func(phraseNameInt interface{}) interface{} {
if Dev.DebugMode { phraseName, ok := phraseNameInt.(string)
log.Print("Loading the template files...") if !ok {
panic("phraseNameInt is not a string")
}
return GetTmplPhrase(phraseName)
} }
// The interpreted templates...
DebugLog("Loading the template files...")
Templates.Funcs(fmap) Templates.Funcs(fmap)
template.Must(Templates.ParseGlob("templates/*")) template.Must(Templates.ParseGlob("templates/*"))
template.Must(Templates.ParseGlob("pages/*")) template.Must(Templates.ParseGlob("pages/*"))

View File

@ -0,0 +1,54 @@
package tmpl
import (
"strconv"
"strings"
)
// TODO: Write unit tests for this
func minify(data string) string {
data = strings.Replace(data, "\t", "", -1)
data = strings.Replace(data, "\v", "", -1)
data = strings.Replace(data, "\n", "", -1)
data = strings.Replace(data, "\r", "", -1)
data = strings.Replace(data, " ", " ", -1)
return data
}
// TODO: Strip comments
// TODO: Handle CSS nested in <style> tags?
// TODO: Write unit tests for this
func minifyHTML(data string) string {
return minify(data)
}
// TODO: Have static files use this
// TODO: Strip comments
// TODO: Convert the rgb()s to hex codes?
// TODO: Write unit tests for this
func minifyCSS(data string) string {
return minify(data)
}
// TODO: Convert this to three character hex strings whenever possible?
// TODO: Write unit tests for this
// nolint
func rgbToHexstr(red int, green int, blue int) string {
return strconv.FormatInt(int64(red), 16) + strconv.FormatInt(int64(green), 16) + strconv.FormatInt(int64(blue), 16)
}
/*
// TODO: Write unit tests for this
func hexstrToRgb(hexstr string) (red int, blue int, green int, err error) {
// Strip the # at the start
if hexstr[0] == '#' {
hexstr = strings.TrimPrefix(hexstr,"#")
}
if len(hexstr) != 3 && len(hexstr) != 6 {
return 0, 0, 0, errors.New("Hex colour codes may only be three or six characters long")
}
if len(hexstr) == 3 {
hexstr = hexstr[0] + hexstr[0] + hexstr[1] + hexstr[1] + hexstr[2] + hexstr[2]
}
}*/

View File

@ -3,33 +3,41 @@ package tmpl
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"strconv"
"strings"
//"regexp"
"io/ioutil" "io/ioutil"
"log"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strconv"
"strings"
"text/template/parse" "text/template/parse"
) )
// TODO: Turn this file into a library // TODO: Turn this file into a library
var textOverlapList = make(map[string]int) var textOverlapList = make(map[string]int)
// nolint
type VarItem struct { type VarItem struct {
Name string Name string
Destination string Destination string
Type string Type string
} }
type VarItemReflect struct { type VarItemReflect struct {
Name string Name string
Destination string Destination string
Value reflect.Value Value reflect.Value
} }
type CTemplateConfig struct {
Minify bool
Debug bool
SuperDebug bool
SkipHandles bool
}
// nolint
type CTemplateSet struct { type CTemplateSet struct {
tlist map[string]*parse.Tree templateList map[string]*parse.Tree
dir string fileDir string
funcMap map[string]interface{} funcMap map[string]interface{}
importMap map[string]string importMap map[string]string
Fragments map[string]int Fragments map[string]int
@ -39,43 +47,31 @@ type CTemplateSet struct {
localVars map[string]map[string]VarItemReflect localVars map[string]map[string]VarItemReflect
hasDispInt bool hasDispInt bool
localDispStructIndex int localDispStructIndex int
langIndexToName []string
stats map[string]int stats map[string]int
pVarList string
pVarPosition int
previousNode parse.NodeType previousNode parse.NodeType
currentNode parse.NodeType currentNode parse.NodeType
nextNode parse.NodeType nextNode parse.NodeType
//tempVars map[string]string //tempVars map[string]string
doImports bool config CTemplateConfig
minify bool doImports bool
debug bool expectsInt interface{}
superDebug bool
skipHandles bool
expectsInt interface{}
} }
func (c *CTemplateSet) Minify(on bool) { func (c *CTemplateSet) SetConfig(config CTemplateConfig) {
c.minify = on c.config = config
} }
func (c *CTemplateSet) Debug(on bool) { func (c *CTemplateSet) GetConfig() CTemplateConfig {
c.debug = on return c.config
} }
func (c *CTemplateSet) SuperDebug(on bool) { func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
c.superDebug = on if c.config.Debug {
}
func (c *CTemplateSet) SkipHandles(on bool) {
c.skipHandles = on
}
func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
if c.debug {
fmt.Println("Compiling template '" + name + "'") fmt.Println("Compiling template '" + name + "'")
} }
c.dir = dir c.fileDir = fileDir
c.doImports = true c.doImports = true
c.funcMap = map[string]interface{}{ c.funcMap = map[string]interface{}{
"and": "&&", "and": "&&",
@ -92,6 +88,7 @@ func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsI
"multiply": true, "multiply": true,
"divide": true, "divide": true,
"dock": true, "dock": true,
"lang": true,
} }
c.importMap = map[string]string{ c.importMap = map[string]string{
@ -107,19 +104,17 @@ func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsI
c.varList = varList c.varList = varList
c.hasDispInt = false c.hasDispInt = false
c.localDispStructIndex = 0 c.localDispStructIndex = 0
//c.pVarList = ""
//c.pVarPosition = 0
c.stats = make(map[string]int) c.stats = make(map[string]int)
c.expectsInt = expectsInt c.expectsInt = expectsInt
holdreflect := reflect.ValueOf(expectsInt) holdreflect := reflect.ValueOf(expectsInt)
res, err := ioutil.ReadFile(dir + name) res, err := ioutil.ReadFile(fileDir + name)
if err != nil { if err != nil {
return "", err return "", err
} }
content := string(res) content := string(res)
if c.minify { if c.config.Minify {
content = minify(content) content = minify(content)
} }
@ -133,21 +128,20 @@ func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsI
out = "" out = ""
fname := strings.TrimSuffix(name, filepath.Ext(name)) fname := strings.TrimSuffix(name, filepath.Ext(name))
c.tlist = make(map[string]*parse.Tree) c.templateList = map[string]*parse.Tree{fname: tree}
c.tlist[fname] = tree
varholder := "tmpl_" + fname + "_vars" varholder := "tmpl_" + fname + "_vars"
c.log(c.tlist) c.log(c.templateList)
c.localVars = make(map[string]map[string]VarItemReflect) c.localVars = make(map[string]map[string]VarItemReflect)
c.localVars[fname] = make(map[string]VarItemReflect) c.localVars[fname] = make(map[string]VarItemReflect)
c.localVars[fname]["."] = VarItemReflect{".", varholder, holdreflect} c.localVars[fname]["."] = VarItemReflect{".", varholder, holdreflect}
if c.Fragments == nil { if c.Fragments == nil {
c.Fragments = make(map[string]int) c.Fragments = make(map[string]int)
} }
c.FragmentCursor = make(map[string]int) c.FragmentCursor = map[string]int{fname: 0}
c.FragmentCursor[fname] = 0 c.langIndexToName = nil
out += c.rootIterate(c.tlist[fname], varholder, holdreflect, fname) out += c.rootIterate(c.templateList[fname], varholder, holdreflect, fname)
var importList string var importList string
if c.doImports { if c.doImports {
@ -163,18 +157,31 @@ func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsI
fout := "// +build !no_templategen\n\n// Code generated by Gosora. More below:\n/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */\n" fout := "// +build !no_templategen\n\n// Code generated by Gosora. More below:\n/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */\n"
fout += "package main\n" + importList + c.pVarList + "\n" fout += "package main\n" + importList + "\n"
fout += "var " + fname + "_Tmpl_Phrase_ID int\n\n"
fout += "// nolint\nfunc init() {\n" fout += "// nolint\nfunc init() {\n"
if !c.skipHandles { if !c.config.SkipHandles {
fout += "\tcommon.Template_" + fname + "_handle = Template_" + fname + "\n" fout += "\tcommon.Template_" + fname + "_handle = Template_" + fname + "\n"
fout += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n\tcommon.TmplPtrMap[\"" + fname + "\"] = &common.Template_" + fname + "_handle\n" fout += "\tcommon.Ctemplates = append(common.Ctemplates,\"" + fname + "\")\n\tcommon.TmplPtrMap[\"" + fname + "\"] = &common.Template_" + fname + "_handle\n"
} }
fout += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = Template_" + fname + "\n}\n\n" fout += "\tcommon.TmplPtrMap[\"o_" + fname + "\"] = Template_" + fname + "\n"
if len(c.langIndexToName) > 0 {
fout += "\t" + fname + "_Tmpl_Phrase_ID = common.RegisterTmplPhraseNames([]string{\n"
for _, name := range c.langIndexToName {
fout += "\t\t" + `"` + name + `"` + ",\n"
}
fout += "\t})\n"
}
fout += "}\n\n"
fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars " + expects + ", w http.ResponseWriter) error {\n" + varString + out + "\treturn nil\n}\n" fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars " + expects + ", w http.ResponseWriter) error {\n"
if len(c.langIndexToName) > 0 {
fout += "\tvar phrases = common.GetTmplPhrasesBytes(" + fname + "_Tmpl_Phrase_ID)\n"
}
fout += varString + out + "\treturn nil\n}\n"
fout = strings.Replace(fout, `)) fout = strings.Replace(fout, `))
w.Write([]byte(`, " + ", -1) w.Write([]byte(`, " + ", -1)
@ -183,13 +190,12 @@ w.Write([]byte(`, " + ", -1)
//whitespaceWrites := regexp.MustCompile(`(?s)w.Write\(\[\]byte\(`+spstr+`\)\)`) //whitespaceWrites := regexp.MustCompile(`(?s)w.Write\(\[\]byte\(`+spstr+`\)\)`)
//fout = whitespaceWrites.ReplaceAllString(fout,"") //fout = whitespaceWrites.ReplaceAllString(fout,"")
if c.debug { if c.config.Debug {
for index, count := range c.stats { for index, count := range c.stats {
fmt.Println(index+": ", strconv.Itoa(count)) fmt.Println(index+": ", strconv.Itoa(count))
} }
fmt.Println(" ") fmt.Println(" ")
} }
c.log("Output!") c.log("Output!")
c.log(fout) c.log(fout)
return fout, nil return fout, nil
@ -224,26 +230,26 @@ func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value
case *parse.IfNode: case *parse.IfNode:
c.log("If Node:") c.log("If Node:")
c.log("node.Pipe", node.Pipe) c.log("node.Pipe", node.Pipe)
var expr string var expr string
for _, cmd := range node.Pipe.Cmds { for _, cmd := range node.Pipe.Cmds {
c.log("If Node Bit:", cmd) c.log("If Node Bit:", cmd)
c.log("If Node Bit Type:", reflect.ValueOf(cmd).Type().Name()) c.log("Bit Type:", reflect.ValueOf(cmd).Type().Name())
expr += c.compileVarswitch(varholder, holdreflect, templateName, cmd) expr += c.compileVarswitch(varholder, holdreflect, templateName, cmd)
c.log("If Node Expression Step:", c.compileVarswitch(varholder, holdreflect, templateName, cmd)) c.log("Expression Step:", c.compileVarswitch(varholder, holdreflect, templateName, cmd))
} }
c.log("If Node Expression:", expr) c.log("Expression:", expr)
c.previousNode = c.currentNode c.previousNode = c.currentNode
c.currentNode = parse.NodeList c.currentNode = parse.NodeList
c.nextNode = -1 c.nextNode = -1
out = "if " + expr + " {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.List) + "}"
if node.ElseList == nil { if node.ElseList == nil {
c.log("Selected Branch 1") c.log("Selected Branch 1")
return "if " + expr + " {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.List) + "}\n" return out + "\n"
} }
c.log("Selected Branch 2") c.log("Selected Branch 2")
return "if " + expr + " {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.List) + "} else {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.ElseList) + "}\n" return out + " else {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.ElseList) + "}\n"
case *parse.ListNode: case *parse.ListNode:
c.log("List Node") c.log("List Node")
for _, subnode := range node.Nodes { for _, subnode := range node.Nodes {
@ -279,7 +285,6 @@ func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value
func (c *CTemplateSet) compileRangeNode(varholder string, holdreflect reflect.Value, templateName string, node *parse.RangeNode) (out string) { func (c *CTemplateSet) compileRangeNode(varholder string, holdreflect reflect.Value, templateName string, node *parse.RangeNode) (out string) {
c.log("Range Node!") c.log("Range Node!")
c.log(node.Pipe) c.log(node.Pipe)
var outVal reflect.Value var outVal reflect.Value
for _, cmd := range node.Pipe.Cmds { for _, cmd := range node.Pipe.Cmds {
c.log("Range Bit:", cmd) c.log("Range Bit:", cmd)
@ -294,17 +299,15 @@ func (c *CTemplateSet) compileRangeNode(varholder string, holdreflect reflect.Va
for _, key := range outVal.MapKeys() { for _, key := range outVal.MapKeys() {
item = outVal.MapIndex(key) item = outVal.MapIndex(key)
} }
if c.debug {
fmt.Println("Range item:", item) c.log("Range item:", item)
}
if !item.IsValid() { if !item.IsValid() {
panic("item" + "^\n" + "Invalid map. Maybe, it doesn't have any entries for the template engine to analyse?") panic("item" + "^\n" + "Invalid map. Maybe, it doesn't have any entries for the template engine to analyse?")
} }
out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n}"
if node.ElseList != nil { if node.ElseList != nil {
out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n} else {\n" + c.compileSwitch("item", item, templateName, node.ElseList) + "}\n" out += " else {\n" + c.compileSwitch("item", item, templateName, node.ElseList) + "}\n"
} else {
out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n}"
} }
case reflect.Slice: case reflect.Slice:
if outVal.Len() == 0 { if outVal.Len() == 0 {
@ -312,13 +315,13 @@ func (c *CTemplateSet) compileRangeNode(varholder string, holdreflect reflect.Va
} }
item := outVal.Index(0) item := outVal.Index(0)
out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n}" out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n}"
if node.ElseList != nil {
out += " else {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.ElseList) + "}"
}
case reflect.Invalid: case reflect.Invalid:
return "" return ""
} }
if node.ElseList != nil {
out += " else {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.ElseList) + "}"
}
return out + "\n" return out + "\n"
} }
@ -328,7 +331,6 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
switch n := firstWord.(type) { switch n := firstWord.(type) {
case *parse.FieldNode: case *parse.FieldNode:
c.log("Field Node:", n.Ident) c.log("Field Node:", n.Ident)
/* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Variable declarations are coming soon! */ /* Use reflect to determine if the field is for a method, otherwise assume it's a variable. Variable declarations are coming soon! */
cur := holdreflect cur := holdreflect
@ -362,15 +364,14 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
c.error("Debug Data:") c.error("Debug Data:")
c.error("Holdreflect:", holdreflect) c.error("Holdreflect:", holdreflect)
c.error("Holdreflect.Kind():", holdreflect.Kind()) c.error("Holdreflect.Kind():", holdreflect.Kind())
if !c.superDebug { if !c.config.SuperDebug {
c.error("cur.Kind():", cur.Kind().String()) c.error("cur.Kind():", cur.Kind().String())
} }
c.error("") c.error("")
if !multiline { if !multiline {
panic(varholder + varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?") panic(varholder + varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
} else {
panic(varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
} }
panic(varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
} }
c.log("in-loop varbit: " + varbit) c.log("in-loop varbit: " + varbit)
@ -395,8 +396,8 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
newVarByte = ":" newVarByte = ":"
c.localDispStructIndex++ c.localDispStructIndex++
} }
varbit = "disp" + dispStr + " " + newVarByte + "= " + varholder + varbit + "\n"
varholder = "disp" + dispStr varholder = "disp" + dispStr
varbit = varholder + " " + newVarByte + "= " + varholder + varbit + "\n"
multiline = true multiline = true
} else { } else {
continue continue
@ -466,7 +467,7 @@ func (c *CTemplateSet) compileVarswitch(varholder string, holdreflect reflect.Va
firstWord := node.Args[0] firstWord := node.Args[0]
switch n := firstWord.(type) { switch n := firstWord.(type) {
case *parse.FieldNode: case *parse.FieldNode:
if c.superDebug { if c.config.SuperDebug {
fmt.Println("Field Node:", n.Ident) fmt.Println("Field Node:", n.Ident)
for _, id := range n.Ident { for _, id := range n.Ident {
fmt.Println("Field Bit:", id) fmt.Println("Field Bit:", id)
@ -477,25 +478,24 @@ func (c *CTemplateSet) compileVarswitch(varholder string, holdreflect reflect.Va
return c.compileBoolsub(n.String(), varholder, templateName, holdreflect) return c.compileBoolsub(n.String(), varholder, templateName, holdreflect)
case *parse.ChainNode: case *parse.ChainNode:
c.log("Chain Node:", n.Node) c.log("Chain Node:", n.Node)
c.log("Chain Node Args:", node.Args) c.log("Node Args:", node.Args)
case *parse.IdentifierNode: case *parse.IdentifierNode:
c.log("Identifier Node:", node) c.log("Identifier Node:", node)
c.log("Identifier Node Args:", node.Args) c.log("Node Args:", node.Args)
return c.compileIdentSwitchN(varholder, holdreflect, templateName, node) return c.compileIdentSwitchN(varholder, holdreflect, templateName, node)
case *parse.DotNode: case *parse.DotNode:
return varholder return varholder
case *parse.VariableNode: case *parse.VariableNode:
c.log("Variable Node:", n.String()) c.log("Variable Node:", n.String())
c.log("Variable Node Identifier:", n.Ident) c.log("Node Identifier:", n.Ident)
out, _ = c.compileIfVarsub(n.String(), varholder, templateName, holdreflect) out, _ = c.compileIfVarsub(n.String(), varholder, templateName, holdreflect)
case *parse.NilNode: case *parse.NilNode:
panic("Nil is not a command x.x") panic("Nil is not a command x.x")
case *parse.PipeNode: case *parse.PipeNode:
c.log("Pipe Node!") c.log("Pipe Node!")
c.log(n) c.log(n)
c.log("Args:", node.Args) c.log("Node Args:", node.Args)
out += c.compileIdentSwitchN(varholder, holdreflect, templateName, node) out += c.compileIdentSwitchN(varholder, holdreflect, templateName, node)
c.log("Out:", out) c.log("Out:", out)
default: default:
return c.unknownNode(firstWord) return c.unknownNode(firstWord)
@ -539,7 +539,6 @@ func (c *CTemplateSet) simpleMath(varholder string, holdreflect reflect.Value, t
numSample := 1 numSample := 1
val = reflect.ValueOf(numSample) val = reflect.ValueOf(numSample)
} }
c.dumpSymbol(pos, node, symbol) c.dumpSymbol(pos, node, symbol)
return leftParam + " " + symbol + " " + rightParam, val return leftParam + " " + symbol + " " + rightParam, val
} }
@ -647,16 +646,34 @@ ArgLoop:
} }
rightParam, val3 := c.compileIfVarsub(rightOperand, varholder, templateName, holdreflect) rightParam, val3 := c.compileIfVarsub(rightOperand, varholder, templateName, holdreflect)
if val3.IsValid() { if !val3.IsValid() {
val = val3
} else {
panic("val3 is invalid") panic("val3 is invalid")
} }
val = val3
// TODO: Refactor this // TODO: Refactor this
out = "w.Write([]byte(common.BuildWidget(" + leftParam + "," + rightParam + ")))\n" out = "w.Write([]byte(common.BuildWidget(" + leftParam + "," + rightParam + ")))\n"
literal = true literal = true
break ArgLoop break ArgLoop
case "lang":
var leftParam string
// TODO: Implement string literals properly
leftOperand := node.Args[pos+1].String()
if len(leftOperand) == 0 {
panic("The left operand for the language string cannot be left blank")
}
if leftOperand[0] == '"' {
// ! Slightly crude but it does the job
leftParam = strings.Replace(leftOperand, "\"", "", -1)
} else {
panic("Phrase names cannot be dynamic")
}
c.langIndexToName = append(c.langIndexToName, leftParam)
out = "w.Write(phrases[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n"
literal = true
break ArgLoop
default: default:
c.log("Variable!") c.log("Variable!")
if len(node.Args) > (pos + 1) { if len(node.Args) > (pos + 1) {
@ -676,7 +693,7 @@ func (c *CTemplateSet) compileReflectSwitch(varholder string, holdreflect reflec
firstWord := node.Args[0] firstWord := node.Args[0]
switch n := firstWord.(type) { switch n := firstWord.(type) {
case *parse.FieldNode: case *parse.FieldNode:
if c.superDebug { if c.config.SuperDebug {
fmt.Println("Field Node:", n.Ident) fmt.Println("Field Node:", n.Ident)
for _, id := range n.Ident { for _, id := range n.Ident {
fmt.Println("Field Bit:", id) fmt.Println("Field Bit:", id)
@ -759,7 +776,6 @@ func (c *CTemplateSet) compileIfVarsub(varname string, varholder string, templat
if !cur.IsValid() { if !cur.IsValid() {
panic(out + "^\n" + "Invalid value. Maybe, it doesn't exist?") panic(out + "^\n" + "Invalid value. Maybe, it doesn't exist?")
} }
c.log("Data Kind:", cur.Kind()) c.log("Data Kind:", cur.Kind())
c.log("Data Type:", cur.Type().Name()) c.log("Data Type:", cur.Type().Name())
} }
@ -887,13 +903,13 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
} }
// TODO: Cascade errors back up the tree to the caller? // TODO: Cascade errors back up the tree to the caller?
res, err := ioutil.ReadFile(c.dir + node.Name) res, err := ioutil.ReadFile(c.fileDir + node.Name)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
content := string(res) content := string(res)
if c.minify { if c.config.Minify {
content = minify(content) content = minify(content)
} }
@ -904,8 +920,8 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
log.Fatal(err) log.Fatal(err)
} }
c.tlist[fname] = tree c.templateList[fname] = tree
subtree := c.tlist[fname] subtree := c.templateList[fname]
c.log("subtree.Root", subtree.Root) c.log("subtree.Root", subtree.Root)
c.localVars[fname] = make(map[string]VarItemReflect) c.localVars[fname] = make(map[string]VarItemReflect)
@ -916,68 +932,22 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
return out return out
} }
// TODO: Should we rethink the way the log methods work or their names?
func (c *CTemplateSet) log(args ...interface{}) { func (c *CTemplateSet) log(args ...interface{}) {
if c.superDebug { if c.config.SuperDebug {
fmt.Println(args...) fmt.Println(args...)
} }
} }
func (c *CTemplateSet) logf(left string, args ...interface{}) { func (c *CTemplateSet) logf(left string, args ...interface{}) {
if c.superDebug { if c.config.SuperDebug {
fmt.Printf(left, args...) fmt.Printf(left, args...)
} }
} }
func (c *CTemplateSet) error(args ...interface{}) { func (c *CTemplateSet) error(args ...interface{}) {
if c.debug { if c.config.Debug {
fmt.Println(args...) fmt.Println(args...)
} }
} }
// TODO: Write unit tests for this
func minify(data string) string {
data = strings.Replace(data, "\t", "", -1)
data = strings.Replace(data, "\v", "", -1)
data = strings.Replace(data, "\n", "", -1)
data = strings.Replace(data, "\r", "", -1)
data = strings.Replace(data, " ", " ", -1)
return data
}
// TODO: Strip comments
// TODO: Handle CSS nested in <style> tags?
// TODO: Write unit tests for this
func minifyHTML(data string) string {
return minify(data)
}
// TODO: Have static files use this
// TODO: Strip comments
// TODO: Convert the rgb()s to hex codes?
// TODO: Write unit tests for this
func minifyCSS(data string) string {
return minify(data)
}
// TODO: Convert this to three character hex strings whenever possible?
// TODO: Write unit tests for this
// nolint
func rgbToHexstr(red int, green int, blue int) string {
return strconv.FormatInt(int64(red), 16) + strconv.FormatInt(int64(green), 16) + strconv.FormatInt(int64(blue), 16)
}
/*
// TODO: Write unit tests for this
func hexstrToRgb(hexstr string) (red int, blue int, green int, err error) {
// Strip the # at the start
if hexstr[0] == '#' {
hexstr = strings.TrimPrefix(hexstr,"#")
}
if len(hexstr) != 3 && len(hexstr) != 6 {
return 0, 0, 0, errors.New("Hex colour codes may only be three or six characters long")
}
if len(hexstr) == 3 {
hexstr = hexstr[0] + hexstr[0] + hexstr[1] + hexstr[1] + hexstr[2] + hexstr[2]
}
}*/

View File

@ -317,6 +317,15 @@ func (theme *Theme) MapTemplates() {
default: default:
log.Fatal("The source and destination templates are incompatible") log.Fatal("The source and destination templates are incompatible")
} }
case *func(IPSearchPage, http.ResponseWriter):
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(IPSearchPage, http.ResponseWriter):
//overridenTemplates[themeTmpl.Name] = d_tmpl_ptr
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
log.Fatal("The source and destination templates are incompatible")
}
case *func(Page, http.ResponseWriter): case *func(Page, http.ResponseWriter):
switch sTmplPtr := sourceTmplPtr.(type) { switch sTmplPtr := sourceTmplPtr.(type) {
case *func(Page, http.ResponseWriter): case *func(Page, http.ResponseWriter):
@ -395,6 +404,13 @@ func ResetTemplateOverrides() {
default: default:
log.Fatal("The origin and destination templates are incompatible") log.Fatal("The origin and destination templates are incompatible")
} }
case func(IPSearchPage, http.ResponseWriter):
switch dPtr := destTmplPtr.(type) {
case *func(IPSearchPage, http.ResponseWriter):
*dPtr = oPtr
default:
log.Fatal("The origin and destination templates are incompatible")
}
case func(Page, http.ResponseWriter): case func(Page, http.ResponseWriter):
switch dPtr := destTmplPtr.(type) { switch dPtr := destTmplPtr.(type) {
case *func(Page, http.ResponseWriter): case *func(Page, http.ResponseWriter):
@ -435,6 +451,9 @@ func RunThemeTemplate(theme string, template string, pi interface{}, w http.Resp
case *func(CreateTopicPage, http.ResponseWriter) error: case *func(CreateTopicPage, http.ResponseWriter) error:
var tmpl = *tmplO var tmpl = *tmplO
return tmpl(pi.(CreateTopicPage), w) return tmpl(pi.(CreateTopicPage), w)
case *func(IPSearchPage, http.ResponseWriter) error:
var tmpl = *tmplO
return tmpl(pi.(IPSearchPage), w)
case *func(Page, http.ResponseWriter) error: case *func(Page, http.ResponseWriter) error:
var tmpl = *tmplO var tmpl = *tmplO
return tmpl(pi.(Page), w) return tmpl(pi.(Page), w)
@ -450,6 +469,8 @@ func RunThemeTemplate(theme string, template string, pi interface{}, w http.Resp
return tmplO(pi.(ProfilePage), w) return tmplO(pi.(ProfilePage), w)
case func(CreateTopicPage, http.ResponseWriter) error: case func(CreateTopicPage, http.ResponseWriter) error:
return tmplO(pi.(CreateTopicPage), w) return tmplO(pi.(CreateTopicPage), w)
case func(IPSearchPage, http.ResponseWriter) error:
return tmplO(pi.(IPSearchPage), w)
case func(Page, http.ResponseWriter) error: case func(Page, http.ResponseWriter) error:
return tmplO(pi.(Page), w) return tmplO(pi.(Page), w)
case string: case string:

View File

@ -49,7 +49,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
// TODO: Add an admin version of registerStmt with more flexibility? // TODO: Add an admin version of registerStmt with more flexibility?
return &DefaultUserStore{ return &DefaultUserStore{
cache: cache, cache: cache,
get: acc.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", ""), get: acc.SimpleSelect("users", "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", ""),
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""), exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), // TODO: Implement user_count on users_groups here register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), // TODO: Implement user_count on users_groups here
usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""), usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""),
@ -64,7 +64,7 @@ func (mus *DefaultUserStore) DirtyGet(id int) *User {
} }
user = &User{ID: id, Loggedin: true} user = &User{ID: id, Loggedin: true}
err = mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err = mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Active, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
user.Init() user.Init()
if err == nil { if err == nil {
@ -82,7 +82,7 @@ func (mus *DefaultUserStore) Get(id int) (*User, error) {
} }
user = &User{ID: id, Loggedin: true} user = &User{ID: id, Loggedin: true}
err = mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err = mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Active, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
user.Init() user.Init()
if err == nil { if err == nil {
@ -126,14 +126,14 @@ func (mus *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err erro
qlist = qlist[0 : len(qlist)-1] qlist = qlist[0 : len(qlist)-1]
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
rows, err := acc.Select("users").Columns("uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group").Where("uid IN(" + qlist + ")").Query(uidList...) rows, err := acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group").Where("uid IN(" + qlist + ")").Query(uidList...)
if err != nil { if err != nil {
return list, err return list, err
} }
for rows.Next() { for rows.Next() {
user := &User{Loggedin: true} user := &User{Loggedin: true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Active, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil { if err != nil {
return list, err return list, err
} }
@ -174,7 +174,7 @@ func (mus *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err erro
func (mus *DefaultUserStore) BypassGet(id int) (*User, error) { func (mus *DefaultUserStore) BypassGet(id int) (*User, error) {
user := &User{ID: id, Loggedin: true} user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Active, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
user.Init() user.Init()
return user, err return user, err
@ -182,7 +182,7 @@ func (mus *DefaultUserStore) BypassGet(id int) (*User, error) {
func (mus *DefaultUserStore) Reload(id int) error { func (mus *DefaultUserStore) Reload(id int) error {
user := &User{ID: id, Loggedin: true} user := &User{ID: id, Loggedin: true}
err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup) err := mus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Active, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil { if err != nil {
mus.cache.Remove(id) mus.cache.Remove(id)
return err return err

View File

@ -6,7 +6,7 @@
* Copyright Azareal 2017 - 2018 * Copyright Azareal 2017 - 2018
* *
*/ */
package main package common
import ( import (
"bytes" "bytes"
@ -18,7 +18,6 @@ import (
"sync" "sync"
"time" "time"
"./common"
"github.com/Azareal/gopsutil/cpu" "github.com/Azareal/gopsutil/cpu"
"github.com/Azareal/gopsutil/mem" "github.com/Azareal/gopsutil/mem"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -26,60 +25,60 @@ import (
type WSUser struct { type WSUser struct {
conn *websocket.Conn conn *websocket.Conn
User *common.User User *User
} }
type WSHub struct { type WSHub struct {
onlineUsers map[int]*WSUser OnlineUsers map[int]*WSUser
onlineGuests map[*WSUser]bool OnlineGuests map[*WSUser]bool
guests sync.RWMutex GuestLock sync.RWMutex
users sync.RWMutex UserLock sync.RWMutex
} }
// TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it? // TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it?
var enableWebsockets = true // Put this in caps for consistency with the other constants? var EnableWebsockets = true // Put this in caps for consistency with the other constants?
var wsHub WSHub var WsHub WSHub
var wsUpgrader = websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024} var wsUpgrader = websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}
var errWsNouser = errors.New("This user isn't connected via WebSockets") var errWsNouser = errors.New("This user isn't connected via WebSockets")
func init() { func init() {
adminStatsWatchers = make(map[*WSUser]bool) adminStatsWatchers = make(map[*WSUser]bool)
wsHub = WSHub{ WsHub = WSHub{
onlineUsers: make(map[int]*WSUser), OnlineUsers: make(map[int]*WSUser),
onlineGuests: make(map[*WSUser]bool), OnlineGuests: make(map[*WSUser]bool),
} }
} }
func (hub *WSHub) guestCount() int { func (hub *WSHub) GuestCount() int {
defer hub.guests.RUnlock() defer hub.GuestLock.RUnlock()
hub.guests.RLock() hub.GuestLock.RLock()
return len(hub.onlineGuests) return len(hub.OnlineGuests)
} }
func (hub *WSHub) userCount() int { func (hub *WSHub) UserCount() int {
defer hub.users.RUnlock() defer hub.UserLock.RUnlock()
hub.users.RLock() hub.UserLock.RLock()
return len(hub.onlineUsers) return len(hub.OnlineUsers)
} }
func (hub *WSHub) broadcastMessage(msg string) error { func (hub *WSHub) broadcastMessage(msg string) error {
hub.users.RLock() hub.UserLock.RLock()
for _, wsUser := range hub.onlineUsers { for _, wsUser := range hub.OnlineUsers {
w, err := wsUser.conn.NextWriter(websocket.TextMessage) w, err := wsUser.conn.NextWriter(websocket.TextMessage)
if err != nil { if err != nil {
return err return err
} }
_, _ = w.Write([]byte(msg)) _, _ = w.Write([]byte(msg))
} }
hub.users.RUnlock() hub.UserLock.RUnlock()
return nil return nil
} }
func (hub *WSHub) pushMessage(targetUser int, msg string) error { func (hub *WSHub) pushMessage(targetUser int, msg string) error {
hub.users.RLock() hub.UserLock.RLock()
wsUser, ok := hub.onlineUsers[targetUser] wsUser, ok := hub.OnlineUsers[targetUser]
hub.users.RUnlock() hub.UserLock.RUnlock()
if !ok { if !ok {
return errWsNouser return errWsNouser
} }
@ -96,15 +95,15 @@ func (hub *WSHub) pushMessage(targetUser int, msg string) error {
func (hub *WSHub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error { func (hub *WSHub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
//log.Print("In pushAlert") //log.Print("In pushAlert")
hub.users.RLock() hub.UserLock.RLock()
wsUser, ok := hub.onlineUsers[targetUser] wsUser, ok := hub.OnlineUsers[targetUser]
hub.users.RUnlock() hub.UserLock.RUnlock()
if !ok { if !ok {
return errWsNouser return errWsNouser
} }
//log.Print("Building alert") //log.Print("Building alert")
alert, err := buildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User) alert, err := BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User)
if err != nil { if err != nil {
return err return err
} }
@ -122,12 +121,12 @@ func (hub *WSHub) pushAlert(targetUser int, asid int, event string, elementType
func (hub *WSHub) pushAlerts(users []int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error { func (hub *WSHub) pushAlerts(users []int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
var wsUsers []*WSUser var wsUsers []*WSUser
hub.users.RLock() hub.UserLock.RLock()
// We don't want to keep a lock on this for too long, so we'll accept some nil pointers // We don't want to keep a lock on this for too long, so we'll accept some nil pointers
for _, uid := range users { for _, uid := range users {
wsUsers = append(wsUsers, hub.onlineUsers[uid]) wsUsers = append(wsUsers, hub.OnlineUsers[uid])
} }
hub.users.RUnlock() hub.UserLock.RUnlock()
if len(wsUsers) == 0 { if len(wsUsers) == 0 {
return errWsNouser return errWsNouser
} }
@ -138,7 +137,7 @@ func (hub *WSHub) pushAlerts(users []int, asid int, event string, elementType st
continue continue
} }
alert, err := buildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User) alert, err := BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
@ -161,25 +160,26 @@ func (hub *WSHub) pushAlerts(users []int, asid int, event string, elementType st
} }
// TODO: How should we handle errors for this? // TODO: How should we handle errors for this?
func routeWebsockets(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { // TODO: Move this out of common?
func RouteWebsockets(w http.ResponseWriter, r *http.Request, user User) RouteError {
conn, err := wsUpgrader.Upgrade(w, r, nil) conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
return nil return nil
} }
userptr, err := common.Users.Get(user.ID) userptr, err := Users.Get(user.ID)
if err != nil && err != common.ErrStoreCapacityOverflow { if err != nil && err != ErrStoreCapacityOverflow {
return nil return nil
} }
wsUser := &WSUser{conn, userptr} wsUser := &WSUser{conn, userptr}
if user.ID == 0 { if user.ID == 0 {
wsHub.guests.Lock() WsHub.GuestLock.Lock()
wsHub.onlineGuests[wsUser] = true WsHub.OnlineGuests[wsUser] = true
wsHub.guests.Unlock() WsHub.GuestLock.Unlock()
} else { } else {
wsHub.users.Lock() WsHub.UserLock.Lock()
wsHub.onlineUsers[user.ID] = wsUser WsHub.OnlineUsers[user.ID] = wsUser
wsHub.users.Unlock() WsHub.UserLock.Unlock()
} }
//conn.SetReadLimit(/* put the max request size from earlier here? */) //conn.SetReadLimit(/* put the max request size from earlier here? */)
@ -189,13 +189,13 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user common.User) c
_, message, err := conn.ReadMessage() _, message, err := conn.ReadMessage()
if err != nil { if err != nil {
if user.ID == 0 { if user.ID == 0 {
wsHub.guests.Lock() WsHub.GuestLock.Lock()
delete(wsHub.onlineGuests, wsUser) delete(WsHub.OnlineGuests, wsUser)
wsHub.guests.Unlock() WsHub.GuestLock.Unlock()
} else { } else {
wsHub.users.Lock() WsHub.UserLock.Lock()
delete(wsHub.onlineUsers, user.ID) delete(WsHub.OnlineUsers, user.ID)
wsHub.users.Unlock() WsHub.UserLock.Unlock()
} }
break break
} }
@ -239,9 +239,9 @@ func wsPageResponses(wsUser *WSUser, page []byte) {
return return
} }
log.Print(wsHub.online_users) log.Print(WsHub.online_users)
uonline := wsHub.userCount() uonline := WsHub.UserCount()
gonline := wsHub.guestCount() gonline := WsHub.GuestCount()
totonline := uonline + gonline totonline := uonline + gonline
w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + " online\r")) w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + " online\r"))
@ -321,8 +321,8 @@ AdminStatLoop:
cpuPerc, cpuerr = cpu.Percent(time.Second, true) cpuPerc, cpuerr = cpu.Percent(time.Second, true)
memres, ramerr = mem.VirtualMemory() memres, ramerr = mem.VirtualMemory()
uonline := wsHub.userCount() uonline := WsHub.UserCount()
gonline := wsHub.guestCount() gonline := WsHub.GuestCount()
totonline := uonline + gonline totonline := uonline + gonline
reqCount := 0 reqCount := 0
@ -339,9 +339,9 @@ AdminStatLoop:
onlineGuestsColour = greaterThanSwitch(gonline, 1, 10) onlineGuestsColour = greaterThanSwitch(gonline, 1, 10)
onlineUsersColour = greaterThanSwitch(uonline, 1, 5) onlineUsersColour = greaterThanSwitch(uonline, 1, 5)
totonline, totunit = common.ConvertFriendlyUnit(totonline) totonline, totunit = ConvertFriendlyUnit(totonline)
uonline, uunit = common.ConvertFriendlyUnit(uonline) uonline, uunit = ConvertFriendlyUnit(uonline)
gonline, gunit = common.ConvertFriendlyUnit(gonline) gonline, gunit = ConvertFriendlyUnit(gonline)
} }
if cpuerr != nil { if cpuerr != nil {
@ -363,8 +363,8 @@ AdminStatLoop:
if ramerr != nil { if ramerr != nil {
ramstr = "Unknown" ramstr = "Unknown"
} else { } else {
totalCount, totalUnit := common.ConvertByteUnit(float64(memres.Total)) totalCount, totalUnit := ConvertByteUnit(float64(memres.Total))
usedCount := common.ConvertByteInUnit(float64(memres.Total-memres.Available), totalUnit) usedCount := ConvertByteInUnit(float64(memres.Total-memres.Available), totalUnit)
// Round totals with .9s up, it's how most people see it anyway. Floats are notoriously imprecise, so do it off 0.85 // Round totals with .9s up, it's how most people see it anyway. Floats are notoriously imprecise, so do it off 0.85
var totstr string var totstr string

View File

@ -20,15 +20,11 @@ type Stmts struct {
getUserName *sql.Stmt getUserName *sql.Stmt
getEmailsByUser *sql.Stmt getEmailsByUser *sql.Stmt
getTopicBasic *sql.Stmt getTopicBasic *sql.Stmt
getActivityEntry *sql.Stmt
forumEntryExists *sql.Stmt forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getAttachment *sql.Stmt getAttachment *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
getWatchers *sql.Stmt
createReport *sql.Stmt createReport *sql.Stmt
addActivity *sql.Stmt
notifyOne *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt addPlugin *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
@ -48,7 +44,6 @@ type Stmts struct {
deleteActivityStreamMatch *sql.Stmt deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt deleteWordFilter *sql.Stmt
reportExists *sql.Stmt reportExists *sql.Stmt
notifyWatchers *sql.Stmt
getActivityFeedByWatcher *sql.Stmt getActivityFeedByWatcher *sql.Stmt
getActivityCountByWatcher *sql.Stmt getActivityCountByWatcher *sql.Stmt
@ -152,14 +147,6 @@ func _gen_mssql() (err error) {
return err return err
} }
common.DebugLog("Preparing getActivityEntry statement.")
stmts.getActivityEntry, err = db.Prepare("SELECT [actor],[targetUser],[event],[elementType],[elementID] FROM [activity_stream] WHERE [asid] = ?1")
if err != nil {
log.Print("Error in getActivityEntry statement.")
log.Print("Bad Query: ","SELECT [actor],[targetUser],[event],[elementType],[elementID] FROM [activity_stream] WHERE [asid] = ?1")
return err
}
common.DebugLog("Preparing forumEntryExists statement.") common.DebugLog("Preparing forumEntryExists statement.")
stmts.forumEntryExists, err = db.Prepare("SELECT [fid] FROM [forums] WHERE [name] = '' ORDER BY fid ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY") stmts.forumEntryExists, err = db.Prepare("SELECT [fid] FROM [forums] WHERE [name] = '' ORDER BY fid ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY")
if err != nil { if err != nil {
@ -192,14 +179,6 @@ func _gen_mssql() (err error) {
return err return err
} }
common.DebugLog("Preparing getWatchers statement.")
stmts.getWatchers, err = db.Prepare("SELECT [activity_subscriptions].[user] FROM [activity_stream] INNER JOIN [activity_subscriptions] ON [activity_subscriptions].[targetType] = [activity_stream].[elementType] AND [activity_subscriptions].[targetID] = [activity_stream].[elementID] AND [activity_subscriptions].[user] != [activity_stream].[actor] WHERE [asid] = ?1")
if err != nil {
log.Print("Error in getWatchers statement.")
log.Print("Bad Query: ","SELECT [activity_subscriptions].[user] FROM [activity_stream] INNER JOIN [activity_subscriptions] ON [activity_subscriptions].[targetType] = [activity_stream].[elementType] AND [activity_subscriptions].[targetID] = [activity_stream].[elementID] AND [activity_subscriptions].[user] != [activity_stream].[actor] WHERE [asid] = ?1")
return err
}
common.DebugLog("Preparing createReport statement.") common.DebugLog("Preparing createReport statement.")
stmts.createReport, err = db.Prepare("INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[createdBy],[lastReplyBy],[data],[parentID],[css_class]) VALUES (?,?,?,GETUTCDATE(),GETUTCDATE(),?,?,?,1,'report')") stmts.createReport, err = db.Prepare("INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[createdBy],[lastReplyBy],[data],[parentID],[css_class]) VALUES (?,?,?,GETUTCDATE(),GETUTCDATE(),?,?,?,1,'report')")
if err != nil { if err != nil {
@ -208,22 +187,6 @@ func _gen_mssql() (err error) {
return err return err
} }
common.DebugLog("Preparing addActivity statement.")
stmts.addActivity, err = db.Prepare("INSERT INTO [activity_stream] ([actor],[targetUser],[event],[elementType],[elementID]) VALUES (?,?,?,?,?)")
if err != nil {
log.Print("Error in addActivity statement.")
log.Print("Bad Query: ","INSERT INTO [activity_stream] ([actor],[targetUser],[event],[elementType],[elementID]) VALUES (?,?,?,?,?)")
return err
}
common.DebugLog("Preparing notifyOne statement.")
stmts.notifyOne, err = db.Prepare("INSERT INTO [activity_stream_matches] ([watcher],[asid]) VALUES (?,?)")
if err != nil {
log.Print("Error in notifyOne statement.")
log.Print("Bad Query: ","INSERT INTO [activity_stream_matches] ([watcher],[asid]) VALUES (?,?)")
return err
}
common.DebugLog("Preparing addForumPermsToForum statement.") common.DebugLog("Preparing addForumPermsToForum statement.")
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) VALUES (?,?,?,?)") stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) VALUES (?,?,?,?)")
if err != nil { if err != nil {
@ -376,13 +339,5 @@ func _gen_mssql() (err error) {
return err return err
} }
common.DebugLog("Preparing notifyWatchers statement.")
stmts.notifyWatchers, err = db.Prepare("INSERT INTO [activity_stream_matches] ([watcher],[asid]) SELECT [activity_subscriptions].[user],[activity_stream].[asid] FROM [activity_stream] INNER JOIN [activity_subscriptions] ON [activity_subscriptions].[targetType] = [activity_stream].[elementType] AND [activity_subscriptions].[targetID] = [activity_stream].[elementID] AND [activity_subscriptions].[user] != [activity_stream].[actor] WHERE [asid] = ?1")
if err != nil {
log.Print("Error in notifyWatchers statement.")
log.Print("Bad Query: ","INSERT INTO [activity_stream_matches] ([watcher],[asid]) SELECT [activity_subscriptions].[user],[activity_stream].[asid] FROM [activity_stream] INNER JOIN [activity_subscriptions] ON [activity_subscriptions].[targetType] = [activity_stream].[elementType] AND [activity_subscriptions].[targetID] = [activity_stream].[elementID] AND [activity_subscriptions].[user] != [activity_stream].[actor] WHERE [asid] = ?1")
return err
}
return nil return nil
} }

View File

@ -22,15 +22,11 @@ type Stmts struct {
getUserName *sql.Stmt getUserName *sql.Stmt
getEmailsByUser *sql.Stmt getEmailsByUser *sql.Stmt
getTopicBasic *sql.Stmt getTopicBasic *sql.Stmt
getActivityEntry *sql.Stmt
forumEntryExists *sql.Stmt forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getAttachment *sql.Stmt getAttachment *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
getWatchers *sql.Stmt
createReport *sql.Stmt createReport *sql.Stmt
addActivity *sql.Stmt
notifyOne *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt addPlugin *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
@ -50,7 +46,6 @@ type Stmts struct {
deleteActivityStreamMatch *sql.Stmt deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt deleteWordFilter *sql.Stmt
reportExists *sql.Stmt reportExists *sql.Stmt
notifyWatchers *sql.Stmt
getActivityFeedByWatcher *sql.Stmt getActivityFeedByWatcher *sql.Stmt
getActivityCountByWatcher *sql.Stmt getActivityCountByWatcher *sql.Stmt
@ -143,13 +138,6 @@ func _gen_mysql() (err error) {
return err return err
} }
common.DebugLog("Preparing getActivityEntry statement.")
stmts.getActivityEntry, err = db.Prepare("SELECT `actor`,`targetUser`,`event`,`elementType`,`elementID` FROM `activity_stream` WHERE `asid` = ?")
if err != nil {
log.Print("Error in getActivityEntry statement.")
return err
}
common.DebugLog("Preparing forumEntryExists statement.") common.DebugLog("Preparing forumEntryExists statement.")
stmts.forumEntryExists, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' ORDER BY fid ASC LIMIT 0,1") stmts.forumEntryExists, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' ORDER BY fid ASC LIMIT 0,1")
if err != nil { if err != nil {
@ -178,13 +166,6 @@ func _gen_mysql() (err error) {
return err return err
} }
common.DebugLog("Preparing getWatchers statement.")
stmts.getWatchers, err = db.Prepare("SELECT `activity_subscriptions`.`user` FROM `activity_stream` INNER JOIN `activity_subscriptions` ON `activity_subscriptions`.`targetType` = `activity_stream`.`elementType` AND `activity_subscriptions`.`targetID` = `activity_stream`.`elementID` AND `activity_subscriptions`.`user` != `activity_stream`.`actor` WHERE `asid` = ?")
if err != nil {
log.Print("Error in getWatchers statement.")
return err
}
common.DebugLog("Preparing createReport statement.") common.DebugLog("Preparing createReport statement.")
stmts.createReport, err = db.Prepare("INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`createdBy`,`lastReplyBy`,`data`,`parentID`,`css_class`) VALUES (?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,1,'report')") stmts.createReport, err = db.Prepare("INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`createdBy`,`lastReplyBy`,`data`,`parentID`,`css_class`) VALUES (?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,1,'report')")
if err != nil { if err != nil {
@ -192,20 +173,6 @@ func _gen_mysql() (err error) {
return err return err
} }
common.DebugLog("Preparing addActivity statement.")
stmts.addActivity, err = db.Prepare("INSERT INTO `activity_stream`(`actor`,`targetUser`,`event`,`elementType`,`elementID`) VALUES (?,?,?,?,?)")
if err != nil {
log.Print("Error in addActivity statement.")
return err
}
common.DebugLog("Preparing notifyOne statement.")
stmts.notifyOne, err = db.Prepare("INSERT INTO `activity_stream_matches`(`watcher`,`asid`) VALUES (?,?)")
if err != nil {
log.Print("Error in notifyOne statement.")
return err
}
common.DebugLog("Preparing addForumPermsToForum statement.") common.DebugLog("Preparing addForumPermsToForum statement.")
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)") stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)")
if err != nil { if err != nil {
@ -339,12 +306,5 @@ func _gen_mysql() (err error) {
return err return err
} }
common.DebugLog("Preparing notifyWatchers statement.")
stmts.notifyWatchers, err = db.Prepare("INSERT INTO `activity_stream_matches`(`watcher`,`asid`) SELECT `activity_subscriptions`.`user`, `activity_stream`.`asid` FROM `activity_stream` INNER JOIN `activity_subscriptions` ON `activity_subscriptions`.`targetType` = `activity_stream`.`elementType` AND `activity_subscriptions`.`targetID` = `activity_stream`.`elementID` AND `activity_subscriptions`.`user` != `activity_stream`.`actor` WHERE `asid` = ?")
if err != nil {
log.Print("Error in notifyWatchers statement.")
return err
}
return nil return nil
} }

View File

@ -5,6 +5,7 @@ package main
import ( import (
"log" "log"
"strings" "strings"
"strconv"
"sync" "sync"
"errors" "errors"
"net/http" "net/http"
@ -24,7 +25,7 @@ var RouteMap = map[string]interface{}{
"routes.ViewForum": routes.ViewForum, "routes.ViewForum": routes.ViewForum,
"routeChangeTheme": routeChangeTheme, "routeChangeTheme": routeChangeTheme,
"routeShowAttachment": routeShowAttachment, "routeShowAttachment": routeShowAttachment,
"routeWebsockets": routeWebsockets, "common.RouteWebsockets": common.RouteWebsockets,
"routeReportSubmit": routeReportSubmit, "routeReportSubmit": routeReportSubmit,
"routes.CreateTopic": routes.CreateTopic, "routes.CreateTopic": routes.CreateTopic,
"routes.TopicList": routes.TopicList, "routes.TopicList": routes.TopicList,
@ -58,11 +59,13 @@ var RouteMap = map[string]interface{}{
"routePanelAnalyticsRoutes": routePanelAnalyticsRoutes, "routePanelAnalyticsRoutes": routePanelAnalyticsRoutes,
"routePanelAnalyticsAgents": routePanelAnalyticsAgents, "routePanelAnalyticsAgents": routePanelAnalyticsAgents,
"routePanelAnalyticsSystems": routePanelAnalyticsSystems, "routePanelAnalyticsSystems": routePanelAnalyticsSystems,
"routePanelAnalyticsLanguages": routePanelAnalyticsLanguages,
"routePanelAnalyticsReferrers": routePanelAnalyticsReferrers, "routePanelAnalyticsReferrers": routePanelAnalyticsReferrers,
"routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews, "routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews,
"routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews, "routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews,
"routePanelAnalyticsForumViews": routePanelAnalyticsForumViews, "routePanelAnalyticsForumViews": routePanelAnalyticsForumViews,
"routePanelAnalyticsSystemViews": routePanelAnalyticsSystemViews, "routePanelAnalyticsSystemViews": routePanelAnalyticsSystemViews,
"routePanelAnalyticsLanguageViews": routePanelAnalyticsLanguageViews,
"routePanelAnalyticsReferrerViews": routePanelAnalyticsReferrerViews, "routePanelAnalyticsReferrerViews": routePanelAnalyticsReferrerViews,
"routePanelAnalyticsPosts": routePanelAnalyticsPosts, "routePanelAnalyticsPosts": routePanelAnalyticsPosts,
"routePanelAnalyticsTopics": routePanelAnalyticsTopics, "routePanelAnalyticsTopics": routePanelAnalyticsTopics,
@ -100,7 +103,7 @@ var RouteMap = map[string]interface{}{
"routes.MoveTopicSubmit": routes.MoveTopicSubmit, "routes.MoveTopicSubmit": routes.MoveTopicSubmit,
"routeLikeTopicSubmit": routeLikeTopicSubmit, "routeLikeTopicSubmit": routeLikeTopicSubmit,
"routes.ViewTopic": routes.ViewTopic, "routes.ViewTopic": routes.ViewTopic,
"routeCreateReplySubmit": routeCreateReplySubmit, "routes.CreateReplySubmit": routes.CreateReplySubmit,
"routes.ReplyEditSubmit": routes.ReplyEditSubmit, "routes.ReplyEditSubmit": routes.ReplyEditSubmit,
"routes.ReplyDeleteSubmit": routes.ReplyDeleteSubmit, "routes.ReplyDeleteSubmit": routes.ReplyDeleteSubmit,
"routeReplyLikeSubmit": routeReplyLikeSubmit, "routeReplyLikeSubmit": routeReplyLikeSubmit,
@ -129,7 +132,7 @@ var routeMapEnum = map[string]int{
"routes.ViewForum": 4, "routes.ViewForum": 4,
"routeChangeTheme": 5, "routeChangeTheme": 5,
"routeShowAttachment": 6, "routeShowAttachment": 6,
"routeWebsockets": 7, "common.RouteWebsockets": 7,
"routeReportSubmit": 8, "routeReportSubmit": 8,
"routes.CreateTopic": 9, "routes.CreateTopic": 9,
"routes.TopicList": 10, "routes.TopicList": 10,
@ -163,66 +166,68 @@ var routeMapEnum = map[string]int{
"routePanelAnalyticsRoutes": 38, "routePanelAnalyticsRoutes": 38,
"routePanelAnalyticsAgents": 39, "routePanelAnalyticsAgents": 39,
"routePanelAnalyticsSystems": 40, "routePanelAnalyticsSystems": 40,
"routePanelAnalyticsReferrers": 41, "routePanelAnalyticsLanguages": 41,
"routePanelAnalyticsRouteViews": 42, "routePanelAnalyticsReferrers": 42,
"routePanelAnalyticsAgentViews": 43, "routePanelAnalyticsRouteViews": 43,
"routePanelAnalyticsForumViews": 44, "routePanelAnalyticsAgentViews": 44,
"routePanelAnalyticsSystemViews": 45, "routePanelAnalyticsForumViews": 45,
"routePanelAnalyticsReferrerViews": 46, "routePanelAnalyticsSystemViews": 46,
"routePanelAnalyticsPosts": 47, "routePanelAnalyticsLanguageViews": 47,
"routePanelAnalyticsTopics": 48, "routePanelAnalyticsReferrerViews": 48,
"routePanelAnalyticsForums": 49, "routePanelAnalyticsPosts": 49,
"routePanelGroups": 50, "routePanelAnalyticsTopics": 50,
"routePanelGroupsEdit": 51, "routePanelAnalyticsForums": 51,
"routePanelGroupsEditPerms": 52, "routePanelGroups": 52,
"routePanelGroupsEditSubmit": 53, "routePanelGroupsEdit": 53,
"routePanelGroupsEditPermsSubmit": 54, "routePanelGroupsEditPerms": 54,
"routePanelGroupsCreateSubmit": 55, "routePanelGroupsEditSubmit": 55,
"routePanelBackups": 56, "routePanelGroupsEditPermsSubmit": 56,
"routePanelLogsMod": 57, "routePanelGroupsCreateSubmit": 57,
"routePanelDebug": 58, "routePanelBackups": 58,
"routePanelDashboard": 59, "routePanelLogsMod": 59,
"routes.AccountEditCritical": 60, "routePanelDebug": 60,
"routeAccountEditCriticalSubmit": 61, "routePanelDashboard": 61,
"routeAccountEditAvatar": 62, "routes.AccountEditCritical": 62,
"routeAccountEditAvatarSubmit": 63, "routeAccountEditCriticalSubmit": 63,
"routeAccountEditUsername": 64, "routeAccountEditAvatar": 64,
"routeAccountEditUsernameSubmit": 65, "routeAccountEditAvatarSubmit": 65,
"routeAccountEditEmail": 66, "routeAccountEditUsername": 66,
"routeAccountEditEmailTokenSubmit": 67, "routeAccountEditUsernameSubmit": 67,
"routes.ViewProfile": 68, "routeAccountEditEmail": 68,
"routes.BanUserSubmit": 69, "routeAccountEditEmailTokenSubmit": 69,
"routes.UnbanUser": 70, "routes.ViewProfile": 70,
"routes.ActivateUser": 71, "routes.BanUserSubmit": 71,
"routes.IPSearch": 72, "routes.UnbanUser": 72,
"routes.CreateTopicSubmit": 73, "routes.ActivateUser": 73,
"routes.EditTopicSubmit": 74, "routes.IPSearch": 74,
"routes.DeleteTopicSubmit": 75, "routes.CreateTopicSubmit": 75,
"routes.StickTopicSubmit": 76, "routes.EditTopicSubmit": 76,
"routes.UnstickTopicSubmit": 77, "routes.DeleteTopicSubmit": 77,
"routes.LockTopicSubmit": 78, "routes.StickTopicSubmit": 78,
"routes.UnlockTopicSubmit": 79, "routes.UnstickTopicSubmit": 79,
"routes.MoveTopicSubmit": 80, "routes.LockTopicSubmit": 80,
"routeLikeTopicSubmit": 81, "routes.UnlockTopicSubmit": 81,
"routes.ViewTopic": 82, "routes.MoveTopicSubmit": 82,
"routeCreateReplySubmit": 83, "routeLikeTopicSubmit": 83,
"routes.ReplyEditSubmit": 84, "routes.ViewTopic": 84,
"routes.ReplyDeleteSubmit": 85, "routes.CreateReplySubmit": 85,
"routeReplyLikeSubmit": 86, "routes.ReplyEditSubmit": 86,
"routeProfileReplyCreateSubmit": 87, "routes.ReplyDeleteSubmit": 87,
"routes.ProfileReplyEditSubmit": 88, "routeReplyLikeSubmit": 88,
"routes.ProfileReplyDeleteSubmit": 89, "routeProfileReplyCreateSubmit": 89,
"routes.PollVote": 90, "routes.ProfileReplyEditSubmit": 90,
"routes.PollResults": 91, "routes.ProfileReplyDeleteSubmit": 91,
"routes.AccountLogin": 92, "routes.PollVote": 92,
"routes.AccountRegister": 93, "routes.PollResults": 93,
"routeLogout": 94, "routes.AccountLogin": 94,
"routes.AccountLoginSubmit": 95, "routes.AccountRegister": 95,
"routes.AccountRegisterSubmit": 96, "routeLogout": 96,
"routeDynamic": 97, "routes.AccountLoginSubmit": 97,
"routeUploads": 98, "routes.AccountRegisterSubmit": 98,
"routes.StaticFile": 99, "routeDynamic": 99,
"BadRoute": 100, "routeUploads": 100,
"routes.StaticFile": 101,
"BadRoute": 102,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routeAPI", 0: "routeAPI",
@ -232,7 +237,7 @@ var reverseRouteMapEnum = map[int]string{
4: "routes.ViewForum", 4: "routes.ViewForum",
5: "routeChangeTheme", 5: "routeChangeTheme",
6: "routeShowAttachment", 6: "routeShowAttachment",
7: "routeWebsockets", 7: "common.RouteWebsockets",
8: "routeReportSubmit", 8: "routeReportSubmit",
9: "routes.CreateTopic", 9: "routes.CreateTopic",
10: "routes.TopicList", 10: "routes.TopicList",
@ -266,66 +271,68 @@ var reverseRouteMapEnum = map[int]string{
38: "routePanelAnalyticsRoutes", 38: "routePanelAnalyticsRoutes",
39: "routePanelAnalyticsAgents", 39: "routePanelAnalyticsAgents",
40: "routePanelAnalyticsSystems", 40: "routePanelAnalyticsSystems",
41: "routePanelAnalyticsReferrers", 41: "routePanelAnalyticsLanguages",
42: "routePanelAnalyticsRouteViews", 42: "routePanelAnalyticsReferrers",
43: "routePanelAnalyticsAgentViews", 43: "routePanelAnalyticsRouteViews",
44: "routePanelAnalyticsForumViews", 44: "routePanelAnalyticsAgentViews",
45: "routePanelAnalyticsSystemViews", 45: "routePanelAnalyticsForumViews",
46: "routePanelAnalyticsReferrerViews", 46: "routePanelAnalyticsSystemViews",
47: "routePanelAnalyticsPosts", 47: "routePanelAnalyticsLanguageViews",
48: "routePanelAnalyticsTopics", 48: "routePanelAnalyticsReferrerViews",
49: "routePanelAnalyticsForums", 49: "routePanelAnalyticsPosts",
50: "routePanelGroups", 50: "routePanelAnalyticsTopics",
51: "routePanelGroupsEdit", 51: "routePanelAnalyticsForums",
52: "routePanelGroupsEditPerms", 52: "routePanelGroups",
53: "routePanelGroupsEditSubmit", 53: "routePanelGroupsEdit",
54: "routePanelGroupsEditPermsSubmit", 54: "routePanelGroupsEditPerms",
55: "routePanelGroupsCreateSubmit", 55: "routePanelGroupsEditSubmit",
56: "routePanelBackups", 56: "routePanelGroupsEditPermsSubmit",
57: "routePanelLogsMod", 57: "routePanelGroupsCreateSubmit",
58: "routePanelDebug", 58: "routePanelBackups",
59: "routePanelDashboard", 59: "routePanelLogsMod",
60: "routes.AccountEditCritical", 60: "routePanelDebug",
61: "routeAccountEditCriticalSubmit", 61: "routePanelDashboard",
62: "routeAccountEditAvatar", 62: "routes.AccountEditCritical",
63: "routeAccountEditAvatarSubmit", 63: "routeAccountEditCriticalSubmit",
64: "routeAccountEditUsername", 64: "routeAccountEditAvatar",
65: "routeAccountEditUsernameSubmit", 65: "routeAccountEditAvatarSubmit",
66: "routeAccountEditEmail", 66: "routeAccountEditUsername",
67: "routeAccountEditEmailTokenSubmit", 67: "routeAccountEditUsernameSubmit",
68: "routes.ViewProfile", 68: "routeAccountEditEmail",
69: "routes.BanUserSubmit", 69: "routeAccountEditEmailTokenSubmit",
70: "routes.UnbanUser", 70: "routes.ViewProfile",
71: "routes.ActivateUser", 71: "routes.BanUserSubmit",
72: "routes.IPSearch", 72: "routes.UnbanUser",
73: "routes.CreateTopicSubmit", 73: "routes.ActivateUser",
74: "routes.EditTopicSubmit", 74: "routes.IPSearch",
75: "routes.DeleteTopicSubmit", 75: "routes.CreateTopicSubmit",
76: "routes.StickTopicSubmit", 76: "routes.EditTopicSubmit",
77: "routes.UnstickTopicSubmit", 77: "routes.DeleteTopicSubmit",
78: "routes.LockTopicSubmit", 78: "routes.StickTopicSubmit",
79: "routes.UnlockTopicSubmit", 79: "routes.UnstickTopicSubmit",
80: "routes.MoveTopicSubmit", 80: "routes.LockTopicSubmit",
81: "routeLikeTopicSubmit", 81: "routes.UnlockTopicSubmit",
82: "routes.ViewTopic", 82: "routes.MoveTopicSubmit",
83: "routeCreateReplySubmit", 83: "routeLikeTopicSubmit",
84: "routes.ReplyEditSubmit", 84: "routes.ViewTopic",
85: "routes.ReplyDeleteSubmit", 85: "routes.CreateReplySubmit",
86: "routeReplyLikeSubmit", 86: "routes.ReplyEditSubmit",
87: "routeProfileReplyCreateSubmit", 87: "routes.ReplyDeleteSubmit",
88: "routes.ProfileReplyEditSubmit", 88: "routeReplyLikeSubmit",
89: "routes.ProfileReplyDeleteSubmit", 89: "routeProfileReplyCreateSubmit",
90: "routes.PollVote", 90: "routes.ProfileReplyEditSubmit",
91: "routes.PollResults", 91: "routes.ProfileReplyDeleteSubmit",
92: "routes.AccountLogin", 92: "routes.PollVote",
93: "routes.AccountRegister", 93: "routes.PollResults",
94: "routeLogout", 94: "routes.AccountLogin",
95: "routes.AccountLoginSubmit", 95: "routes.AccountRegister",
96: "routes.AccountRegisterSubmit", 96: "routeLogout",
97: "routeDynamic", 97: "routes.AccountLoginSubmit",
98: "routeUploads", 98: "routes.AccountRegisterSubmit",
99: "routes.StaticFile", 99: "routeDynamic",
100: "BadRoute", 100: "routeUploads",
101: "routes.StaticFile",
102: "BadRoute",
} }
var osMapEnum = map[string]int{ var osMapEnum = map[string]int{
"unknown": 0, "unknown": 0,
@ -366,12 +373,13 @@ var agentMapEnum = map[string]int{
"twitter": 19, "twitter": 19,
"cloudflare": 20, "cloudflare": 20,
"uptimebot": 21, "uptimebot": 21,
"discourse": 22, "slackbot": 22,
"lynx": 23, "discourse": 23,
"blank": 24, "lynx": 24,
"malformed": 25, "blank": 25,
"suspicious": 26, "malformed": 26,
"zgrab": 27, "suspicious": 27,
"zgrab": 28,
} }
var reverseAgentMapEnum = map[int]string{ var reverseAgentMapEnum = map[int]string{
0: "unknown", 0: "unknown",
@ -396,12 +404,13 @@ var reverseAgentMapEnum = map[int]string{
19: "twitter", 19: "twitter",
20: "cloudflare", 20: "cloudflare",
21: "uptimebot", 21: "uptimebot",
22: "discourse", 22: "slackbot",
23: "lynx", 23: "discourse",
24: "blank", 24: "lynx",
25: "malformed", 25: "blank",
26: "suspicious", 26: "malformed",
27: "zgrab", 27: "suspicious",
28: "zgrab",
} }
var markToAgent = map[string]string{ var markToAgent = map[string]string{
"OPR":"opera", "OPR":"opera",
@ -424,6 +433,7 @@ var markToAgent = map[string]string{
"SeznamBot":"seznambot", "SeznamBot":"seznambot",
"CloudFlare":"cloudflare", // Track alwayson specifically in case there are other bots? "CloudFlare":"cloudflare", // Track alwayson specifically in case there are other bots?
"Uptimebot":"uptimebot", "Uptimebot":"uptimebot",
"Slackbot":"slackbot",
"Discordbot":"discord", "Discordbot":"discord",
"Twitterbot":"twitter", "Twitterbot":"twitter",
"Discourse":"discourse", "Discourse":"discourse",
@ -497,7 +507,7 @@ func (router *GenRouter) StripNewlines(data string) string {
return strings.Replace(strings.Replace(data,"\n","",-1),"\r","",-1) return strings.Replace(strings.Replace(data,"\n","",-1),"\r","",-1)
} }
func (router *GenRouter) DumpRequest(req *http.Request) { func (router *GenRouter) DumpRequest(req *http.Request, prepend string) {
var heads string var heads string
for key, value := range req.Header { for key, value := range req.Header {
for _, vvalue := range value { for _, vvalue := range value {
@ -505,7 +515,8 @@ func (router *GenRouter) DumpRequest(req *http.Request) {
} }
} }
log.Print("\nUA: " + router.StripNewlines(req.UserAgent()) + "\n" + log.Print(prepend +
"\nUA: " + router.StripNewlines(req.UserAgent()) + "\n" +
"Method: " + router.StripNewlines(req.Method) + "\n" + heads + "Method: " + router.StripNewlines(req.Method) + "\n" + heads +
"req.Host: " + router.StripNewlines(req.Host) + "\n" + "req.Host: " + router.StripNewlines(req.Host) + "\n" +
"req.URL.Path: " + router.StripNewlines(req.URL.Path) + "\n" + "req.URL.Path: " + router.StripNewlines(req.URL.Path) + "\n" +
@ -515,9 +526,8 @@ func (router *GenRouter) DumpRequest(req *http.Request) {
} }
func (router *GenRouter) SuspiciousRequest(req *http.Request) { func (router *GenRouter) SuspiciousRequest(req *http.Request) {
log.Print("Suspicious Request") router.DumpRequest(req,"Suspicious Request")
router.DumpRequest(req) counters.AgentViewCounter.Bump(27)
counters.AgentViewCounter.Bump(26)
} }
// TODO: Pass the default route or config struct to the router rather than accessing it via a package global // TODO: Pass the default route or config struct to the router rather than accessing it via a package global
@ -543,24 +553,23 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host {
w.WriteHeader(200) // 400 w.WriteHeader(200) // 400
w.Write([]byte("")) w.Write([]byte(""))
log.Print("Malformed Request") router.DumpRequest(req,"Malformed Request")
router.DumpRequest(req) counters.AgentViewCounter.Bump(26)
counters.AgentViewCounter.Bump(25)
return return
} }
// TODO: Cover more suspicious strings and at a lower layer than this // TODO: Cover more suspicious strings and at a lower layer than this
for _, char := range req.URL.Path { for _, char := range req.URL.Path {
if char != '&' && !(char > 44 && char < 58) && char != '=' && char != '?' && !(char > 64 && char < 91) && char != '\\' && char != '_' && !(char > 96 && char < 123) { if char != '&' && !(char > 44 && char < 58) && char != '=' && char != '?' && !(char > 64 && char < 91) && char != '\\' && char != '_' && !(char > 96 && char < 123) {
router.SuspiciousRequest(req)
break
}
}
lowerPath := strings.ToLower(req.URL.Path)
// TODO: Flag any requests which has a dot with anything but a number after that
if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") || strings.Contains(lowerPath,".php") || strings.Contains(lowerPath,".asp") || strings.Contains(lowerPath,".cgi") || strings.Contains(lowerPath,".py") || strings.Contains(lowerPath,".sql") {
router.SuspiciousRequest(req) router.SuspiciousRequest(req)
break
} }
}
lowerPath := strings.ToLower(req.URL.Path)
// TODO: Flag any requests which has a dot with anything but a number after that
if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") || strings.Contains(lowerPath,".php") || strings.Contains(lowerPath,".asp") || strings.Contains(lowerPath,".cgi") || strings.Contains(lowerPath,".py") || strings.Contains(lowerPath,".sql") || strings.Contains(lowerPath,".action") {
router.SuspiciousRequest(req)
}
var prefix, extraData string var prefix, extraData string
prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1] prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1]
@ -570,14 +579,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
if common.Dev.SuperDebug { if common.Dev.SuperDebug {
log.Print("before routes.StaticFile") router.DumpRequest(req,"before routes.StaticFile")
router.DumpRequest(req)
} }
// Increment the request counter // Increment the request counter
counters.GlobalViewCounter.Bump() counters.GlobalViewCounter.Bump()
if prefix == "/static" { if prefix == "/static" {
counters.RouteViewCounter.Bump(99) counters.RouteViewCounter.Bump(101)
req.URL.Path += extraData req.URL.Path += extraData
routes.StaticFile(w, req) routes.StaticFile(w, req)
return return
@ -591,10 +599,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// TODO: Use a more efficient detector instead of smashing every possible combination in // TODO: Use a more efficient detector instead of smashing every possible combination in
ua := strings.TrimSpace(strings.Replace(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36","",-1)) // Noise, no one's going to be running this and it would require some sort of agent ranking system to determine which identifier should be prioritised over another ua := strings.TrimSpace(strings.Replace(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36","",-1)) // Noise, no one's going to be running this and it would require some sort of agent ranking system to determine which identifier should be prioritised over another
if ua == "" { if ua == "" {
counters.AgentViewCounter.Bump(24) counters.AgentViewCounter.Bump(25)
if common.Dev.DebugMode { if common.Dev.DebugMode {
log.Print("Blank UA: ", req.UserAgent()) var prepend string
router.DumpRequest(req) for _, char := range req.UserAgent() {
prepend += strconv.Itoa(int(char)) + " "
}
router.DumpRequest(req,"Blank UA: " + prepend)
} }
} else { } else {
var runeEquals = func(a []rune, b []rune) bool { var runeEquals = func(a []rune, b []rune) bool {
@ -693,8 +704,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if agent == "" { if agent == "" {
counters.AgentViewCounter.Bump(0) counters.AgentViewCounter.Bump(0)
if common.Dev.DebugMode { if common.Dev.DebugMode {
log.Print("Unknown UA: ", req.UserAgent()) var prepend string
router.DumpRequest(req) for _, char := range req.UserAgent() {
prepend += strconv.Itoa(int(char)) + " "
}
router.DumpRequest(req,"Blank UA: " + prepend)
} }
} else { } else {
counters.AgentViewCounter.Bump(agentMapEnum[agent]) counters.AgentViewCounter.Bump(agentMapEnum[agent])
@ -702,6 +716,15 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.OSViewCounter.Bump(osMapEnum[os]) counters.OSViewCounter.Bump(osMapEnum[os])
} }
// TODO: Do we want to track missing language headers too? Maybe as it's own type, e.g. "noheader"?
lang := req.Header.Get("Accept-Language")
if lang != "" {
lang = strings.TrimSpace(lang)
lLang := strings.Split(lang,"-")
common.DebugDetail("lLang:", lLang)
counters.LangViewCounter.Bump(lLang[0])
}
referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P
if referrer != "" { if referrer != "" {
// ? Optimise this a little? // ? Optimise this a little?
@ -782,7 +805,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/ws": case "/ws":
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(7) counters.RouteViewCounter.Bump(7)
err = routeWebsockets(w,req,user) err = common.RouteWebsockets(w,req,user)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
@ -1043,7 +1066,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.RouteViewCounter.Bump(40) counters.RouteViewCounter.Bump(40)
err = routePanelAnalyticsSystems(w,req,user) err = routePanelAnalyticsSystems(w,req,user)
case "/panel/analytics/referrers/": case "/panel/analytics/langs/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
@ -1051,21 +1074,33 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(41) counters.RouteViewCounter.Bump(41)
err = routePanelAnalyticsLanguages(w,req,user)
case "/panel/analytics/referrers/":
err = common.ParseForm(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
counters.RouteViewCounter.Bump(42)
err = routePanelAnalyticsReferrers(w,req,user) err = routePanelAnalyticsReferrers(w,req,user)
case "/panel/analytics/route/": case "/panel/analytics/route/":
counters.RouteViewCounter.Bump(42) counters.RouteViewCounter.Bump(43)
err = routePanelAnalyticsRouteViews(w,req,user,extraData) err = routePanelAnalyticsRouteViews(w,req,user,extraData)
case "/panel/analytics/agent/": case "/panel/analytics/agent/":
counters.RouteViewCounter.Bump(43) counters.RouteViewCounter.Bump(44)
err = routePanelAnalyticsAgentViews(w,req,user,extraData) err = routePanelAnalyticsAgentViews(w,req,user,extraData)
case "/panel/analytics/forum/": case "/panel/analytics/forum/":
counters.RouteViewCounter.Bump(44) counters.RouteViewCounter.Bump(45)
err = routePanelAnalyticsForumViews(w,req,user,extraData) err = routePanelAnalyticsForumViews(w,req,user,extraData)
case "/panel/analytics/system/": case "/panel/analytics/system/":
counters.RouteViewCounter.Bump(45)
err = routePanelAnalyticsSystemViews(w,req,user,extraData)
case "/panel/analytics/referrer/":
counters.RouteViewCounter.Bump(46) counters.RouteViewCounter.Bump(46)
err = routePanelAnalyticsSystemViews(w,req,user,extraData)
case "/panel/analytics/lang/":
counters.RouteViewCounter.Bump(47)
err = routePanelAnalyticsLanguageViews(w,req,user,extraData)
case "/panel/analytics/referrer/":
counters.RouteViewCounter.Bump(48)
err = routePanelAnalyticsReferrerViews(w,req,user,extraData) err = routePanelAnalyticsReferrerViews(w,req,user,extraData)
case "/panel/analytics/posts/": case "/panel/analytics/posts/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1074,7 +1109,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(47) counters.RouteViewCounter.Bump(49)
err = routePanelAnalyticsPosts(w,req,user) err = routePanelAnalyticsPosts(w,req,user)
case "/panel/analytics/topics/": case "/panel/analytics/topics/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1083,7 +1118,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(48) counters.RouteViewCounter.Bump(50)
err = routePanelAnalyticsTopics(w,req,user) err = routePanelAnalyticsTopics(w,req,user)
case "/panel/analytics/forums/": case "/panel/analytics/forums/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1092,16 +1127,16 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(49) counters.RouteViewCounter.Bump(51)
err = routePanelAnalyticsForums(w,req,user) err = routePanelAnalyticsForums(w,req,user)
case "/panel/groups/": case "/panel/groups/":
counters.RouteViewCounter.Bump(50) counters.RouteViewCounter.Bump(52)
err = routePanelGroups(w,req,user) err = routePanelGroups(w,req,user)
case "/panel/groups/edit/": case "/panel/groups/edit/":
counters.RouteViewCounter.Bump(51) counters.RouteViewCounter.Bump(53)
err = routePanelGroupsEdit(w,req,user,extraData) err = routePanelGroupsEdit(w,req,user,extraData)
case "/panel/groups/edit/perms/": case "/panel/groups/edit/perms/":
counters.RouteViewCounter.Bump(52) counters.RouteViewCounter.Bump(54)
err = routePanelGroupsEditPerms(w,req,user,extraData) err = routePanelGroupsEditPerms(w,req,user,extraData)
case "/panel/groups/edit/submit/": case "/panel/groups/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1110,7 +1145,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(53) counters.RouteViewCounter.Bump(55)
err = routePanelGroupsEditSubmit(w,req,user,extraData) err = routePanelGroupsEditSubmit(w,req,user,extraData)
case "/panel/groups/edit/perms/submit/": case "/panel/groups/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1119,7 +1154,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(54) counters.RouteViewCounter.Bump(56)
err = routePanelGroupsEditPermsSubmit(w,req,user,extraData) err = routePanelGroupsEditPermsSubmit(w,req,user,extraData)
case "/panel/groups/create/": case "/panel/groups/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1128,7 +1163,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(55) counters.RouteViewCounter.Bump(57)
err = routePanelGroupsCreateSubmit(w,req,user) err = routePanelGroupsCreateSubmit(w,req,user)
case "/panel/backups/": case "/panel/backups/":
err = common.SuperAdminOnly(w,req,user) err = common.SuperAdminOnly(w,req,user)
@ -1137,10 +1172,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(56) counters.RouteViewCounter.Bump(58)
err = routePanelBackups(w,req,user,extraData) err = routePanelBackups(w,req,user,extraData)
case "/panel/logs/mod/": case "/panel/logs/mod/":
counters.RouteViewCounter.Bump(57) counters.RouteViewCounter.Bump(59)
err = routePanelLogsMod(w,req,user) err = routePanelLogsMod(w,req,user)
case "/panel/debug/": case "/panel/debug/":
err = common.AdminOnly(w,req,user) err = common.AdminOnly(w,req,user)
@ -1149,10 +1184,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(58) counters.RouteViewCounter.Bump(60)
err = routePanelDebug(w,req,user) err = routePanelDebug(w,req,user)
default: default:
counters.RouteViewCounter.Bump(59) counters.RouteViewCounter.Bump(61)
err = routePanelDashboard(w,req,user) err = routePanelDashboard(w,req,user)
} }
if err != nil { if err != nil {
@ -1167,7 +1202,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(60) counters.RouteViewCounter.Bump(62)
err = routes.AccountEditCritical(w,req,user) err = routes.AccountEditCritical(w,req,user)
case "/user/edit/critical/submit/": case "/user/edit/critical/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1182,7 +1217,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(61) counters.RouteViewCounter.Bump(63)
err = routeAccountEditCriticalSubmit(w,req,user) err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/": case "/user/edit/avatar/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1191,7 +1226,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(62) counters.RouteViewCounter.Bump(64)
err = routeAccountEditAvatar(w,req,user) err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/": case "/user/edit/avatar/submit/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1211,7 +1246,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(63) counters.RouteViewCounter.Bump(65)
err = routeAccountEditAvatarSubmit(w,req,user) err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/": case "/user/edit/username/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1220,7 +1255,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(64) counters.RouteViewCounter.Bump(66)
err = routeAccountEditUsername(w,req,user) err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/": case "/user/edit/username/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1235,7 +1270,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(65) counters.RouteViewCounter.Bump(67)
err = routeAccountEditUsernameSubmit(w,req,user) err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/": case "/user/edit/email/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1244,7 +1279,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(66) counters.RouteViewCounter.Bump(68)
err = routeAccountEditEmail(w,req,user) err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/": case "/user/edit/token/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1259,11 +1294,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(67) counters.RouteViewCounter.Bump(69)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData) err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default: default:
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(68) counters.RouteViewCounter.Bump(70)
err = routes.ViewProfile(w,req,user) err = routes.ViewProfile(w,req,user)
} }
if err != nil { if err != nil {
@ -1284,7 +1319,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(69) counters.RouteViewCounter.Bump(71)
err = routes.BanUserSubmit(w,req,user,extraData) err = routes.BanUserSubmit(w,req,user,extraData)
case "/users/unban/": case "/users/unban/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1299,7 +1334,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(70) counters.RouteViewCounter.Bump(72)
err = routes.UnbanUser(w,req,user,extraData) err = routes.UnbanUser(w,req,user,extraData)
case "/users/activate/": case "/users/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1314,7 +1349,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(71) counters.RouteViewCounter.Bump(73)
err = routes.ActivateUser(w,req,user,extraData) err = routes.ActivateUser(w,req,user,extraData)
case "/users/ips/": case "/users/ips/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -1323,7 +1358,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(72) counters.RouteViewCounter.Bump(74)
err = routes.IPSearch(w,req,user) err = routes.IPSearch(w,req,user)
} }
if err != nil { if err != nil {
@ -1349,7 +1384,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(73) counters.RouteViewCounter.Bump(75)
err = routes.CreateTopicSubmit(w,req,user) err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/": case "/topic/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1364,7 +1399,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(74) counters.RouteViewCounter.Bump(76)
err = routes.EditTopicSubmit(w,req,user,extraData) err = routes.EditTopicSubmit(w,req,user,extraData)
case "/topic/delete/submit/": case "/topic/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1380,7 +1415,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(75) counters.RouteViewCounter.Bump(77)
err = routes.DeleteTopicSubmit(w,req,user) err = routes.DeleteTopicSubmit(w,req,user)
case "/topic/stick/submit/": case "/topic/stick/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1395,7 +1430,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(76) counters.RouteViewCounter.Bump(78)
err = routes.StickTopicSubmit(w,req,user,extraData) err = routes.StickTopicSubmit(w,req,user,extraData)
case "/topic/unstick/submit/": case "/topic/unstick/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1410,7 +1445,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(77) counters.RouteViewCounter.Bump(79)
err = routes.UnstickTopicSubmit(w,req,user,extraData) err = routes.UnstickTopicSubmit(w,req,user,extraData)
case "/topic/lock/submit/": case "/topic/lock/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1426,7 +1461,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(78) counters.RouteViewCounter.Bump(80)
err = routes.LockTopicSubmit(w,req,user) err = routes.LockTopicSubmit(w,req,user)
case "/topic/unlock/submit/": case "/topic/unlock/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1441,7 +1476,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(79) counters.RouteViewCounter.Bump(81)
err = routes.UnlockTopicSubmit(w,req,user,extraData) err = routes.UnlockTopicSubmit(w,req,user,extraData)
case "/topic/move/submit/": case "/topic/move/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1456,7 +1491,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(80) counters.RouteViewCounter.Bump(82)
err = routes.MoveTopicSubmit(w,req,user,extraData) err = routes.MoveTopicSubmit(w,req,user,extraData)
case "/topic/like/submit/": case "/topic/like/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1471,10 +1506,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(81) counters.RouteViewCounter.Bump(83)
err = routeLikeTopicSubmit(w,req,user,extraData) err = routeLikeTopicSubmit(w,req,user,extraData)
default: default:
counters.RouteViewCounter.Bump(82) counters.RouteViewCounter.Bump(84)
err = routes.ViewTopic(w,req,user, extraData) err = routes.ViewTopic(w,req,user, extraData)
} }
if err != nil { if err != nil {
@ -1500,8 +1535,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(83) counters.RouteViewCounter.Bump(85)
err = routeCreateReplySubmit(w,req,user) err = routes.CreateReplySubmit(w,req,user)
case "/reply/edit/submit/": case "/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1515,7 +1550,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(84) counters.RouteViewCounter.Bump(86)
err = routes.ReplyEditSubmit(w,req,user,extraData) err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/": case "/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1530,7 +1565,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(85) counters.RouteViewCounter.Bump(87)
err = routes.ReplyDeleteSubmit(w,req,user,extraData) err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/": case "/reply/like/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1545,7 +1580,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(86) counters.RouteViewCounter.Bump(88)
err = routeReplyLikeSubmit(w,req,user,extraData) err = routeReplyLikeSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1566,7 +1601,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(87) counters.RouteViewCounter.Bump(89)
err = routeProfileReplyCreateSubmit(w,req,user) err = routeProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/": case "/profile/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1581,7 +1616,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(88) counters.RouteViewCounter.Bump(90)
err = routes.ProfileReplyEditSubmit(w,req,user,extraData) err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/": case "/profile/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1596,7 +1631,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(89) counters.RouteViewCounter.Bump(91)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1617,10 +1652,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(90) counters.RouteViewCounter.Bump(92)
err = routes.PollVote(w,req,user,extraData) err = routes.PollVote(w,req,user,extraData)
case "/poll/results/": case "/poll/results/":
counters.RouteViewCounter.Bump(91) counters.RouteViewCounter.Bump(93)
err = routes.PollResults(w,req,user,extraData) err = routes.PollResults(w,req,user,extraData)
} }
if err != nil { if err != nil {
@ -1629,10 +1664,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/accounts": case "/accounts":
switch(req.URL.Path) { switch(req.URL.Path) {
case "/accounts/login/": case "/accounts/login/":
counters.RouteViewCounter.Bump(92) counters.RouteViewCounter.Bump(94)
err = routes.AccountLogin(w,req,user) err = routes.AccountLogin(w,req,user)
case "/accounts/create/": case "/accounts/create/":
counters.RouteViewCounter.Bump(93) counters.RouteViewCounter.Bump(95)
err = routes.AccountRegister(w,req,user) err = routes.AccountRegister(w,req,user)
case "/accounts/logout/": case "/accounts/logout/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -1647,7 +1682,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(94) counters.RouteViewCounter.Bump(96)
err = routeLogout(w,req,user) err = routeLogout(w,req,user)
case "/accounts/login/submit/": case "/accounts/login/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1656,7 +1691,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(95) counters.RouteViewCounter.Bump(97)
err = routes.AccountLoginSubmit(w,req,user) err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/create/submit/": case "/accounts/create/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
@ -1665,7 +1700,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
counters.RouteViewCounter.Bump(96) counters.RouteViewCounter.Bump(98)
err = routes.AccountRegisterSubmit(w,req,user) err = routes.AccountRegisterSubmit(w,req,user)
} }
if err != nil { if err != nil {
@ -1682,7 +1717,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
return return
} }
counters.RouteViewCounter.Bump(98) counters.RouteViewCounter.Bump(100)
req.URL.Path += extraData req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this? // TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) // TODO: Count these views router.UploadHandler(w,req) // TODO: Count these views
@ -1725,7 +1760,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock() router.RUnlock()
if ok { if ok {
counters.RouteViewCounter.Bump(97) // TODO: Be more specific about *which* dynamic route it is counters.RouteViewCounter.Bump(99) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData req.URL.Path += extraData
err = handle(w,req,user) err = handle(w,req,user)
if err != nil { if err != nil {
@ -1739,7 +1774,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if strings.Contains(lowerPath,"admin") || strings.Contains(lowerPath,"sql") || strings.Contains(lowerPath,"manage") || strings.Contains(lowerPath,"//") || strings.Contains(lowerPath,"\\\\") || strings.Contains(lowerPath,"wp") || strings.Contains(lowerPath,"wordpress") || strings.Contains(lowerPath,"config") || strings.Contains(lowerPath,"setup") || strings.Contains(lowerPath,"install") || strings.Contains(lowerPath,"update") || strings.Contains(lowerPath,"php") { if strings.Contains(lowerPath,"admin") || strings.Contains(lowerPath,"sql") || strings.Contains(lowerPath,"manage") || strings.Contains(lowerPath,"//") || strings.Contains(lowerPath,"\\\\") || strings.Contains(lowerPath,"wp") || strings.Contains(lowerPath,"wordpress") || strings.Contains(lowerPath,"config") || strings.Contains(lowerPath,"setup") || strings.Contains(lowerPath,"install") || strings.Contains(lowerPath,"update") || strings.Contains(lowerPath,"php") {
router.SuspiciousRequest(req) router.SuspiciousRequest(req)
} }
counters.RouteViewCounter.Bump(100) counters.RouteViewCounter.Bump(102)
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
} }
} }

View File

@ -1,9 +1,11 @@
{ {
"Name": "english", "Name": "english",
"Levels": { "Levels": {
"Level": "Level {0}", "Level": "Level {0}",
"LevelMax": "" "LevelMax": ""
}, },
"GlobalPerms": { "GlobalPerms": {
"BanUsers": "Can ban users", "BanUsers": "Can ban users",
"ActivateUsers": "Can activate users", "ActivateUsers": "Can activate users",
@ -27,6 +29,7 @@
"UploadFiles": "Can upload files" "UploadFiles": "Can upload files"
}, },
"LocalPerms": { "LocalPerms": {
"ViewTopic": "Can view topics", "ViewTopic": "Can view topics",
"LikeItem": "Can like items", "LikeItem": "Can like items",
@ -40,9 +43,11 @@
"CloseTopic": "Can lock topics", "CloseTopic": "Can lock topics",
"MoveTopic": "Can move topics in or out" "MoveTopic": "Can move topics in or out"
}, },
"SettingLabels": { "SettingLabels": {
"activation_type": "Activate All,Email Activation,Admin Approval" "activation_type": "Activate All,Email Activation,Admin Approval"
}, },
"PermPresets": { "PermPresets": {
"all":"Public", "all":"Public",
"announce":"Announcements", "announce":"Announcements",
@ -53,14 +58,17 @@
"custom":"Custom", "custom":"Custom",
"unknown":"Unknown" "unknown":"Unknown"
}, },
"Accounts": { "Accounts": {
"VerifyEmailSubject": "Validate Your Email @ {{name}}", "VerifyEmailSubject": "Validate Your Email @ {{name}}",
"VerifyEmailBody": "Dear {{username}}, following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. {{schema}}://{{url}}/user/edit/token/{{token}}\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused." "VerifyEmailBody": "Dear {{username}}, following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. {{schema}}://{{url}}/user/edit/token/{{token}}\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
}, },
"Errors": { "Errors": {
"NoPerms": { "NoPerms": {
} }
}, },
"PageTitles": { "PageTitles": {
"overview":"Overview", "overview":"Overview",
"page":"Page", "page":"Page",
@ -68,7 +76,7 @@
"forums":"Forum List", "forums":"Forum List",
"login":"Login", "login":"Login",
"register":"Registration", "register":"Registration",
"ip-search":"IP Search", "ip_search":"IP Search",
"panel_dashboard":"Control Panel Dashboard", "panel_dashboard":"Control Panel Dashboard",
"panel_forums":"Forum Manager", "panel_forums":"Forum Manager",
@ -114,6 +122,7 @@
"twitter":"Twitterbot", "twitter":"Twitterbot",
"cloudflare":"Cloudflare Alwayson", "cloudflare":"Cloudflare Alwayson",
"uptimebot":"Uptimebot", "uptimebot":"Uptimebot",
"slackbot":"Slackbot",
"discourse":"Discourse Forum Onebox", "discourse":"Discourse Forum Onebox",
"lynx":"Lynx", "lynx":"Lynx",
@ -123,6 +132,7 @@
"blank":"Blank", "blank":"Blank",
"malformed":"Malformed" "malformed":"Malformed"
}, },
"OperatingSystems": { "OperatingSystems": {
"windows": "Microsoft Windows", "windows": "Microsoft Windows",
"linux":"Linux", "linux":"Linux",
@ -130,5 +140,110 @@
"android": "Android", "android": "Android",
"iphone":"iPhone", "iphone":"iPhone",
"unknown":"Unknown" "unknown":"Unknown"
},
"HumanLanguages": {
"af":"Afrikaans",
"ar":"Arabic",
"az":"Azeri (Latin)",
"be":"Belarusian",
"bg":"Bulgarian",
"bs":"Bosnian (Bosnia and Herzegovina)",
"ca":"Catalan",
"cs":"Czech",
"cy":"Welsh",
"da":"Danish",
"de":"German",
"dv":"Divehi",
"el":"Greek",
"en":"English",
"eo":"Esperanto",
"es":"Spanish",
"et":"Estonian",
"eu":"Basque",
"fa":"Farsi",
"fi":"Finnish",
"fo":"Faroese",
"fr":"French",
"gl":"Galician",
"gu":"Gujarati",
"he":"Hebrew",
"hi":"Hindi",
"hr":"Croatian",
"hu":"Hungarian",
"hy":"Armenian",
"id":"Indonesian",
"is":"Icelandic",
"it":"Italian",
"ja":"Japanese",
"ka":"Georgian",
"kk":"Kazakh",
"kn":"Kannada",
"ko":"Korean",
"kok":"Konkani",
"ky":"Kyrgyz",
"lt":"Lithuanian",
"lv":"Latvian",
"mi":"Maori",
"mk":"FYRO Macedonian",
"mn":"Mongolian",
"mr":"Marathi",
"ms":"Malay",
"mt":"Maltese",
"nb":"Norwegian (Bokm?l)",
"nl":"Dutch",
"nn":"Norwegian (Nynorsk) (Norway)",
"ns":"Northern Sotho",
"pa":"Punjabi",
"pl":"Polish",
"ps":"Pashto",
"pt":"Portuguese",
"qu":"Quechua",
"ro":"Romanian",
"ru":"Russian",
"sa":"Sanskrit",
"se":"Sami (Northern)",
"sk":"Slovak",
"sl":"Slovenian",
"sq":"Albanian",
"sr":"Serbian (Latin)",
"sv":"Swedish",
"sw":"Swahili",
"syr":"Syriac",
"ta":"Tamil",
"te":"Telugu",
"th":"Thai",
"tl":"Tagalog",
"tn":"Tswana",
"tr":"Turkish",
"tt":"Tatar",
"ts":"Tsonga",
"uk":"Ukrainian",
"ur":"Urdu",
"uz":"Uzbek (Latin)",
"vi":"Vietnamese",
"xh":"Xhosa",
"zh":"Chinese",
"zu":"Zulu"
},
"TmplPhrases": {
"login_head":"Login",
"login_account_name":"Account Name",
"login_account_password":"Password",
"login_submit_button":"Login",
"login_no_account":"Don't have an account?",
"register_head":"Create Account",
"register_account_name":"Account Name",
"register_account_email":"Email",
"register_account_password":"Password",
"register_account_confirm_password":"Confirm Password",
"register_submit_button":"Create Account",
"ip_search_head":"IP Search",
"ip_search_no_users":"No users found.",
"error_head":"An error has occured"
} }
} }

View File

@ -114,6 +114,10 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
counters.LangViewCounter, err = counters.NewDefaultLangViewCounter()
if err != nil {
return err
}
counters.RouteViewCounter, err = counters.NewDefaultRouteViewCounter() counters.RouteViewCounter, err = counters.NewDefaultRouteViewCounter()
if err != nil { if err != nil {
return err return err
@ -290,9 +294,9 @@ func main() {
// Run this goroutine once every half second // Run this goroutine once every half second
halfSecondTicker := time.NewTicker(time.Second / 2) halfSecondTicker := time.NewTicker(time.Second / 2)
secondTicker := time.NewTicker(1 * time.Second) secondTicker := time.NewTicker(time.Second)
fifteenMinuteTicker := time.NewTicker(15 * time.Minute) fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
//hourTicker := time.NewTicker(1 * time.Hour) //hourTicker := time.NewTicker(time.Hour)
go func() { go func() {
var runHook = func(name string) { var runHook = func(name string) {
err := common.RunTaskHook(name) err := common.RunTaskHook(name)

View File

@ -1,11 +1,8 @@
package main package main
import ( import (
"crypto/sha256"
"encoding/hex"
"html" "html"
"io" "io"
"log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -17,188 +14,6 @@ import (
"./common/counters" "./common/counters"
) )
func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
tid, err := strconv.Atoi(r.PostFormValue("tid"))
if err != nil {
return common.PreError("Failed to convert the Topic ID", w, r)
}
topic, err := common.Topics.Get(tid)
if err == ErrNoRows {
return common.PreError("Couldn't find the parent topic", w, r)
} else if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateReply {
return common.NoPermissions(w, r, user)
}
// Handle the file attachments
// TODO: Stop duplicating this code
if user.Perms.UploadFiles {
files, ok := r.MultipartForm.File["upload_files"]
if ok {
if len(files) > 5 {
return common.LocalError("You can't attach more than five files", w, r, user)
}
for _, file := range files {
log.Print("file.Filename ", file.Filename)
extarr := strings.Split(file.Filename, ".")
if len(extarr) < 2 {
return common.LocalError("Bad file", w, r, user)
}
ext := extarr[len(extarr)-1]
// TODO: Can we do this without a regex?
reg, err := regexp.Compile("[^A-Za-z0-9]+")
if err != nil {
return common.LocalError("Bad file extension", w, r, user)
}
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
if !common.AllowedFileExts.Contains(ext) {
return common.LocalError("You're not allowed to upload files with this extension", w, r, user)
}
infile, err := file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, infile)
if err != nil {
return common.LocalError("Upload failed [Hashing Failed]", w, r, user)
}
infile.Close()
checksum := hex.EncodeToString(hasher.Sum(nil))
filename := checksum + "." + ext
outfile, err := os.Create("." + "/attachs/" + filename)
if err != nil {
return common.LocalError("Upload failed [File Creation Failed]", w, r, user)
}
defer outfile.Close()
infile, err = file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
_, err = io.Copy(outfile, infile)
if err != nil {
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
}
err = common.Attachments.Add(topic.ParentID, "forums", tid, "replies", user.ID, filename)
if err != nil {
return common.InternalError(err, w, r)
}
}
}
}
content := common.PreparseMessage(r.PostFormValue("reply-content"))
// TODO: Fully parse the post and put that in the parsed column
rid, err := common.Rstore.Create(topic, content, user.LastIP, user.ID)
if err != nil {
return common.InternalError(err, w, r)
}
reply, err := common.Rstore.Get(rid)
if err != nil {
return common.LocalError("Unable to load the reply", w, r, user)
}
if r.PostFormValue("has_poll") == "1" {
var maxPollOptions = 10
var pollInputItems = make(map[int]string)
for key, values := range r.Form {
//if common.Dev.SuperDebug {
log.Print("key: ", key)
log.Printf("values: %+v\n", values)
//}
for _, value := range values {
if strings.HasPrefix(key, "pollinputitem[") {
halves := strings.Split(key, "[")
if len(halves) != 2 {
return common.LocalError("Malformed pollinputitem", w, r, user)
}
halves[1] = strings.TrimSuffix(halves[1], "]")
index, err := strconv.Atoi(halves[1])
if err != nil {
return common.LocalError("Malformed pollinputitem", w, r, user)
}
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
_, exists := pollInputItems[index]
if !exists && len(html.EscapeString(value)) != 0 {
pollInputItems[index] = html.EscapeString(value)
if len(pollInputItems) >= maxPollOptions {
break
}
}
}
}
}
// Make sure the indices are sequential to avoid out of bounds issues
var seqPollInputItems = make(map[int]string)
for i := 0; i < len(pollInputItems); i++ {
seqPollInputItems[i] = pollInputItems[i]
}
pollType := 0 // Basic single choice
_, err := common.Polls.Create(reply, pollType, seqPollInputItems)
if err != nil {
return common.LocalError("Failed to add poll to reply", w, r, user) // TODO: Might need to be an internal error as it could leave phantom polls?
}
}
err = common.Forums.UpdateLastTopic(tid, user.ID, topic.ParentID)
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
res, err := stmts.addActivity.Exec(user.ID, topic.CreatedBy, "reply", "topic", tid)
if err != nil {
return common.InternalError(err, w, r)
}
lastID, err := res.LastInsertId()
if err != nil {
return common.InternalError(err, w, r)
}
_, err = stmts.notifyWatchers.Exec(lastID)
if err != nil {
return common.InternalError(err, w, r)
}
// Alert the subscribers about this post without blocking this post from being posted
if enableWebsockets {
go notifyWatchers(lastID)
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
wcount := common.WordCount(content)
err = user.IncreasePostStats(wcount, false)
if err != nil {
return common.InternalError(err, w, r)
}
counters.PostCounter.Bump()
return nil
}
// TODO: Refactor this // TODO: Refactor this
func routeLikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError { func routeLikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
tid, err := strconv.Atoi(stid) tid, err := strconv.Atoi(stid)
@ -240,22 +55,10 @@ func routeLikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.Us
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
res, err := stmts.addActivity.Exec(user.ID, topic.CreatedBy, "like", "topic", tid) err = common.AddActivityAndNotifyTarget(user.ID, topic.CreatedBy, "like", "topic", tid)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
lastID, err := res.LastInsertId()
if err != nil {
return common.InternalError(err, w, r)
}
_, err = stmts.notifyOne.Exec(topic.CreatedBy, lastID)
if err != nil {
return common.InternalError(err, w, r)
}
// Live alerts, if the poster is online and WebSockets is enabled
_ = wsHub.pushAlert(topic.CreatedBy, int(lastID), "like", "topic", user.ID, topic.CreatedBy, tid)
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
return nil return nil
@ -308,22 +111,10 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.Us
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
res, err := stmts.addActivity.Exec(user.ID, reply.CreatedBy, "like", "post", rid) err = common.AddActivityAndNotifyTarget(user.ID, reply.CreatedBy, "like", "post", rid)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
lastID, err := res.LastInsertId()
if err != nil {
return common.InternalError(err, w, r)
}
_, err = stmts.notifyOne.Exec(reply.CreatedBy, lastID)
if err != nil {
return common.InternalError(err, w, r)
}
// Live alerts, if the poster is online and WebSockets is enabled
_ = wsHub.pushAlert(reply.CreatedBy, int(lastID), "like", "post", user.ID, reply.CreatedBy, rid)
http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther)
return nil return nil
@ -338,13 +129,22 @@ func routeProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user
if err != nil { if err != nil {
return common.LocalError("Invalid UID", w, r, user) return common.LocalError("Invalid UID", w, r, user)
} }
if !common.Users.Exists(uid) {
profileOwner, err := common.Users.Get(uid)
if err == ErrNoRows {
return common.LocalError("The profile you're trying to post on doesn't exist.", w, r, user) return common.LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
} else if err != nil {
return common.InternalError(err,w,r)
} }
content := common.PreparseMessage(r.PostFormValue("reply-content")) content := common.PreparseMessage(r.PostFormValue("reply-content"))
// TODO: Fully parse the post and store it in the parsed column // TODO: Fully parse the post and store it in the parsed column
_, err = common.Prstore.Create(uid, content, user.ID, user.LastIP) _, err = common.Prstore.Create(profileOwner.ID, content, user.ID, user.LastIP)
if err != nil {
return common.InternalError(err, w, r)
}
err = common.AddActivityAndNotifyTarget(user.ID, profileOwner.ID, "reply", "user", profileOwner.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

View File

@ -132,9 +132,9 @@ func routePanelDashboard(w http.ResponseWriter, r *http.Request, user common.Use
common.GridElement{"dash-ram", "RAM: " + ramstr, 2, "grid_istat " + ramColour, "", "", "The global RAM usage of this server"}, common.GridElement{"dash-ram", "RAM: " + ramstr, 2, "grid_istat " + ramColour, "", "", "The global RAM usage of this server"},
} }
if enableWebsockets { if common.EnableWebsockets {
uonline := wsHub.userCount() uonline := common.WsHub.UserCount()
gonline := wsHub.guestCount() gonline := common.WsHub.GuestCount()
totonline := uonline + gonline totonline := uonline + gonline
reqCount := 0 reqCount := 0
@ -743,6 +743,9 @@ func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange) revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
// ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff
agent = html.EscapeString(agent)
common.DebugLog("in routePanelAnalyticsAgentViews") common.DebugLog("in routePanelAnalyticsAgentViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // TODO: Verify the agent is valid
@ -763,8 +766,6 @@ func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList} graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
common.DebugLogf("graph: %+v\n", graph) common.DebugLogf("graph: %+v\n", graph)
// ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff
agent = html.EscapeString(agent)
friendlyAgent, ok := common.GetUserAgentPhrase(agent) friendlyAgent, ok := common.GetUserAgentPhrase(agent)
if !ok { if !ok {
friendlyAgent = agent friendlyAgent = agent
@ -789,10 +790,15 @@ func routePanelAnalyticsForumViews(w http.ResponseWriter, r *http.Request, user
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange) revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
fid, err := strconv.Atoi(sfid)
if err != nil {
return common.LocalError("Invalid integer", w, r, user)
}
common.DebugLog("in routePanelAnalyticsForumViews") common.DebugLog("in routePanelAnalyticsForumViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // TODO: Verify the agent is valid
rows, err := acc.Select("viewchunks_forums").Columns("count, createdAt").Where("forum = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(sfid) rows, err := acc.Select("viewchunks_forums").Columns("count, createdAt").Where("forum = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(fid)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -809,10 +815,6 @@ func routePanelAnalyticsForumViews(w http.ResponseWriter, r *http.Request, user
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList} graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
common.DebugLogf("graph: %+v\n", graph) common.DebugLogf("graph: %+v\n", graph)
fid, err := strconv.Atoi(sfid)
if err != nil {
return common.InternalError(err, w, r)
}
forum, err := common.Forums.Get(fid) forum, err := common.Forums.Get(fid)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
@ -836,10 +838,11 @@ func routePanelAnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange) revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
system = html.EscapeString(system)
common.DebugLog("in routePanelAnalyticsSystemViews") common.DebugLog("in routePanelAnalyticsSystemViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // TODO: Verify the OS name is valid
rows, err := acc.Select("viewchunks_systems").Columns("count, createdAt").Where("system = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(system) rows, err := acc.Select("viewchunks_systems").Columns("count, createdAt").Where("system = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(system)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
@ -857,7 +860,6 @@ func routePanelAnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList} graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
common.DebugLogf("graph: %+v\n", graph) common.DebugLogf("graph: %+v\n", graph)
system = html.EscapeString(system)
friendlySystem, ok := common.GetOSPhrase(system) friendlySystem, ok := common.GetOSPhrase(system)
if !ok { if !ok {
friendlySystem = system friendlySystem = system
@ -867,6 +869,51 @@ func routePanelAnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user
return panelRenderTemplate("panel_analytics_system_views", w, r, user, &pi) return panelRenderTemplate("panel_analytics_system_views", w, r, user, &pi)
} }
func routePanelAnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common.User, lang string) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
headerVars.Stylesheets = append(headerVars.Stylesheets, "chartist/chartist.min.css")
headerVars.Scripts = append(headerVars.Scripts, "chartist/chartist.min.js")
headerVars.Scripts = append(headerVars.Scripts, "analytics.js")
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
lang = html.EscapeString(lang)
common.DebugLog("in routePanelAnalyticsLanguageViews")
acc := qgen.Builder.Accumulator()
// TODO: Verify the language code is valid
rows, err := acc.Select("viewchunks_langs").Columns("count, createdAt").Where("lang = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(lang)
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
viewMap, err = panelAnalyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return common.InternalError(err, w, r)
}
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
}
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
common.DebugLogf("graph: %+v\n", graph)
friendlyLang, ok := common.GetHumanLangPhrase(lang)
if !ok {
friendlyLang = lang
}
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", lang, friendlyLang, graph, timeRange.Range}
return panelRenderTemplate("panel_analytics_lang_views", w, r, user, &pi)
}
func routePanelAnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.User, domain string) common.RouteError { func routePanelAnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.User, domain string) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
@ -1163,6 +1210,46 @@ func routePanelAnalyticsSystems(w http.ResponseWriter, r *http.Request, user com
return panelRenderTemplate("panel_analytics_systems", w, r, user, &pi) return panelRenderTemplate("panel_analytics_systems", w, r, user, &pi)
} }
func routePanelAnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := panelAnalyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
acc := qgen.Builder.Accumulator()
rows, err := acc.Select("viewchunks_langs").Columns("count, lang").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
langMap, err := panelAnalyticsRowsToNameMap(rows)
if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Can we de-duplicate these analytics functions further?
// TODO: Sort this slice
var langItems []common.PanelAnalyticsAgentsItem
for lang, count := range langMap {
lLang, ok := common.GetHumanLangPhrase(lang)
if !ok {
lLang = lang
}
langItems = append(langItems, common.PanelAnalyticsAgentsItem{
Agent: lang,
FriendlyAgent: lLang,
Count: count,
})
}
pi := common.PanelAnalyticsAgentsPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", langItems, timeRange.Range}
return panelRenderTemplate("panel_analytics_langs", w, r, user, &pi)
}
func routePanelAnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelAnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user) headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
@ -1688,6 +1775,10 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user common.Use
groupList = append(groupList, group) groupList = append(groupList, group)
} }
if r.FormValue("updated") == "1" {
headerVars.NoticeList = append(headerVars.NoticeList, "The user was successfully updated")
}
pi := common.PanelPage{common.GetTitlePhrase("panel_edit_user"), user, headerVars, stats, "users", groupList, targetUser} pi := common.PanelPage{common.GetTitlePhrase("panel_edit_user"), user, headerVars, stats, "users", groupList, targetUser}
if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) {
return nil return nil
@ -1776,7 +1867,7 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user comm
} }
targetUser.CacheRemove() targetUser.CacheRemove()
http.Redirect(w, r, "/panel/users/edit/"+strconv.Itoa(targetUser.ID), http.StatusSeeOther) http.Redirect(w, r, "/panel/users/edit/"+strconv.Itoa(targetUser.ID)+"?updated=1", http.StatusSeeOther)
return nil return nil
} }

View File

@ -241,8 +241,6 @@ func writeSelects(adapter qgen.Adapter) error {
build.Select("getTopicBasic").Table("topics").Columns("title, content").Where("tid = ?").Parse() build.Select("getTopicBasic").Table("topics").Columns("title, content").Where("tid = ?").Parse()
build.Select("getActivityEntry").Table("activity_stream").Columns("actor, targetUser, event, elementType, elementID").Where("asid = ?").Parse()
build.Select("forumEntryExists").Table("forums").Columns("fid").Where("name = ''").Orderby("fid ASC").Limit("0,1").Parse() build.Select("forumEntryExists").Table("forums").Columns("fid").Where("name = ''").Orderby("fid ASC").Limit("0,1").Parse()
build.Select("groupEntryExists").Table("users_groups").Columns("gid").Where("name = ''").Orderby("gid ASC").Limit("0,1").Parse() build.Select("groupEntryExists").Table("users_groups").Columns("gid").Where("name = ''").Orderby("gid ASC").Limit("0,1").Parse()
@ -259,11 +257,6 @@ func writeLeftJoins(adapter qgen.Adapter) error {
} }
func writeInnerJoins(adapter qgen.Adapter) (err error) { func writeInnerJoins(adapter qgen.Adapter) (err error) {
_, err = adapter.SimpleInnerJoin("getWatchers", "activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", "")
if err != nil {
return err
}
return nil return nil
} }
@ -272,10 +265,6 @@ func writeInserts(adapter qgen.Adapter) error {
build.Insert("createReport").Table("topics").Columns("title, content, parsed_content, createdAt, lastReplyAt, createdBy, lastReplyBy, data, parentID, css_class").Fields("?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,1,'report'").Parse() build.Insert("createReport").Table("topics").Columns("title, content, parsed_content, createdAt, lastReplyAt, createdBy, lastReplyBy, data, parentID, css_class").Fields("?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,1,'report'").Parse()
build.Insert("addActivity").Table("activity_stream").Columns("actor, targetUser, event, elementType, elementID").Fields("?,?,?,?,?").Parse()
build.Insert("notifyOne").Table("activity_stream_matches").Columns("watcher, asid").Fields("?,?").Parse()
build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse() build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse()
build.Insert("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse() build.Insert("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse()
@ -361,11 +350,6 @@ func writeInsertLeftJoins(adapter qgen.Adapter) error {
} }
func writeInsertInnerJoins(adapter qgen.Adapter) error { func writeInsertInnerJoins(adapter qgen.Adapter) error {
adapter.SimpleInsertInnerJoin("notifyWatchers",
qgen.DBInsert{"activity_stream_matches", "watcher, asid", ""},
qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""},
)
return nil return nil
} }

View File

@ -424,6 +424,15 @@ func createTables(adapter qgen.Adapter) error {
[]qgen.DBTableKey{}, []qgen.DBTableKey{},
) )
qgen.Install.CreateTable("viewchunks_langs", "", "",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"count", "int", 0, false, false, "0"},
qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""},
qgen.DBTableColumn{"lang", "varchar", 200, false, false, ""}, // en, ru, etc.
},
[]qgen.DBTableKey{},
)
qgen.Install.CreateTable("viewchunks_referrers", "", "", qgen.Install.CreateTable("viewchunks_referrers", "", "",
[]qgen.DBTableColumn{ []qgen.DBTableColumn{
qgen.DBTableColumn{"count", "int", 0, false, false, "0"}, qgen.DBTableColumn{"count", "int", 0, false, false, "0"},

View File

@ -200,6 +200,7 @@ func main() {
"twitter", "twitter",
"cloudflare", "cloudflare",
"uptimebot", "uptimebot",
"slackbot",
"discourse", "discourse",
"lynx", "lynx",
"blank", "blank",
@ -220,6 +221,7 @@ package main
import ( import (
"log" "log"
"strings" "strings"
"strconv"
"sync" "sync"
"errors" "errors"
"net/http" "net/http"
@ -275,6 +277,7 @@ var markToAgent = map[string]string{
"SeznamBot":"seznambot", "SeznamBot":"seznambot",
"CloudFlare":"cloudflare", // Track alwayson specifically in case there are other bots? "CloudFlare":"cloudflare", // Track alwayson specifically in case there are other bots?
"Uptimebot":"uptimebot", "Uptimebot":"uptimebot",
"Slackbot":"slackbot",
"Discordbot":"discord", "Discordbot":"discord",
"Twitterbot":"twitter", "Twitterbot":"twitter",
"Discourse":"discourse", "Discourse":"discourse",
@ -348,7 +351,7 @@ func (router *GenRouter) StripNewlines(data string) string {
return strings.Replace(strings.Replace(data,"\n","",-1),"\r","",-1) return strings.Replace(strings.Replace(data,"\n","",-1),"\r","",-1)
} }
func (router *GenRouter) DumpRequest(req *http.Request) { func (router *GenRouter) DumpRequest(req *http.Request, prepend string) {
var heads string var heads string
for key, value := range req.Header { for key, value := range req.Header {
for _, vvalue := range value { for _, vvalue := range value {
@ -356,7 +359,8 @@ func (router *GenRouter) DumpRequest(req *http.Request) {
} }
} }
log.Print("\nUA: " + router.StripNewlines(req.UserAgent()) + "\n" + log.Print(prepend +
"\nUA: " + router.StripNewlines(req.UserAgent()) + "\n" +
"Method: " + router.StripNewlines(req.Method) + "\n" + heads + "Method: " + router.StripNewlines(req.Method) + "\n" + heads +
"req.Host: " + router.StripNewlines(req.Host) + "\n" + "req.Host: " + router.StripNewlines(req.Host) + "\n" +
"req.URL.Path: " + router.StripNewlines(req.URL.Path) + "\n" + "req.URL.Path: " + router.StripNewlines(req.URL.Path) + "\n" +
@ -366,8 +370,7 @@ func (router *GenRouter) DumpRequest(req *http.Request) {
} }
func (router *GenRouter) SuspiciousRequest(req *http.Request) { func (router *GenRouter) SuspiciousRequest(req *http.Request) {
log.Print("Suspicious Request") router.DumpRequest(req,"Suspicious Request")
router.DumpRequest(req)
counters.AgentViewCounter.Bump({{.AllAgentMap.suspicious}}) counters.AgentViewCounter.Bump({{.AllAgentMap.suspicious}})
} }
@ -394,24 +397,23 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host {
w.WriteHeader(200) // 400 w.WriteHeader(200) // 400
w.Write([]byte("")) w.Write([]byte(""))
log.Print("Malformed Request") router.DumpRequest(req,"Malformed Request")
router.DumpRequest(req)
counters.AgentViewCounter.Bump({{.AllAgentMap.malformed}}) counters.AgentViewCounter.Bump({{.AllAgentMap.malformed}})
return return
} }
// TODO: Cover more suspicious strings and at a lower layer than this // TODO: Cover more suspicious strings and at a lower layer than this
for _, char := range req.URL.Path { for _, char := range req.URL.Path {
if char != '&' && !(char > 44 && char < 58) && char != '=' && char != '?' && !(char > 64 && char < 91) && char != '\\' && char != '_' && !(char > 96 && char < 123) { if char != '&' && !(char > 44 && char < 58) && char != '=' && char != '?' && !(char > 64 && char < 91) && char != '\\' && char != '_' && !(char > 96 && char < 123) {
router.SuspiciousRequest(req)
break
}
}
lowerPath := strings.ToLower(req.URL.Path)
// TODO: Flag any requests which has a dot with anything but a number after that
if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") || strings.Contains(lowerPath,".php") || strings.Contains(lowerPath,".asp") || strings.Contains(lowerPath,".cgi") || strings.Contains(lowerPath,".py") || strings.Contains(lowerPath,".sql") {
router.SuspiciousRequest(req) router.SuspiciousRequest(req)
break
} }
}
lowerPath := strings.ToLower(req.URL.Path)
// TODO: Flag any requests which has a dot with anything but a number after that
if strings.Contains(req.URL.Path,"..") || strings.Contains(req.URL.Path,"--") || strings.Contains(lowerPath,".php") || strings.Contains(lowerPath,".asp") || strings.Contains(lowerPath,".cgi") || strings.Contains(lowerPath,".py") || strings.Contains(lowerPath,".sql") || strings.Contains(lowerPath,".action") {
router.SuspiciousRequest(req)
}
var prefix, extraData string var prefix, extraData string
prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1] prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1]
@ -421,8 +423,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
if common.Dev.SuperDebug { if common.Dev.SuperDebug {
log.Print("before routes.StaticFile") router.DumpRequest(req,"before routes.StaticFile")
router.DumpRequest(req)
} }
// Increment the request counter // Increment the request counter
counters.GlobalViewCounter.Bump() counters.GlobalViewCounter.Bump()
@ -444,8 +445,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if ua == "" { if ua == "" {
counters.AgentViewCounter.Bump({{.AllAgentMap.blank}}) counters.AgentViewCounter.Bump({{.AllAgentMap.blank}})
if common.Dev.DebugMode { if common.Dev.DebugMode {
log.Print("Blank UA: ", req.UserAgent()) var prepend string
router.DumpRequest(req) for _, char := range req.UserAgent() {
prepend += strconv.Itoa(int(char)) + " "
}
router.DumpRequest(req,"Blank UA: " + prepend)
} }
} else { } else {
var runeEquals = func(a []rune, b []rune) bool { var runeEquals = func(a []rune, b []rune) bool {
@ -544,8 +548,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if agent == "" { if agent == "" {
counters.AgentViewCounter.Bump({{.AllAgentMap.unknown}}) counters.AgentViewCounter.Bump({{.AllAgentMap.unknown}})
if common.Dev.DebugMode { if common.Dev.DebugMode {
log.Print("Unknown UA: ", req.UserAgent()) var prepend string
router.DumpRequest(req) for _, char := range req.UserAgent() {
prepend += strconv.Itoa(int(char)) + " "
}
router.DumpRequest(req,"Blank UA: " + prepend)
} }
} else { } else {
counters.AgentViewCounter.Bump(agentMapEnum[agent]) counters.AgentViewCounter.Bump(agentMapEnum[agent])
@ -553,6 +560,15 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.OSViewCounter.Bump(osMapEnum[os]) counters.OSViewCounter.Bump(osMapEnum[os])
} }
// TODO: Do we want to track missing language headers too? Maybe as it's own type, e.g. "noheader"?
lang := req.Header.Get("Accept-Language")
if lang != "" {
lang = strings.TrimSpace(lang)
lLang := strings.Split(lang,"-")
common.DebugDetail("lLang:", lLang)
counters.LangViewCounter.Bump(lLang[0])
}
referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P
if referrer != "" { if referrer != "" {
// ? Optimise this a little? // ? Optimise this a little?

View File

@ -32,7 +32,7 @@ func routes() {
buildPollRoutes() buildPollRoutes()
buildAccountRoutes() buildAccountRoutes()
addRoute(Special("routeWebsockets", "/ws/")) addRoute(Special("common.RouteWebsockets", "/ws/"))
} }
// TODO: Test the email token route // TODO: Test the email token route
@ -85,7 +85,7 @@ func buildReplyRoutes() {
replyGroup := newRouteGroup("/reply/") replyGroup := newRouteGroup("/reply/")
replyGroup.Routes( replyGroup.Routes(
// TODO: Reduce this to 1MB for attachments for each file? // TODO: Reduce this to 1MB for attachments for each file?
UploadAction("routeCreateReplySubmit", "/reply/create/").MaxSizeVar("common.Config.MaxRequestSize"), // TODO: Rename the route so it's /reply/create/submit/ UploadAction("routes.CreateReplySubmit", "/reply/create/").MaxSizeVar("common.Config.MaxRequestSize"), // TODO: Rename the route so it's /reply/create/submit/
Action("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"), Action("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"),
Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"), Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"),
Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData"), Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData"),
@ -167,11 +167,13 @@ func buildPanelRoutes() {
View("routePanelAnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"), View("routePanelAnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"),
View("routePanelAnalyticsAgents", "/panel/analytics/agents/").Before("ParseForm"), View("routePanelAnalyticsAgents", "/panel/analytics/agents/").Before("ParseForm"),
View("routePanelAnalyticsSystems", "/panel/analytics/systems/").Before("ParseForm"), View("routePanelAnalyticsSystems", "/panel/analytics/systems/").Before("ParseForm"),
View("routePanelAnalyticsLanguages", "/panel/analytics/langs/").Before("ParseForm"),
View("routePanelAnalyticsReferrers", "/panel/analytics/referrers/").Before("ParseForm"), View("routePanelAnalyticsReferrers", "/panel/analytics/referrers/").Before("ParseForm"),
View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"), View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"),
View("routePanelAnalyticsAgentViews", "/panel/analytics/agent/", "extraData"), View("routePanelAnalyticsAgentViews", "/panel/analytics/agent/", "extraData"),
View("routePanelAnalyticsForumViews", "/panel/analytics/forum/", "extraData"), View("routePanelAnalyticsForumViews", "/panel/analytics/forum/", "extraData"),
View("routePanelAnalyticsSystemViews", "/panel/analytics/system/", "extraData"), View("routePanelAnalyticsSystemViews", "/panel/analytics/system/", "extraData"),
View("routePanelAnalyticsLanguageViews", "/panel/analytics/lang/", "extraData"),
View("routePanelAnalyticsReferrerViews", "/panel/analytics/referrer/", "extraData"), View("routePanelAnalyticsReferrerViews", "/panel/analytics/referrer/", "extraData"),
View("routePanelAnalyticsPosts", "/panel/analytics/posts/").Before("ParseForm"), View("routePanelAnalyticsPosts", "/panel/analytics/posts/").Before("ParseForm"),
View("routePanelAnalyticsTopics", "/panel/analytics/topics/").Before("ParseForm"), View("routePanelAnalyticsTopics", "/panel/analytics/topics/").Before("ParseForm"),

View File

@ -175,7 +175,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
if err != nil { if err != nil {
return common.InternalErrorJS(err, w, r) return common.InternalErrorJS(err, w, r)
} }
res, err := buildAlert(asid, event, elementType, actorID, targetUserID, elementID, user) res, err := common.BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, user)
if err != nil { if err != nil {
return common.LocalErrorJS(err.Error(), w, r) return common.LocalErrorJS(err.Error(), w, r)
} }

View File

@ -25,7 +25,7 @@ func AccountLogin(w http.ResponseWriter, r *http.Request, user common.User) comm
if common.RunPreRenderHook("pre_render_login", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_login", w, r, &user, &pi) {
return nil return nil
} }
err := common.Templates.ExecuteTemplate(w, "login.html", pi) err := common.RunThemeTemplate(headerVars.Theme.Name, "login", pi, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -84,7 +84,7 @@ func AccountRegister(w http.ResponseWriter, r *http.Request, user common.User) c
if common.RunPreRenderHook("pre_render_register", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_register", w, r, &user, &pi) {
return nil return nil
} }
err := common.Templates.ExecuteTemplate(w, "register.html", pi) err := common.RunThemeTemplate(headerVars.Theme.Name, "register", pi, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

View File

@ -30,11 +30,11 @@ func IPSearch(w http.ResponseWriter, r *http.Request, user common.User) common.R
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
pi := common.IPSearchPage{common.GetTitlePhrase("ip-search"), user, headerVars, userList, ip} pi := common.IPSearchPage{common.GetTitlePhrase("ip_search"), user, headerVars, userList, ip}
if common.RunPreRenderHook("pre_render_ip_search", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_ip_search", w, r, &user, &pi) {
return nil return nil
} }
err = common.Templates.ExecuteTemplate(w, "ip-search.html", pi) err = common.RunThemeTemplate(headerVars.Theme.Name, "ip_search", pi, w)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

View File

@ -1,13 +1,188 @@
package routes package routes
import ( import (
"crypto/sha256"
"database/sql" "database/sql"
"encoding/hex"
"html"
"io"
"log"
"net/http" "net/http"
"os"
"regexp"
"strconv" "strconv"
"strings"
"../common" "../common"
"../common/counters"
) )
// TODO: De-duplicate the upload logic
func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
tid, err := strconv.Atoi(r.PostFormValue("tid"))
if err != nil {
return common.PreError("Failed to convert the Topic ID", w, r)
}
topic, err := common.Topics.Get(tid)
if err == sql.ErrNoRows {
return common.PreError("Couldn't find the parent topic", w, r)
} else if err != nil {
return common.InternalError(err, w, r)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateReply {
return common.NoPermissions(w, r, user)
}
// Handle the file attachments
// TODO: Stop duplicating this code
if user.Perms.UploadFiles {
files, ok := r.MultipartForm.File["upload_files"]
if ok {
if len(files) > 5 {
return common.LocalError("You can't attach more than five files", w, r, user)
}
for _, file := range files {
log.Print("file.Filename ", file.Filename)
extarr := strings.Split(file.Filename, ".")
if len(extarr) < 2 {
return common.LocalError("Bad file", w, r, user)
}
ext := extarr[len(extarr)-1]
// TODO: Can we do this without a regex?
reg, err := regexp.Compile("[^A-Za-z0-9]+")
if err != nil {
return common.LocalError("Bad file extension", w, r, user)
}
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
if !common.AllowedFileExts.Contains(ext) {
return common.LocalError("You're not allowed to upload files with this extension", w, r, user)
}
infile, err := file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, infile)
if err != nil {
return common.LocalError("Upload failed [Hashing Failed]", w, r, user)
}
infile.Close()
checksum := hex.EncodeToString(hasher.Sum(nil))
filename := checksum + "." + ext
outfile, err := os.Create("." + "/attachs/" + filename)
if err != nil {
return common.LocalError("Upload failed [File Creation Failed]", w, r, user)
}
defer outfile.Close()
infile, err = file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
_, err = io.Copy(outfile, infile)
if err != nil {
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
}
err = common.Attachments.Add(topic.ParentID, "forums", tid, "replies", user.ID, filename)
if err != nil {
return common.InternalError(err, w, r)
}
}
}
}
content := common.PreparseMessage(r.PostFormValue("reply-content"))
// TODO: Fully parse the post and put that in the parsed column
rid, err := common.Rstore.Create(topic, content, user.LastIP, user.ID)
if err != nil {
return common.InternalError(err, w, r)
}
reply, err := common.Rstore.Get(rid)
if err != nil {
return common.LocalError("Unable to load the reply", w, r, user)
}
if r.PostFormValue("has_poll") == "1" {
var maxPollOptions = 10
var pollInputItems = make(map[int]string)
for key, values := range r.Form {
common.DebugDetail("key: ", key)
common.DebugDetailf("values: %+v\n", values)
for _, value := range values {
if strings.HasPrefix(key, "pollinputitem[") {
halves := strings.Split(key, "[")
if len(halves) != 2 {
return common.LocalError("Malformed pollinputitem", w, r, user)
}
halves[1] = strings.TrimSuffix(halves[1], "]")
index, err := strconv.Atoi(halves[1])
if err != nil {
return common.LocalError("Malformed pollinputitem", w, r, user)
}
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
_, exists := pollInputItems[index]
if !exists && len(html.EscapeString(value)) != 0 {
pollInputItems[index] = html.EscapeString(value)
if len(pollInputItems) >= maxPollOptions {
break
}
}
}
}
}
// Make sure the indices are sequential to avoid out of bounds issues
var seqPollInputItems = make(map[int]string)
for i := 0; i < len(pollInputItems); i++ {
seqPollInputItems[i] = pollInputItems[i]
}
pollType := 0 // Basic single choice
_, err := common.Polls.Create(reply, pollType, seqPollInputItems)
if err != nil {
return common.LocalError("Failed to add poll to reply", w, r, user) // TODO: Might need to be an internal error as it could leave phantom polls?
}
}
err = common.Forums.UpdateLastTopic(tid, user.ID, topic.ParentID)
if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r)
}
common.AddActivityAndNotifyAll(user.ID, topic.CreatedBy, "reply", "topic", tid)
if err != nil {
return common.InternalError(err, w, r)
}
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
wcount := common.WordCount(content)
err = user.IncreasePostStats(wcount, false)
if err != nil {
return common.InternalError(err, w, r)
}
counters.PostCounter.Bump()
return nil
}
// TODO: Disable stat updates in posts handled by plugin_guilds // TODO: Disable stat updates in posts handled by plugin_guilds
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes // TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError {

View File

@ -0,0 +1,5 @@
CREATE TABLE [viewchunks_langs] (
[count] int DEFAULT 0 not null,
[createdAt] datetime not null,
[lang] nvarchar (200) not null
);

View File

@ -0,0 +1,5 @@
CREATE TABLE `viewchunks_langs` (
`count` int DEFAULT 0 not null,
`createdAt` datetime not null,
`lang` varchar(200) not null
);

View File

@ -0,0 +1,5 @@
CREATE TABLE `viewchunks_langs` (
`count` int DEFAULT 0 not null,
`createdAt` timestamp not null,
`lang` varchar (200) not null
);

115
template_error.go Normal file
View File

@ -0,0 +1,115 @@
// +build !no_templategen
// Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "net/http"
import "./common"
var error_Tmpl_Phrase_ID int
// nolint
func init() {
common.Template_error_handle = Template_error
common.Ctemplates = append(common.Ctemplates,"error")
common.TmplPtrMap["error"] = &common.Template_error_handle
common.TmplPtrMap["o_error"] = Template_error
error_Tmpl_Phrase_ID = common.RegisterTmplPhraseNames([]string{
"error_head",
})
}
// nolint
func Template_error(tmpl_error_vars common.Page, w http.ResponseWriter) error {
var phrases = common.GetTmplPhrasesBytes(error_Tmpl_Phrase_ID)
w.Write(header_0)
w.Write([]byte(tmpl_error_vars.Title))
w.Write(header_1)
w.Write([]byte(tmpl_error_vars.Header.Site.Name))
w.Write(header_2)
w.Write([]byte(tmpl_error_vars.Header.Theme.Name))
w.Write(header_3)
if len(tmpl_error_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_error_vars.Header.Stylesheets {
w.Write(header_4)
w.Write([]byte(item))
w.Write(header_5)
}
}
w.Write(header_6)
if len(tmpl_error_vars.Header.Scripts) != 0 {
for _, item := range tmpl_error_vars.Header.Scripts {
w.Write(header_7)
w.Write([]byte(item))
w.Write(header_8)
}
}
w.Write(header_9)
w.Write([]byte(tmpl_error_vars.CurrentUser.Session))
w.Write(header_10)
w.Write([]byte(tmpl_error_vars.Header.Site.URL))
w.Write(header_11)
if tmpl_error_vars.Header.MetaDesc != "" {
w.Write(header_12)
w.Write([]byte(tmpl_error_vars.Header.MetaDesc))
w.Write(header_13)
}
w.Write(header_14)
if !tmpl_error_vars.CurrentUser.IsSuperMod {
w.Write(header_15)
}
w.Write(header_16)
w.Write(menu_0)
w.Write(menu_1)
w.Write([]byte(tmpl_error_vars.Header.Site.ShortName))
w.Write(menu_2)
if tmpl_error_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_error_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_error_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_17)
if tmpl_error_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_18)
}
w.Write(header_19)
if len(tmpl_error_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_error_vars.Header.NoticeList {
w.Write(header_20)
w.Write([]byte(item))
w.Write(header_21)
}
}
w.Write(error_0)
w.Write(phrases[0])
w.Write(error_1)
w.Write([]byte(tmpl_error_vars.Something.(string)))
w.Write(error_2)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_error_vars.Header)))
w.Write(footer_1)
if len(tmpl_error_vars.Header.Themes) != 0 {
for _, item := range tmpl_error_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_2)
w.Write([]byte(item.Name))
w.Write(footer_3)
if tmpl_error_vars.Header.Theme.Name == item.Name {
w.Write(footer_4)
}
w.Write(footer_5)
w.Write([]byte(item.FriendlyName))
w.Write(footer_6)
}
}
}
w.Write(footer_7)
w.Write([]byte(common.BuildWidget("rightSidebar",tmpl_error_vars.Header)))
w.Write(footer_8)
return nil
}

View File

@ -3,9 +3,11 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "strconv"
import "net/http" import "net/http"
import "./common" import "./common"
import "strconv"
var forum_Tmpl_Phrase_ID int
// nolint // nolint
func init() { func init() {
@ -102,133 +104,134 @@ w.Write(forum_8)
w.Write([]byte(tmpl_forum_vars.Title)) w.Write([]byte(tmpl_forum_vars.Title))
w.Write(forum_9) w.Write(forum_9)
if tmpl_forum_vars.CurrentUser.ID != 0 { if tmpl_forum_vars.CurrentUser.ID != 0 {
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_10) w.Write(forum_10)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_11) w.Write(forum_11)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_12) w.Write(forum_12)
} else {
w.Write(forum_13) w.Write(forum_13)
} } else {
w.Write(forum_14) w.Write(forum_14)
} }
w.Write(forum_15) w.Write(forum_15)
if tmpl_forum_vars.CurrentUser.ID != 0 {
w.Write(forum_16)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_17)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Avatar))
w.Write(forum_18)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_19)
if tmpl_forum_vars.CurrentUser.Perms.UploadFiles {
w.Write(forum_20)
} }
w.Write(forum_16)
if tmpl_forum_vars.CurrentUser.ID != 0 {
w.Write(forum_17)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_18)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Avatar))
w.Write(forum_19)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_20)
if tmpl_forum_vars.CurrentUser.Perms.UploadFiles {
w.Write(forum_21) w.Write(forum_21)
} }
}
w.Write(forum_22) w.Write(forum_22)
}
}
w.Write(forum_23)
if len(tmpl_forum_vars.ItemList) != 0 { if len(tmpl_forum_vars.ItemList) != 0 {
for _, item := range tmpl_forum_vars.ItemList { for _, item := range tmpl_forum_vars.ItemList {
w.Write(forum_23)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(forum_24) w.Write(forum_24)
if item.Sticky { w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(forum_25) w.Write(forum_25)
if item.Sticky {
w.Write(forum_26)
} else { } else {
if item.IsClosed { if item.IsClosed {
w.Write(forum_26)
}
}
w.Write(forum_27) w.Write(forum_27)
w.Write([]byte(item.Creator.Link)) }
}
w.Write(forum_28) w.Write(forum_28)
w.Write([]byte(item.Creator.Avatar)) w.Write([]byte(item.Creator.Link))
w.Write(forum_29) w.Write(forum_29)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Avatar))
w.Write(forum_30) w.Write(forum_30)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(forum_31) w.Write(forum_31)
w.Write([]byte(item.Link))
w.Write(forum_32)
w.Write([]byte(item.Title))
w.Write(forum_33)
w.Write([]byte(item.Creator.Link))
w.Write(forum_34)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(forum_32)
w.Write([]byte(item.Link))
w.Write(forum_33)
w.Write([]byte(item.Title))
w.Write(forum_34)
w.Write([]byte(item.Creator.Link))
w.Write(forum_35) w.Write(forum_35)
if item.IsClosed { w.Write([]byte(item.Creator.Name))
w.Write(forum_36) w.Write(forum_36)
} if item.IsClosed {
if item.Sticky {
w.Write(forum_37) w.Write(forum_37)
} }
w.Write(forum_38)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_39)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(forum_40)
if item.Sticky { if item.Sticky {
w.Write(forum_38)
}
w.Write(forum_39)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_40)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(forum_41) w.Write(forum_41)
if item.Sticky {
w.Write(forum_42)
} else { } else {
if item.IsClosed { if item.IsClosed {
w.Write(forum_42)
}
}
w.Write(forum_43) w.Write(forum_43)
w.Write([]byte(item.LastUser.Link)) }
}
w.Write(forum_44) w.Write(forum_44)
w.Write([]byte(item.LastUser.Avatar)) w.Write([]byte(item.LastUser.Link))
w.Write(forum_45) w.Write(forum_45)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_46) w.Write(forum_46)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Name))
w.Write(forum_47) w.Write(forum_47)
w.Write([]byte(item.LastUser.Link))
w.Write(forum_48)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Name))
w.Write(forum_48)
w.Write([]byte(item.LastUser.Link))
w.Write(forum_49) w.Write(forum_49)
w.Write([]byte(item.RelativeLastReplyAt)) w.Write([]byte(item.LastUser.Name))
w.Write(forum_50) w.Write(forum_50)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write(forum_51)
} }
} else { } else {
w.Write(forum_51)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_52) w.Write(forum_52)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_53) w.Write(forum_53)
} w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_54) w.Write(forum_54)
} }
w.Write(forum_55) w.Write(forum_55)
if tmpl_forum_vars.LastPage > 1 { }
w.Write(forum_56) w.Write(forum_56)
if tmpl_forum_vars.Page > 1 { if tmpl_forum_vars.LastPage > 1 {
w.Write(forum_57) w.Write(forum_57)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page - 1))) if tmpl_forum_vars.Page > 1 {
w.Write(forum_58) w.Write(forum_58)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page - 1))) w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page - 1)))
w.Write(forum_59) w.Write(forum_59)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page - 1)))
w.Write(forum_60)
} }
if len(tmpl_forum_vars.PageList) != 0 { if len(tmpl_forum_vars.PageList) != 0 {
for _, item := range tmpl_forum_vars.PageList { for _, item := range tmpl_forum_vars.PageList {
w.Write(forum_60)
w.Write([]byte(strconv.Itoa(item)))
w.Write(forum_61) w.Write(forum_61)
w.Write([]byte(strconv.Itoa(item))) w.Write([]byte(strconv.Itoa(item)))
w.Write(forum_62) w.Write(forum_62)
w.Write([]byte(strconv.Itoa(item)))
w.Write(forum_63)
} }
} }
if tmpl_forum_vars.LastPage != tmpl_forum_vars.Page { if tmpl_forum_vars.LastPage != tmpl_forum_vars.Page {
w.Write(forum_63)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_64) w.Write(forum_64)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1))) w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_65) w.Write(forum_65)
} w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_66) w.Write(forum_66)
} }
w.Write(forum_67) w.Write(forum_67)
}
w.Write(forum_68)
w.Write(footer_0) w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_forum_vars.Header))) w.Write([]byte(common.BuildWidget("footer",tmpl_forum_vars.Header)))
w.Write(footer_1) w.Write(footer_1)

View File

@ -3,8 +3,10 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "net/http"
import "./common" import "./common"
import "net/http"
var forums_Tmpl_Phrase_ID int
// nolint // nolint
func init() { func init() {

View File

@ -3,10 +3,12 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "./common"
import "./extend/guilds/lib" import "./extend/guilds/lib"
import "strconv" import "strconv"
import "net/http" import "net/http"
import "./common"
var guilds_guild_list_Tmpl_Phrase_ID int
// nolint // nolint
func init() { func init() {

145
template_ip_search.go Normal file
View File

@ -0,0 +1,145 @@
// +build !no_templategen
// Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "net/http"
import "./common"
var ip_search_Tmpl_Phrase_ID int
// nolint
func init() {
common.Template_ip_search_handle = Template_ip_search
common.Ctemplates = append(common.Ctemplates,"ip_search")
common.TmplPtrMap["ip_search"] = &common.Template_ip_search_handle
common.TmplPtrMap["o_ip_search"] = Template_ip_search
ip_search_Tmpl_Phrase_ID = common.RegisterTmplPhraseNames([]string{
"ip_search_head",
"ip_search_no_users",
})
}
// nolint
func Template_ip_search(tmpl_ip_search_vars common.IPSearchPage, w http.ResponseWriter) error {
var phrases = common.GetTmplPhrasesBytes(ip_search_Tmpl_Phrase_ID)
w.Write(header_0)
w.Write([]byte(tmpl_ip_search_vars.Title))
w.Write(header_1)
w.Write([]byte(tmpl_ip_search_vars.Header.Site.Name))
w.Write(header_2)
w.Write([]byte(tmpl_ip_search_vars.Header.Theme.Name))
w.Write(header_3)
if len(tmpl_ip_search_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_ip_search_vars.Header.Stylesheets {
w.Write(header_4)
w.Write([]byte(item))
w.Write(header_5)
}
}
w.Write(header_6)
if len(tmpl_ip_search_vars.Header.Scripts) != 0 {
for _, item := range tmpl_ip_search_vars.Header.Scripts {
w.Write(header_7)
w.Write([]byte(item))
w.Write(header_8)
}
}
w.Write(header_9)
w.Write([]byte(tmpl_ip_search_vars.CurrentUser.Session))
w.Write(header_10)
w.Write([]byte(tmpl_ip_search_vars.Header.Site.URL))
w.Write(header_11)
if tmpl_ip_search_vars.Header.MetaDesc != "" {
w.Write(header_12)
w.Write([]byte(tmpl_ip_search_vars.Header.MetaDesc))
w.Write(header_13)
}
w.Write(header_14)
if !tmpl_ip_search_vars.CurrentUser.IsSuperMod {
w.Write(header_15)
}
w.Write(header_16)
w.Write(menu_0)
w.Write(menu_1)
w.Write([]byte(tmpl_ip_search_vars.Header.Site.ShortName))
w.Write(menu_2)
if tmpl_ip_search_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_ip_search_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_ip_search_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_17)
if tmpl_ip_search_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_18)
}
w.Write(header_19)
if len(tmpl_ip_search_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_ip_search_vars.Header.NoticeList {
w.Write(header_20)
w.Write([]byte(item))
w.Write(header_21)
}
}
w.Write(ip_search_0)
w.Write(phrases[0])
w.Write(ip_search_1)
if tmpl_ip_search_vars.IP != "" {
w.Write(ip_search_2)
w.Write([]byte(tmpl_ip_search_vars.IP))
w.Write(ip_search_3)
}
w.Write(ip_search_4)
if tmpl_ip_search_vars.IP != "" {
w.Write(ip_search_5)
if len(tmpl_ip_search_vars.ItemList) != 0 {
for _, item := range tmpl_ip_search_vars.ItemList {
w.Write(ip_search_6)
w.Write([]byte(item.Avatar))
w.Write(ip_search_7)
w.Write([]byte(item.Avatar))
w.Write(ip_search_8)
w.Write([]byte(item.Name))
w.Write(ip_search_9)
w.Write([]byte(item.Link))
w.Write(ip_search_10)
w.Write([]byte(item.Name))
w.Write(ip_search_11)
}
} else {
w.Write(ip_search_12)
w.Write(phrases[1])
w.Write(ip_search_13)
}
w.Write(ip_search_14)
}
w.Write(ip_search_15)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_ip_search_vars.Header)))
w.Write(footer_1)
if len(tmpl_ip_search_vars.Header.Themes) != 0 {
for _, item := range tmpl_ip_search_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_2)
w.Write([]byte(item.Name))
w.Write(footer_3)
if tmpl_ip_search_vars.Header.Theme.Name == item.Name {
w.Write(footer_4)
}
w.Write(footer_5)
w.Write([]byte(item.FriendlyName))
w.Write(footer_6)
}
}
}
w.Write(footer_7)
w.Write([]byte(common.BuildWidget("rightSidebar",tmpl_ip_search_vars.Header)))
w.Write(footer_8)
return nil
}

View File

@ -83,8 +83,10 @@ var header_17 = []byte(`
var header_18 = []byte(`class="shrink_main"`) var header_18 = []byte(`class="shrink_main"`)
var header_19 = []byte(`> var header_19 = []byte(`>
`) `)
var header_20 = []byte(`<div class="alert">`) var header_20 = []byte(`<div class="alertbox">
var header_21 = []byte(`</div>`) <div class="alert">`)
var header_21 = []byte(`</div>
</div>`)
var topic_0 = []byte(` var topic_0 = []byte(`
<form id="edit_topic_form" action='/topic/edit/submit/`) <form id="edit_topic_form" action='/topic/edit/submit/`)
@ -882,7 +884,7 @@ window.addEventListener("hashchange", handle_profile_hashbit, false)
`) `)
var forums_0 = []byte(` var forums_0 = []byte(`
<main itemscope itemtype="http://schema.org/ItemList"> <main id="forumsItemList" itemscope itemtype="http://schema.org/ItemList">
<div class="rowblock opthead"> <div class="rowblock opthead">
<div class="rowitem"><h1 itemprop="name">Forums</h1></div> <div class="rowitem"><h1 itemprop="name">Forums</h1></div>
@ -936,31 +938,35 @@ var forums_22 = []byte(`
</main> </main>
`) `)
var topics_0 = []byte(` var topics_0 = []byte(`
<main itemscope itemtype="http://schema.org/ItemList"> <main id="topicsItemList" itemscope itemtype="http://schema.org/ItemList">
<div class="rowblock rowhead topic_list_title_block"> <div class="rowblock rowhead topic_list_title_block`)
<div class="rowitem topic_list_title`)
var topics_1 = []byte(` has_opt`) var topics_1 = []byte(` has_opt`)
var topics_2 = []byte(`"><h1 itemprop="name">All Topics</h1></div> var topics_2 = []byte(`">
<div class="rowitem topic_list_title"><h1 itemprop="name">All Topics</h1></div>
`) `)
var topics_3 = []byte(` var topics_3 = []byte(`
<div class="pre_opt auto_hide"></div> <div class="optbox">
<div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/"></a></div>
`) `)
var topics_4 = []byte(` var topics_4 = []byte(`
<div class="opt mod_opt" title="Moderate"> <div class="pre_opt auto_hide"></div>
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a> <div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/"></a></div>
`)
var topics_5 = []byte(`
<div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
</div>
`)
var topics_6 = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic anywhere"><a></a></div>`)
var topics_7 = []byte(`
</div> </div>
`)
var topics_5 = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic anywhere"><a></a></div>`)
var topics_6 = []byte(`
<div style="clear: both;"></div> <div style="clear: both;"></div>
`) `)
var topics_7 = []byte(` var topics_8 = []byte(`
</div> </div>
`) `)
var topics_8 = []byte(` var topics_9 = []byte(`
<div class="mod_floater auto_hide"> <div class="mod_floater auto_hide">
<form method="post"> <form method="post">
<div class="mod_floater_head"> <div class="mod_floater_head">
@ -978,10 +984,10 @@ var topics_8 = []byte(`
</div> </div>
`) `)
var topics_9 = []byte(` var topics_10 = []byte(`
<div id="mod_topic_mover" class="modal_pane auto_hide"> <div id="mod_topic_mover" class="modal_pane auto_hide">
<form action="/topic/move/submit/?session=`) <form action="/topic/move/submit/?session=`)
var topics_10 = []byte(`" method="post"> var topics_11 = []byte(`" method="post">
<input id="mover_fid" name="fid" value="0" type="hidden" /> <input id="mover_fid" name="fid" value="0" type="hidden" />
<div class="pane_header"> <div class="pane_header">
<h3>Move these topics to?</h3> <h3>Move these topics to?</h3>
@ -989,11 +995,11 @@ var topics_10 = []byte(`" method="post">
<div class="pane_body"> <div class="pane_body">
<div class="pane_table"> <div class="pane_table">
`) `)
var topics_11 = []byte(`<div id="mover_fid_`) var topics_12 = []byte(`<div id="mover_fid_`)
var topics_12 = []byte(`" data-fid="`) var topics_13 = []byte(`" data-fid="`)
var topics_13 = []byte(`" class="pane_row">`) var topics_14 = []byte(`" class="pane_row">`)
var topics_14 = []byte(`</div>`) var topics_15 = []byte(`</div>`)
var topics_15 = []byte(` var topics_16 = []byte(`
</div> </div>
</div> </div>
<div class="pane_buttons"> <div class="pane_buttons">
@ -1003,21 +1009,21 @@ var topics_15 = []byte(`
</div> </div>
<div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form name="topic_create_form_form" id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?session=`) <form name="topic_create_form_form" id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?session=`)
var topics_16 = []byte(`" method="post"></form> var topics_17 = []byte(`" method="post"></form>
<input form="quick_post_form" id="has_poll_input" name="has_poll" value="0" type="hidden" /> <input form="quick_post_form" id="has_poll_input" name="has_poll" value="0" type="hidden" />
<img class="little_row_avatar" src="`) <img class="little_row_avatar" src="`)
var topics_17 = []byte(`" height="64" alt="Your Avatar" title="Your Avatar" /> var topics_18 = []byte(`" height="64" alt="Your Avatar" title="Your Avatar" />
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
<div class="formrow topic_board_row real_first_child"> <div class="formrow topic_board_row real_first_child">
<div class="formitem"><select form="quick_post_form" id="topic_board_input" name="topic-board"> <div class="formitem"><select form="quick_post_form" id="topic_board_input" name="topic-board">
`) `)
var topics_18 = []byte(`<option `) var topics_19 = []byte(`<option `)
var topics_19 = []byte(`selected`) var topics_20 = []byte(`selected`)
var topics_20 = []byte(` value="`) var topics_21 = []byte(` value="`)
var topics_21 = []byte(`">`) var topics_22 = []byte(`">`)
var topics_22 = []byte(`</option>`) var topics_23 = []byte(`</option>`)
var topics_23 = []byte(` var topics_24 = []byte(`
</select></div> </select></div>
</div> </div>
<div class="formrow topic_name_row"> <div class="formrow topic_name_row">
@ -1045,101 +1051,101 @@ var topics_23 = []byte(`
<button form="quick_post_form" class="formbutton">Create Topic</button> <button form="quick_post_form" class="formbutton">Create Topic</button>
<button form="quick_post_form" class="formbutton" id="add_poll_button">Add Poll</button> <button form="quick_post_form" class="formbutton" id="add_poll_button">Add Poll</button>
`) `)
var topics_24 = []byte(` var topics_25 = []byte(`
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label> <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`) <div id="upload_file_dock"></div>`)
var topics_25 = []byte(` var topics_26 = []byte(`
<button class="formbutton close_form">Cancel</button> <button class="formbutton close_form">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
`) `)
var topics_26 = []byte(` var topics_27 = []byte(`
<div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum"> <div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum">
`) `)
var topics_27 = []byte(`<div class="topic_row" data-tid="`) var topics_28 = []byte(`<div class="topic_row" data-tid="`)
var topics_28 = []byte(`"> var topics_29 = []byte(`">
<div class="rowitem topic_left passive datarow `) <div class="rowitem topic_left passive datarow `)
var topics_29 = []byte(`topic_sticky`) var topics_30 = []byte(`topic_sticky`)
var topics_30 = []byte(`topic_closed`) var topics_31 = []byte(`topic_closed`)
var topics_31 = []byte(`"> var topics_32 = []byte(`">
<span class="selector"></span> <span class="selector"></span>
<a href="`) <a href="`)
var topics_32 = []byte(`"><img src="`) var topics_33 = []byte(`"><img src="`)
var topics_33 = []byte(`" height="64" alt="`) var topics_34 = []byte(`" height="64" alt="`)
var topics_34 = []byte(`'s Avatar" title="`) var topics_35 = []byte(`'s Avatar" title="`)
var topics_35 = []byte(`'s Avatar" /></a> var topics_36 = []byte(`'s Avatar" /></a>
<span class="topic_inner_left"> <span class="topic_inner_left">
<a class="rowtopic" href="`) <a class="rowtopic" href="`)
var topics_36 = []byte(`" itemprop="itemListElement"><span>`) var topics_37 = []byte(`" itemprop="itemListElement"><span>`)
var topics_37 = []byte(`</span></a> `) var topics_38 = []byte(`</span></a> `)
var topics_38 = []byte(`<a class="rowsmall parent_forum" href="`) var topics_39 = []byte(`<a class="rowsmall parent_forum" href="`)
var topics_39 = []byte(`">`) var topics_40 = []byte(`">`)
var topics_40 = []byte(`</a>`) var topics_41 = []byte(`</a>`)
var topics_41 = []byte(` var topics_42 = []byte(`
<br /><a class="rowsmall starter" href="`) <br /><a class="rowsmall starter" href="`)
var topics_42 = []byte(`">`) var topics_43 = []byte(`">`)
var topics_43 = []byte(`</a> var topics_44 = []byte(`</a>
`) `)
var topics_44 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`) var topics_45 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var topics_45 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`) var topics_46 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_46 = []byte(` var topics_47 = []byte(`
</span> </span>
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`) <span class="replyCount">`)
var topics_47 = []byte(`</span><br /> var topics_48 = []byte(`</span><br />
<span class="likeCount">`) <span class="likeCount">`)
var topics_48 = []byte(`</span> var topics_49 = []byte(`</span>
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow `) <div class="rowitem topic_right passive datarow `)
var topics_49 = []byte(`topic_sticky`) var topics_50 = []byte(`topic_sticky`)
var topics_50 = []byte(`topic_closed`) var topics_51 = []byte(`topic_closed`)
var topics_51 = []byte(`"> var topics_52 = []byte(`">
<a href="`) <a href="`)
var topics_52 = []byte(`"><img src="`) var topics_53 = []byte(`"><img src="`)
var topics_53 = []byte(`" height="64" alt="`) var topics_54 = []byte(`" height="64" alt="`)
var topics_54 = []byte(`'s Avatar" title="`) var topics_55 = []byte(`'s Avatar" title="`)
var topics_55 = []byte(`'s Avatar" /></a> var topics_56 = []byte(`'s Avatar" /></a>
<span> <span>
<a href="`) <a href="`)
var topics_56 = []byte(`" class="lastName" style="font-size: 14px;">`) var topics_57 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_57 = []byte(`</a><br> var topics_58 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`) <span class="rowsmall lastReplyAt">`)
var topics_58 = []byte(`</span> var topics_59 = []byte(`</span>
</span> </span>
</div> </div>
</div>`) </div>`)
var topics_59 = []byte(`<div class="rowitem passive rowmsg">There aren't any topics yet.`) var topics_60 = []byte(`<div class="rowitem passive rowmsg">There aren't any topics yet.`)
var topics_60 = []byte(` <a href="/topics/create/">Start one?</a>`) var topics_61 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_61 = []byte(`</div>`) var topics_62 = []byte(`</div>`)
var topics_62 = []byte(` var topics_63 = []byte(`
</div> </div>
`) `)
var topics_63 = []byte(` var topics_64 = []byte(`
<div class="pageset"> <div class="pageset">
`) `)
var topics_64 = []byte(`<div class="pageitem"><a href="?page=`) var topics_65 = []byte(`<div class="pageitem"><a href="?page=`)
var topics_65 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div> var topics_66 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div>
<link rel="prev" href="?page=`) <link rel="prev" href="?page=`)
var topics_66 = []byte(`" />`) var topics_67 = []byte(`" />`)
var topics_67 = []byte(` var topics_68 = []byte(`
<div class="pageitem"><a href="?page=`) <div class="pageitem"><a href="?page=`)
var topics_68 = []byte(`">`) var topics_69 = []byte(`">`)
var topics_69 = []byte(`</a></div> var topics_70 = []byte(`</a></div>
`) `)
var topics_70 = []byte(` var topics_71 = []byte(`
<link rel="next" href="?page=`) <link rel="next" href="?page=`)
var topics_71 = []byte(`" /> var topics_72 = []byte(`" />
<div class="pageitem"><a href="?page=`) <div class="pageitem"><a href="?page=`)
var topics_72 = []byte(`" rel="next" aria-label="Go to the next page">Next</a></div>`) var topics_73 = []byte(`" rel="next" aria-label="Go to the next page">Next</a></div>`)
var topics_73 = []byte(` var topics_74 = []byte(`
</div> </div>
`) `)
var topics_74 = []byte(` var topics_75 = []byte(`
</main> </main>
`) `)
@ -1151,33 +1157,37 @@ var forum_4 = []byte(`?page=`)
var forum_5 = []byte(`">&gt;</a></div>`) var forum_5 = []byte(`">&gt;</a></div>`)
var forum_6 = []byte(` var forum_6 = []byte(`
<main itemscope itemtype="http://schema.org/ItemList"> <main id="forumItemList" itemscope itemtype="http://schema.org/ItemList">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block"> <div id="forum_head_block" class="rowblock rowhead topic_list_title_block`)
<div class="rowitem forum_title`)
var forum_7 = []byte(` has_opt`) var forum_7 = []byte(` has_opt`)
var forum_8 = []byte(`"> var forum_8 = []byte(`">
<div class="rowitem forum_title">
<h1 itemprop="name">`) <h1 itemprop="name">`)
var forum_9 = []byte(`</h1> var forum_9 = []byte(`</h1>
</div> </div>
`) `)
var forum_10 = []byte(` var forum_10 = []byte(`
<div class="pre_opt auto_hide"></div> <div class="optbox">
<div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/`) `)
var forum_11 = []byte(`"></a></div> var forum_11 = []byte(`
`) <div class="pre_opt auto_hide"></div>
var forum_12 = []byte(` <div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/`)
<div class="opt mod_opt" title="Moderate"> var forum_12 = []byte(`"></a></div>
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a> `)
var forum_13 = []byte(`
<div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
</div>
`)
var forum_14 = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic in this forum"><a></a></div>`)
var forum_15 = []byte(`
</div> </div>
`)
var forum_13 = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic in this forum"><a></a></div>`)
var forum_14 = []byte(`
<div style="clear: both;"></div> <div style="clear: both;"></div>
`) `)
var forum_15 = []byte(` var forum_16 = []byte(`
</div> </div>
`) `)
var forum_16 = []byte(` var forum_17 = []byte(`
<div class="mod_floater auto_hide"> <div class="mod_floater auto_hide">
<form method="post"> <form method="post">
<div class="mod_floater_head"> <div class="mod_floater_head">
@ -1194,13 +1204,13 @@ var forum_16 = []byte(`
</form> </form>
</div> </div>
`) `)
var forum_17 = []byte(` var forum_18 = []byte(`
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<img class="little_row_avatar" src="`) <img class="little_row_avatar" src="`)
var forum_18 = []byte(`" height="64" alt="Your Avatar" title="Your Avatar" /> var forum_19 = []byte(`" height="64" alt="Your Avatar" title="Your Avatar" />
<input form="quick_post_form" id="topic_board_input" name="topic-board" value="`) <input form="quick_post_form" id="topic_board_input" name="topic-board" value="`)
var forum_19 = []byte(`" type="hidden"> var forum_20 = []byte(`" type="hidden">
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
<div class="formrow topic_name_row real_first_child"> <div class="formrow topic_name_row real_first_child">
@ -1224,99 +1234,217 @@ var forum_19 = []byte(`" type="hidden">
<button form="quick_post_form" name="topic-button" class="formbutton">Create Topic</button> <button form="quick_post_form" name="topic-button" class="formbutton">Create Topic</button>
<button form="quick_post_form" class="formbutton" id="add_poll_button">Add Poll</button> <button form="quick_post_form" class="formbutton" id="add_poll_button">Add Poll</button>
`) `)
var forum_20 = []byte(` var forum_21 = []byte(`
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label> <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`) <div id="upload_file_dock"></div>`)
var forum_21 = []byte(` var forum_22 = []byte(`
<button class="formbutton close_form">Cancel</button> <button class="formbutton close_form">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
`) `)
var forum_22 = []byte(` var forum_23 = []byte(`
<div id="forum_topic_list" class="rowblock topic_list"> <div id="forum_topic_list" class="rowblock topic_list">
`) `)
var forum_23 = []byte(`<div class="topic_row" data-tid="`) var forum_24 = []byte(`<div class="topic_row" data-tid="`)
var forum_24 = []byte(`"> var forum_25 = []byte(`">
<div class="rowitem topic_left passive datarow `) <div class="rowitem topic_left passive datarow `)
var forum_25 = []byte(`topic_sticky`) var forum_26 = []byte(`topic_sticky`)
var forum_26 = []byte(`topic_closed`) var forum_27 = []byte(`topic_closed`)
var forum_27 = []byte(`"> var forum_28 = []byte(`">
<span class="selector"></span> <span class="selector"></span>
<a href="`) <a href="`)
var forum_28 = []byte(`"><img src="`) var forum_29 = []byte(`"><img src="`)
var forum_29 = []byte(`" height="64" alt="`) var forum_30 = []byte(`" height="64" alt="`)
var forum_30 = []byte(`'s Avatar" title="`) var forum_31 = []byte(`'s Avatar" title="`)
var forum_31 = []byte(`'s Avatar" /></a> var forum_32 = []byte(`'s Avatar" /></a>
<span class="topic_inner_left"> <span class="topic_inner_left">
<a class="rowtopic" href="`) <a class="rowtopic" href="`)
var forum_32 = []byte(`" itemprop="itemListElement"><span>`) var forum_33 = []byte(`" itemprop="itemListElement"><span>`)
var forum_33 = []byte(`</span></a> var forum_34 = []byte(`</span></a>
<br /><a class="rowsmall starter" href="`) <br /><a class="rowsmall starter" href="`)
var forum_34 = []byte(`">`) var forum_35 = []byte(`">`)
var forum_35 = []byte(`</a> var forum_36 = []byte(`</a>
`) `)
var forum_36 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`) var forum_37 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var forum_37 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`) var forum_38 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_38 = []byte(` var forum_39 = []byte(`
</span> </span>
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`) <span class="replyCount">`)
var forum_39 = []byte(`</span><br /> var forum_40 = []byte(`</span><br />
<span class="likeCount">`) <span class="likeCount">`)
var forum_40 = []byte(`</span> var forum_41 = []byte(`</span>
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow `) <div class="rowitem topic_right passive datarow `)
var forum_41 = []byte(`topic_sticky`) var forum_42 = []byte(`topic_sticky`)
var forum_42 = []byte(`topic_closed`) var forum_43 = []byte(`topic_closed`)
var forum_43 = []byte(`"> var forum_44 = []byte(`">
<a href="`) <a href="`)
var forum_44 = []byte(`"><img src="`) var forum_45 = []byte(`"><img src="`)
var forum_45 = []byte(`" height="64" alt="`) var forum_46 = []byte(`" height="64" alt="`)
var forum_46 = []byte(`'s Avatar" title="`) var forum_47 = []byte(`'s Avatar" title="`)
var forum_47 = []byte(`'s Avatar" /></a> var forum_48 = []byte(`'s Avatar" /></a>
<span> <span>
<a href="`) <a href="`)
var forum_48 = []byte(`" class="lastName" style="font-size: 14px;">`) var forum_49 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_49 = []byte(`</a><br> var forum_50 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`) <span class="rowsmall lastReplyAt">`)
var forum_50 = []byte(`</span> var forum_51 = []byte(`</span>
</span> </span>
</div> </div>
</div>`) </div>`)
var forum_51 = []byte(`<div class="rowitem passive rowmsg">There aren't any topics in this forum yet.`) var forum_52 = []byte(`<div class="rowitem passive rowmsg">There aren't any topics in this forum yet.`)
var forum_52 = []byte(` <a href="/topics/create/`) var forum_53 = []byte(` <a href="/topics/create/`)
var forum_53 = []byte(`">Start one?</a>`) var forum_54 = []byte(`">Start one?</a>`)
var forum_54 = []byte(`</div>`) var forum_55 = []byte(`</div>`)
var forum_55 = []byte(` var forum_56 = []byte(`
</div> </div>
`) `)
var forum_56 = []byte(` var forum_57 = []byte(`
<div class="pageset"> <div class="pageset">
`) `)
var forum_57 = []byte(`<div class="pageitem"><a href="?page=`) var forum_58 = []byte(`<div class="pageitem"><a href="?page=`)
var forum_58 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div> var forum_59 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div>
<link rel="prev" href="?page=`) <link rel="prev" href="?page=`)
var forum_59 = []byte(`" />`) var forum_60 = []byte(`" />`)
var forum_60 = []byte(` var forum_61 = []byte(`
<div class="pageitem"><a href="?page=`) <div class="pageitem"><a href="?page=`)
var forum_61 = []byte(`">`) var forum_62 = []byte(`">`)
var forum_62 = []byte(`</a></div> var forum_63 = []byte(`</a></div>
`) `)
var forum_63 = []byte(` var forum_64 = []byte(`
<link rel="next" href="?page=`) <link rel="next" href="?page=`)
var forum_64 = []byte(`" /> var forum_65 = []byte(`" />
<div class="pageitem"><a href="?page=`) <div class="pageitem"><a href="?page=`)
var forum_65 = []byte(`" rel="next" aria-label="Go to the next page">Next</a></div>`) var forum_66 = []byte(`" rel="next" aria-label="Go to the next page">Next</a></div>`)
var forum_66 = []byte(` var forum_67 = []byte(`
</div> </div>
`) `)
var forum_67 = []byte(` var forum_68 = []byte(`
</main>
`)
var login_0 = []byte(`
<main id="login_page">
<div class="rowblock rowhead">
<div class="rowitem"><h1>`)
var login_1 = []byte(`</h1></div>
</div>
<div class="rowblock">
<form action="/accounts/login/submit/" method="post">
<div class="formrow login_name_row">
<div class="formitem formlabel"><a>`)
var login_2 = []byte(`</a></div>
<div class="formitem"><input name="username" type="text" placeholder="`)
var login_3 = []byte(`" required /></div>
</div>
<div class="formrow login_password_row">
<div class="formitem formlabel"><a>`)
var login_4 = []byte(`</a></div>
<div class="formitem"><input name="password" type="password" autocomplete="current-password" placeholder="*****" required /></div>
</div>
<div class="formrow login_button_row">
<div class="formitem"><button name="login-button" class="formbutton">`)
var login_5 = []byte(`</button></div>
<div class="formitem dont_have_account">`)
var login_6 = []byte(`</div>
</div>
</form>
</div>
</main>
`)
var register_0 = []byte(`
<main id="register_page">
<div class="rowblock rowhead">
<div class="rowitem"><h1>`)
var register_1 = []byte(`</h1></div>
</div>
<div class="rowblock">
<form action="/accounts/create/submit/" method="post">
<div class="formrow">
<div class="formitem formlabel"><a id="username_label">`)
var register_2 = []byte(`</a></div>
<div class="formitem"><input name="username" type="text" placeholder="`)
var register_3 = []byte(`" aria-labelledby="username_label" required /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a id="email_label">`)
var register_4 = []byte(`</a></div>
<div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" aria-labelledby="email_label" required /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a id="password_label">`)
var register_5 = []byte(`</a></div>
<div class="formitem"><input name="password" type="password" autocomplete="new-password" placeholder="*****" aria-labelledby="password_label" required /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a id="confirm_password_label">`)
var register_6 = []byte(`</a></div>
<div class="formitem"><input name="confirm_password" type="password" placeholder="*****" aria-labelledby="confirm_password_label" required /></div>
</div>
<div class="formrow">
<div class="formitem"><button name="register-button" class="formbutton">`)
var register_7 = []byte(`</button></div>
</div>
</form>
</div>
</main>
`)
var error_0 = []byte(`
<main>
<div class="rowblock rowhead">
<div class="rowitem"><h1>`)
var error_1 = []byte(`</h1></div>
</div>
<div class="rowblock">
<div class="rowitem passive rowmsg">`)
var error_2 = []byte(`</div>
</div>
</main>
`)
var ip_search_0 = []byte(`
<main id="ip_search_container">
<div class="rowblock rowhead">
<div class="rowitem">
<h1>`)
var ip_search_1 = []byte(`</h1>
</div>
</div>
<form action="/users/ips/" method="get" id="ip-search-form"></form>
<div class="rowblock ip_search_block">
<div class="rowitem passive">
<input form="ip-search-form" name="ip" class="ip_search_input" type="search" placeholder="🔍︎"`)
var ip_search_2 = []byte(` value="`)
var ip_search_3 = []byte(`"`)
var ip_search_4 = []byte(` />
<input form="ip-search-form" class="ip_search_search" type="submit" value="Search" />
</div>
</div>
`)
var ip_search_5 = []byte(`
<div class="rowblock rowlist bgavatars">
`)
var ip_search_6 = []byte(`<div class="rowitem" style="background-image: url('`)
var ip_search_7 = []byte(`');">
<img src="`)
var ip_search_8 = []byte(`" class="bgsub" alt="`)
var ip_search_9 = []byte(`'s Avatar" />
<a class="rowTitle" href="`)
var ip_search_10 = []byte(`">`)
var ip_search_11 = []byte(`</a>
</div>
`)
var ip_search_12 = []byte(`<div class="rowitem rowmsg">`)
var ip_search_13 = []byte(`</div>`)
var ip_search_14 = []byte(`
</div>
`)
var ip_search_15 = []byte(`
</main> </main>
`) `)
var guilds_guild_list_0 = []byte(` var guilds_guild_list_0 = []byte(`

128
template_login.go Normal file
View File

@ -0,0 +1,128 @@
// +build !no_templategen
// Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "net/http"
import "./common"
var login_Tmpl_Phrase_ID int
// nolint
func init() {
common.Template_login_handle = Template_login
common.Ctemplates = append(common.Ctemplates,"login")
common.TmplPtrMap["login"] = &common.Template_login_handle
common.TmplPtrMap["o_login"] = Template_login
login_Tmpl_Phrase_ID = common.RegisterTmplPhraseNames([]string{
"login_head",
"login_account_name",
"login_account_name",
"login_account_password",
"login_submit_button",
"login_no_account",
})
}
// nolint
func Template_login(tmpl_login_vars common.Page, w http.ResponseWriter) error {
var phrases = common.GetTmplPhrasesBytes(login_Tmpl_Phrase_ID)
w.Write(header_0)
w.Write([]byte(tmpl_login_vars.Title))
w.Write(header_1)
w.Write([]byte(tmpl_login_vars.Header.Site.Name))
w.Write(header_2)
w.Write([]byte(tmpl_login_vars.Header.Theme.Name))
w.Write(header_3)
if len(tmpl_login_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_login_vars.Header.Stylesheets {
w.Write(header_4)
w.Write([]byte(item))
w.Write(header_5)
}
}
w.Write(header_6)
if len(tmpl_login_vars.Header.Scripts) != 0 {
for _, item := range tmpl_login_vars.Header.Scripts {
w.Write(header_7)
w.Write([]byte(item))
w.Write(header_8)
}
}
w.Write(header_9)
w.Write([]byte(tmpl_login_vars.CurrentUser.Session))
w.Write(header_10)
w.Write([]byte(tmpl_login_vars.Header.Site.URL))
w.Write(header_11)
if tmpl_login_vars.Header.MetaDesc != "" {
w.Write(header_12)
w.Write([]byte(tmpl_login_vars.Header.MetaDesc))
w.Write(header_13)
}
w.Write(header_14)
if !tmpl_login_vars.CurrentUser.IsSuperMod {
w.Write(header_15)
}
w.Write(header_16)
w.Write(menu_0)
w.Write(menu_1)
w.Write([]byte(tmpl_login_vars.Header.Site.ShortName))
w.Write(menu_2)
if tmpl_login_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_login_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_login_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_17)
if tmpl_login_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_18)
}
w.Write(header_19)
if len(tmpl_login_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_login_vars.Header.NoticeList {
w.Write(header_20)
w.Write([]byte(item))
w.Write(header_21)
}
}
w.Write(login_0)
w.Write(phrases[0])
w.Write(login_1)
w.Write(phrases[1])
w.Write(login_2)
w.Write(phrases[2])
w.Write(login_3)
w.Write(phrases[3])
w.Write(login_4)
w.Write(phrases[4])
w.Write(login_5)
w.Write(phrases[5])
w.Write(login_6)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_login_vars.Header)))
w.Write(footer_1)
if len(tmpl_login_vars.Header.Themes) != 0 {
for _, item := range tmpl_login_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_2)
w.Write([]byte(item.Name))
w.Write(footer_3)
if tmpl_login_vars.Header.Theme.Name == item.Name {
w.Write(footer_4)
}
w.Write(footer_5)
w.Write([]byte(item.FriendlyName))
w.Write(footer_6)
}
}
}
w.Write(footer_7)
w.Write([]byte(common.BuildWidget("rightSidebar",tmpl_login_vars.Header)))
w.Write(footer_8)
return nil
}

View File

@ -3,9 +3,11 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "strconv"
import "net/http" import "net/http"
import "./common" import "./common"
import "strconv"
var profile_Tmpl_Phrase_ID int
// nolint // nolint
func init() { func init() {

131
template_register.go Normal file
View File

@ -0,0 +1,131 @@
// +build !no_templategen
// Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "net/http"
import "./common"
var register_Tmpl_Phrase_ID int
// nolint
func init() {
common.Template_register_handle = Template_register
common.Ctemplates = append(common.Ctemplates,"register")
common.TmplPtrMap["register"] = &common.Template_register_handle
common.TmplPtrMap["o_register"] = Template_register
register_Tmpl_Phrase_ID = common.RegisterTmplPhraseNames([]string{
"register_head",
"register_account_name",
"register_account_name",
"register_account_email",
"register_account_password",
"register_account_confirm_password",
"register_submit_button",
})
}
// nolint
func Template_register(tmpl_register_vars common.Page, w http.ResponseWriter) error {
var phrases = common.GetTmplPhrasesBytes(register_Tmpl_Phrase_ID)
w.Write(header_0)
w.Write([]byte(tmpl_register_vars.Title))
w.Write(header_1)
w.Write([]byte(tmpl_register_vars.Header.Site.Name))
w.Write(header_2)
w.Write([]byte(tmpl_register_vars.Header.Theme.Name))
w.Write(header_3)
if len(tmpl_register_vars.Header.Stylesheets) != 0 {
for _, item := range tmpl_register_vars.Header.Stylesheets {
w.Write(header_4)
w.Write([]byte(item))
w.Write(header_5)
}
}
w.Write(header_6)
if len(tmpl_register_vars.Header.Scripts) != 0 {
for _, item := range tmpl_register_vars.Header.Scripts {
w.Write(header_7)
w.Write([]byte(item))
w.Write(header_8)
}
}
w.Write(header_9)
w.Write([]byte(tmpl_register_vars.CurrentUser.Session))
w.Write(header_10)
w.Write([]byte(tmpl_register_vars.Header.Site.URL))
w.Write(header_11)
if tmpl_register_vars.Header.MetaDesc != "" {
w.Write(header_12)
w.Write([]byte(tmpl_register_vars.Header.MetaDesc))
w.Write(header_13)
}
w.Write(header_14)
if !tmpl_register_vars.CurrentUser.IsSuperMod {
w.Write(header_15)
}
w.Write(header_16)
w.Write(menu_0)
w.Write(menu_1)
w.Write([]byte(tmpl_register_vars.Header.Site.ShortName))
w.Write(menu_2)
if tmpl_register_vars.CurrentUser.Loggedin {
w.Write(menu_3)
w.Write([]byte(tmpl_register_vars.CurrentUser.Link))
w.Write(menu_4)
w.Write([]byte(tmpl_register_vars.CurrentUser.Session))
w.Write(menu_5)
} else {
w.Write(menu_6)
}
w.Write(menu_7)
w.Write(header_17)
if tmpl_register_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_18)
}
w.Write(header_19)
if len(tmpl_register_vars.Header.NoticeList) != 0 {
for _, item := range tmpl_register_vars.Header.NoticeList {
w.Write(header_20)
w.Write([]byte(item))
w.Write(header_21)
}
}
w.Write(register_0)
w.Write(phrases[0])
w.Write(register_1)
w.Write(phrases[1])
w.Write(register_2)
w.Write(phrases[2])
w.Write(register_3)
w.Write(phrases[3])
w.Write(register_4)
w.Write(phrases[4])
w.Write(register_5)
w.Write(phrases[5])
w.Write(register_6)
w.Write(phrases[6])
w.Write(register_7)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_register_vars.Header)))
w.Write(footer_1)
if len(tmpl_register_vars.Header.Themes) != 0 {
for _, item := range tmpl_register_vars.Header.Themes {
if !item.HideFromThemes {
w.Write(footer_2)
w.Write([]byte(item.Name))
w.Write(footer_3)
if tmpl_register_vars.Header.Theme.Name == item.Name {
w.Write(footer_4)
}
w.Write(footer_5)
w.Write([]byte(item.FriendlyName))
w.Write(footer_6)
}
}
}
w.Write(footer_7)
w.Write([]byte(common.BuildWidget("rightSidebar",tmpl_register_vars.Header)))
w.Write(footer_8)
return nil
}

View File

@ -7,6 +7,8 @@ import "net/http"
import "./common" import "./common"
import "strconv" import "strconv"
var topic_Tmpl_Phrase_ID int
// nolint // nolint
func init() { func init() {
common.Template_topic_handle = Template_topic common.Template_topic_handle = Template_topic

View File

@ -7,6 +7,8 @@ import "net/http"
import "./common" import "./common"
import "strconv" import "strconv"
var topic_alt_Tmpl_Phrase_ID int
// nolint // nolint
func init() { func init() {
common.Template_topic_alt_handle = Template_topic_alt common.Template_topic_alt_handle = Template_topic_alt

View File

@ -7,6 +7,8 @@ import "net/http"
import "./common" import "./common"
import "strconv" import "strconv"
var topics_Tmpl_Phrase_ID int
// nolint // nolint
func init() { func init() {
common.Template_topics_handle = Template_topics common.Template_topics_handle = Template_topics
@ -86,165 +88,166 @@ w.Write(topics_1)
} }
w.Write(topics_2) w.Write(topics_2)
if tmpl_topics_vars.CurrentUser.ID != 0 { if tmpl_topics_vars.CurrentUser.ID != 0 {
if len(tmpl_topics_vars.ForumList) != 0 {
w.Write(topics_3) w.Write(topics_3)
if len(tmpl_topics_vars.ForumList) != 0 {
w.Write(topics_4) w.Write(topics_4)
} else {
w.Write(topics_5) w.Write(topics_5)
} } else {
w.Write(topics_6) w.Write(topics_6)
} }
w.Write(topics_7) w.Write(topics_7)
if tmpl_topics_vars.CurrentUser.ID != 0 { }
w.Write(topics_8) w.Write(topics_8)
if len(tmpl_topics_vars.ForumList) != 0 { if tmpl_topics_vars.CurrentUser.ID != 0 {
w.Write(topics_9) w.Write(topics_9)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session)) if len(tmpl_topics_vars.ForumList) != 0 {
w.Write(topics_10) w.Write(topics_10)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
w.Write(topics_11)
if len(tmpl_topics_vars.ForumList) != 0 { if len(tmpl_topics_vars.ForumList) != 0 {
for _, item := range tmpl_topics_vars.ForumList { for _, item := range tmpl_topics_vars.ForumList {
w.Write(topics_11)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_12) w.Write(topics_12)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_13) w.Write(topics_13)
w.Write([]byte(item.Name)) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_14) w.Write(topics_14)
} w.Write([]byte(item.Name))
}
w.Write(topics_15) w.Write(topics_15)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session)) }
}
w.Write(topics_16) w.Write(topics_16)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Avatar)) w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
w.Write(topics_17) w.Write(topics_17)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Avatar))
w.Write(topics_18)
if len(tmpl_topics_vars.ForumList) != 0 { if len(tmpl_topics_vars.ForumList) != 0 {
for _, item := range tmpl_topics_vars.ForumList { for _, item := range tmpl_topics_vars.ForumList {
w.Write(topics_18)
if item.ID == tmpl_topics_vars.DefaultForum {
w.Write(topics_19) w.Write(topics_19)
} if item.ID == tmpl_topics_vars.DefaultForum {
w.Write(topics_20) w.Write(topics_20)
w.Write([]byte(strconv.Itoa(item.ID))) }
w.Write(topics_21) w.Write(topics_21)
w.Write([]byte(item.Name)) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_22) w.Write(topics_22)
} w.Write([]byte(item.Name))
}
w.Write(topics_23) w.Write(topics_23)
if tmpl_topics_vars.CurrentUser.Perms.UploadFiles {
w.Write(topics_24)
} }
}
w.Write(topics_24)
if tmpl_topics_vars.CurrentUser.Perms.UploadFiles {
w.Write(topics_25) w.Write(topics_25)
} }
}
w.Write(topics_26) w.Write(topics_26)
}
}
w.Write(topics_27)
if len(tmpl_topics_vars.TopicList) != 0 { if len(tmpl_topics_vars.TopicList) != 0 {
for _, item := range tmpl_topics_vars.TopicList { for _, item := range tmpl_topics_vars.TopicList {
w.Write(topics_27)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_28) w.Write(topics_28)
if item.Sticky { w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_29) w.Write(topics_29)
if item.Sticky {
w.Write(topics_30)
} else { } else {
if item.IsClosed { if item.IsClosed {
w.Write(topics_30)
}
}
w.Write(topics_31) w.Write(topics_31)
w.Write([]byte(item.Creator.Link)) }
}
w.Write(topics_32) w.Write(topics_32)
w.Write([]byte(item.Creator.Avatar)) w.Write([]byte(item.Creator.Link))
w.Write(topics_33) w.Write(topics_33)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Avatar))
w.Write(topics_34) w.Write(topics_34)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(topics_35) w.Write(topics_35)
w.Write([]byte(item.Link))
w.Write(topics_36)
w.Write([]byte(item.Title))
w.Write(topics_37)
if item.ForumName != "" {
w.Write(topics_38)
w.Write([]byte(item.ForumLink))
w.Write(topics_39)
w.Write([]byte(item.ForumName))
w.Write(topics_40)
}
w.Write(topics_41)
w.Write([]byte(item.Creator.Link))
w.Write(topics_42)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(topics_43) w.Write(topics_36)
if item.IsClosed { w.Write([]byte(item.Link))
w.Write(topics_44) w.Write(topics_37)
w.Write([]byte(item.Title))
w.Write(topics_38)
if item.ForumName != "" {
w.Write(topics_39)
w.Write([]byte(item.ForumLink))
w.Write(topics_40)
w.Write([]byte(item.ForumName))
w.Write(topics_41)
} }
if item.Sticky { w.Write(topics_42)
w.Write([]byte(item.Creator.Link))
w.Write(topics_43)
w.Write([]byte(item.Creator.Name))
w.Write(topics_44)
if item.IsClosed {
w.Write(topics_45) w.Write(topics_45)
} }
w.Write(topics_46)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(topics_47)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topics_48)
if item.Sticky { if item.Sticky {
w.Write(topics_46)
}
w.Write(topics_47)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(topics_48)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topics_49) w.Write(topics_49)
if item.Sticky {
w.Write(topics_50)
} else { } else {
if item.IsClosed { if item.IsClosed {
w.Write(topics_50)
}
}
w.Write(topics_51) w.Write(topics_51)
w.Write([]byte(item.LastUser.Link)) }
}
w.Write(topics_52) w.Write(topics_52)
w.Write([]byte(item.LastUser.Avatar)) w.Write([]byte(item.LastUser.Link))
w.Write(topics_53) w.Write(topics_53)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_54) w.Write(topics_54)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Name))
w.Write(topics_55) w.Write(topics_55)
w.Write([]byte(item.LastUser.Link))
w.Write(topics_56)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Name))
w.Write(topics_56)
w.Write([]byte(item.LastUser.Link))
w.Write(topics_57) w.Write(topics_57)
w.Write([]byte(item.RelativeLastReplyAt)) w.Write([]byte(item.LastUser.Name))
w.Write(topics_58) w.Write(topics_58)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write(topics_59)
} }
} else { } else {
w.Write(topics_59)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_60) w.Write(topics_60)
} if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_61) w.Write(topics_61)
} }
w.Write(topics_62) w.Write(topics_62)
if tmpl_topics_vars.LastPage > 1 { }
w.Write(topics_63) w.Write(topics_63)
if tmpl_topics_vars.Page > 1 { if tmpl_topics_vars.LastPage > 1 {
w.Write(topics_64) w.Write(topics_64)
w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page - 1))) if tmpl_topics_vars.Page > 1 {
w.Write(topics_65) w.Write(topics_65)
w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page - 1))) w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page - 1)))
w.Write(topics_66) w.Write(topics_66)
w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page - 1)))
w.Write(topics_67)
} }
if len(tmpl_topics_vars.PageList) != 0 { if len(tmpl_topics_vars.PageList) != 0 {
for _, item := range tmpl_topics_vars.PageList { for _, item := range tmpl_topics_vars.PageList {
w.Write(topics_67)
w.Write([]byte(strconv.Itoa(item)))
w.Write(topics_68) w.Write(topics_68)
w.Write([]byte(strconv.Itoa(item))) w.Write([]byte(strconv.Itoa(item)))
w.Write(topics_69) w.Write(topics_69)
w.Write([]byte(strconv.Itoa(item)))
w.Write(topics_70)
} }
} }
if tmpl_topics_vars.LastPage != tmpl_topics_vars.Page { if tmpl_topics_vars.LastPage != tmpl_topics_vars.Page {
w.Write(topics_70)
w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page + 1)))
w.Write(topics_71) w.Write(topics_71)
w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page + 1))) w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page + 1)))
w.Write(topics_72) w.Write(topics_72)
} w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page + 1)))
w.Write(topics_73) w.Write(topics_73)
} }
w.Write(topics_74) w.Write(topics_74)
}
w.Write(topics_75)
w.Write(footer_0) w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header))) w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header)))
w.Write(footer_1) w.Write(footer_1)

View File

@ -1,5 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack account"> <div id="account_edit_password" class="colstack account">
{{template "account_menu.html" . }} {{template "account_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">

View File

@ -1,11 +1,11 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack account"> <div id="account_edit_avatar" class="colstack account">
{{template "account_menu.html" . }} {{template "account_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Edit Avatar</h1></div> <div class="rowitem"><h1>Edit Avatar</h1></div>
</div> </div>
<div class="colstack_item"> <div class="colstack_item avatar_box">
<div class="rowitem"><img src="{{.CurrentUser.Avatar}}" height="128px" max-width="128px" /></div> <div class="rowitem"><img src="{{.CurrentUser.Avatar}}" height="128px" max-width="128px" /></div>
</div> </div>
<div class="colstack_item the_form"> <div class="colstack_item the_form">

View File

@ -1,5 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack account account_emails"> <div id="account_edit_emails" class="colstack account account_emails">
{{template "account_menu.html" . }} {{template "account_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">

View File

@ -1,5 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack account"> <div id="account_edit_username" class="colstack account">
{{template "account_menu.html" . }} {{template "account_menu.html" . }}
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<main> <main>
<div class="rowblock rowhead"> <div class="rowblock rowhead">
<div class="rowitem"><h1>An error has occured</h1></div> <div class="rowitem"><h1>{{lang "error_head"}}</h1></div>
</div> </div>
<div class="rowblock"> <div class="rowblock">
<div class="rowitem passive rowmsg">{{.Something}}</div> <div class="rowitem passive rowmsg">{{.Something}}</div>

View File

@ -3,20 +3,22 @@
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/forum/{{.Forum.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}} {{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/forum/{{.Forum.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}} {{if ne .LastPage .Page}}<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<main itemscope itemtype="http://schema.org/ItemList"> <main id="forumItemList" itemscope itemtype="http://schema.org/ItemList">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block"> <div id="forum_head_block" class="rowblock rowhead topic_list_title_block{{if ne .CurrentUser.ID 0}} has_opt{{end}}">
<div class="rowitem forum_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}"> <div class="rowitem forum_title">
<h1 itemprop="name">{{.Title}}</h1> <h1 itemprop="name">{{.Title}}</h1>
</div> </div>
{{if ne .CurrentUser.ID 0}} {{if ne .CurrentUser.ID 0}}
{{if .CurrentUser.Perms.CreateTopic}} <div class="optbox">
<div class="pre_opt auto_hide"></div> {{if .CurrentUser.Perms.CreateTopic}}
<div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/{{.Forum.ID}}"></a></div> <div class="pre_opt auto_hide"></div>
{{/** TODO: Add a permissions check for this **/}} <div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/{{.Forum.ID}}"></a></div>
<div class="opt mod_opt" title="Moderate"> {{/** TODO: Add a permissions check for this **/}}
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a> <div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
</div>
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic in this forum"><a></a></div>{{end}}
</div> </div>
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic in this forum"><a></a></div>{{end}}
<div style="clear: both;"></div> <div style="clear: both;"></div>
{{end}} {{end}}
</div> </div>

View File

@ -1,5 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<main itemscope itemtype="http://schema.org/ItemList"> <main id="forumsItemList" itemscope itemtype="http://schema.org/ItemList">
<div class="rowblock opthead"> <div class="rowblock opthead">
<div class="rowitem"><h1 itemprop="name">Forums</h1></div> <div class="rowitem"><h1 itemprop="name">Forums</h1></div>

View File

@ -24,4 +24,6 @@
<div class="container"> <div class="container">
{{template "menu.html" .}} {{template "menu.html" .}}
<div id="back"><div id="main" {{if .Header.Widgets.RightSidebar}}class="shrink_main"{{end}}> <div id="back"><div id="main" {{if .Header.Widgets.RightSidebar}}class="shrink_main"{{end}}>
{{range .Header.NoticeList}}<div class="alert">{{.}}</div>{{end}} {{range .Header.NoticeList}}<div class="alertbox">
<div class="alert">{{.}}</div>
</div>{{end}}

View File

@ -2,7 +2,7 @@
<main id="ip_search_container"> <main id="ip_search_container">
<div class="rowblock rowhead"> <div class="rowblock rowhead">
<div class="rowitem"> <div class="rowitem">
<h1>IP Search</h1> <h1>{{lang "ip_search_head"}}</h1>
</div> </div>
</div> </div>
<form action="/users/ips/" method="get" id="ip-search-form"></form> <form action="/users/ips/" method="get" id="ip-search-form"></form>
@ -18,7 +18,7 @@
<img src="{{.Avatar}}" class="bgsub" alt="{{.Name}}'s Avatar" /> <img src="{{.Avatar}}" class="bgsub" alt="{{.Name}}'s Avatar" />
<a class="rowTitle" href="{{.Link}}">{{.Name}}</a> <a class="rowTitle" href="{{.Link}}">{{.Name}}</a>
</div> </div>
{{else}}<div class="rowitem">No users found.</div>{{end}} {{else}}<div class="rowitem rowmsg">{{lang "ip_search_no_users"}}</div>{{end}}
</div> </div>
{{end}} {{end}}
</main> </main>

View File

@ -1,21 +1,21 @@
{{template "header.html" . }} {{template "header.html" . }}
<main id="login_page"> <main id="login_page">
<div class="rowblock rowhead"> <div class="rowblock rowhead">
<div class="rowitem"><h1>Login</h1></div> <div class="rowitem"><h1>{{lang "login_head"}}</h1></div>
</div> </div>
<div class="rowblock"> <div class="rowblock">
<form action="/accounts/login/submit/" method="post"> <form action="/accounts/login/submit/" method="post">
<div class="formrow login_name_row"> <div class="formrow login_name_row">
<div class="formitem formlabel"><a>Account Name</a></div> <div class="formitem formlabel"><a>{{lang "login_account_name"}}</a></div>
<div class="formitem"><input name="username" type="text" placeholder="Account Name" required /></div> <div class="formitem"><input name="username" type="text" placeholder="{{lang "login_account_name"}}" required /></div>
</div> </div>
<div class="formrow login_password_row"> <div class="formrow login_password_row">
<div class="formitem formlabel"><a>Password</a></div> <div class="formitem formlabel"><a>{{lang "login_account_password"}}</a></div>
<div class="formitem"><input name="password" type="password" autocomplete="current-password" placeholder="*****" required /></div> <div class="formitem"><input name="password" type="password" autocomplete="current-password" placeholder="*****" required /></div>
</div> </div>
<div class="formrow login_button_row"> <div class="formrow login_button_row">
<div class="formitem"><button name="login-button" class="formbutton">Login</button></div> <div class="formitem"><button name="login-button" class="formbutton">{{lang "login_submit_button"}}</button></div>
<div class="formitem dont_have_account">Don't have an account?</div> <div class="formitem dont_have_account">{{lang "login_no_account"}}</div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -50,6 +50,9 @@
<div class="rowitem passive submenu"> <div class="rowitem passive submenu">
<a href="/panel/analytics/systems/">Systems</a> <a href="/panel/analytics/systems/">Systems</a>
</div> </div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/langs/">Languages</a>
</div>
<div class="rowitem passive submenu"> <div class="rowitem passive submenu">
<a href="/panel/analytics/referrers/">Referrers</a> <a href="/panel/analytics/referrers/">Referrers</a>
</div> </div>

View File

@ -0,0 +1,34 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/lang/{{.Agent}}" method="get">
<div class="colstack_item colstack_head">
<div class="rowitem">
<a>{{.FriendlyAgent}} Views</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option>
<option val="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
<option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>
</select>
</div>
</div>
</form>
<div id="panel_analytics_langs" class="colstack_graph_holder">
<div class="ct_chart"></div>
</div>
</main>
</div>
<script>
let rawLabels = [{{range .PrimaryGraph.Labels}}
{{.}},{{end}}
];
let seriesData = [{{range .PrimaryGraph.Series}}
{{.}},{{end}}
];
buildStatsChart(rawLabels, seriesData, "{{.TimeRange}}");
</script>
{{template "footer.html" . }}

View File

@ -0,0 +1,30 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/langs/" method="get">
<div class="colstack_item colstack_head">
<div class="rowitem">
<a>Languages</a>
<select class="timeRangeSelector to_right" name="timeRange">
<option val="one-month"{{if eq .TimeRange "one-month"}} selected{{end}}>1 month</option>
<option val="one-week"{{if eq .TimeRange "one-week"}} selected{{end}}>1 week</option>
<option val="two-days"{{if eq .TimeRange "two-days"}} selected{{end}}>2 days</option>
<option val="one-day"{{if eq .TimeRange "one-day"}} selected{{end}}>1 day</option>
<option val="twelve-hours"{{if eq .TimeRange "twelve-hours"}} selected{{end}}>12 hours</option>
<option val="six-hours"{{if eq .TimeRange "six-hours"}} selected{{end}}>6 hours</option>
</select>
</div>
</div>
</form>
<div id="panel_analytics_langs" class="colstack_item rowlist">
{{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent">
<a href="/panel/analytics/lang/{{.Agent}}" class="panel_upshift">{{.FriendlyAgent}}</a>
<span class="panel_compacttext to_right">{{.Count}} views</span>
</div>
{{else}}<div class="rowitem passive rowmsg">No language could be found in the selected time range</div>{{end}}
</div>
</main>
</div>
{{template "footer.html" . }}

View File

@ -1,28 +1,28 @@
{{template "header.html" . }} {{template "header.html" . }}
<main id="register_page"> <main id="register_page">
<div class="rowblock rowhead"> <div class="rowblock rowhead">
<div class="rowitem"><h1>Create Account</h1></div> <div class="rowitem"><h1>{{lang "register_head"}}</h1></div>
</div> </div>
<div class="rowblock"> <div class="rowblock">
<form action="/accounts/create/submit/" method="post"> <form action="/accounts/create/submit/" method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a id="username_label">Account Name</a></div> <div class="formitem formlabel"><a id="username_label">{{lang "register_account_name"}}</a></div>
<div class="formitem"><input name="username" type="text" placeholder="Account Name" aria-labelledby="username_label" required /></div> <div class="formitem"><input name="username" type="text" placeholder="{{lang "register_account_name"}}" aria-labelledby="username_label" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a id="email_label">Email</a></div> <div class="formitem formlabel"><a id="email_label">{{lang "register_account_email"}}</a></div>
<div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" aria-labelledby="email_label" required /></div> <div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" aria-labelledby="email_label" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a id="password_label">Password</a></div> <div class="formitem formlabel"><a id="password_label">{{lang "register_account_password"}}</a></div>
<div class="formitem"><input name="password" type="password" autocomplete="new-password" placeholder="*****" aria-labelledby="password_label" required /></div> <div class="formitem"><input name="password" type="password" autocomplete="new-password" placeholder="*****" aria-labelledby="password_label" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a id="confirm_password_label">Confirm Password</a></div> <div class="formitem formlabel"><a id="confirm_password_label">{{lang "register_account_confirm_password"}}</a></div>
<div class="formitem"><input name="confirm_password" type="password" placeholder="*****" aria-labelledby="confirm_password_label" required /></div> <div class="formitem"><input name="confirm_password" type="password" placeholder="*****" aria-labelledby="confirm_password_label" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="register-button" class="formbutton">Create Account</button></div> <div class="formitem"><button name="register-button" class="formbutton">{{lang "register_submit_button"}}</button></div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -1,17 +1,19 @@
{{template "header.html" . }} {{template "header.html" . }}
<main itemscope itemtype="http://schema.org/ItemList"> <main id="topicsItemList" itemscope itemtype="http://schema.org/ItemList">
<div class="rowblock rowhead topic_list_title_block"> <div class="rowblock rowhead topic_list_title_block{{if ne .CurrentUser.ID 0}} has_opt{{end}}">
<div class="rowitem topic_list_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}"><h1 itemprop="name">All Topics</h1></div> <div class="rowitem topic_list_title"><h1 itemprop="name">All Topics</h1></div>
{{if ne .CurrentUser.ID 0}} {{if ne .CurrentUser.ID 0}}
<div class="optbox">
{{if .ForumList}} {{if .ForumList}}
<div class="pre_opt auto_hide"></div> <div class="pre_opt auto_hide"></div>
<div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/"></a></div> <div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/"></a></div>
{{/** TODO: Add a permissions check for this **/}} {{/** TODO: Add a permissions check for this **/}}
<div class="opt mod_opt" title="Moderate"> <div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a> <a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
</div>
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic anywhere"><a></a></div>{{end}}
</div> </div>
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic anywhere"><a></a></div>{{end}}
<div style="clear: both;"></div> <div style="clear: both;"></div>
{{end}} {{end}}
</div> </div>

View File

@ -1132,11 +1132,22 @@ textarea {
margin-right: 12px; margin-right: 12px;
} }
.colstack_right .colstack_item:not(.rowlist):not(#profile_comments), .colstack_right .colstack_item:not(.rowlist):not(#profile_comments),
#profile_comments .comment { #profile_comments .comment, .alert {
border: 1px solid var(--element-border-color); border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color); border-bottom: 2px solid var(--element-border-color);
background-color: var(--element-background-color); background-color: var(--element-background-color);
} }
.alert {
padding: 12px;
margin-top: -3px;
margin-bottom: 8px;
margin-left: 12px;
margin-right: 12px;
}
.colstack_right .alert {
margin-left: 16px;
margin-right: 0px;
}
.colstack_right .colstack_item, .colstack_right .colstack_grid { .colstack_right .colstack_item, .colstack_right .colstack_grid {
margin-left: 16px; margin-left: 16px;
} }
@ -1268,6 +1279,10 @@ textarea {
margin-left: auto; margin-left: auto;
} }
#account_edit_avatar .avatar_box {
margin-bottom: 10px;
}
#create_topic_page .close_form, #create_topic_page .formlabel, #login_page .formlabel, #register_page .formlabel { #create_topic_page .close_form, #create_topic_page .formlabel, #login_page .formlabel, #register_page .formlabel {
display: none; display: none;
} }

View File

@ -1,3 +1,5 @@
"use strict"
$(document).ready(function(){ $(document).ready(function(){
// Is there we way we can append instead? Maybe, an editor plugin? // Is there we way we can append instead? Maybe, an editor plugin?
attachItemCallback = function(attachItem) { attachItemCallback = function(attachItem) {
@ -33,7 +35,6 @@ $(document).ready(function(){
// TODO: Refactor this to use `each` less // TODO: Refactor this to use `each` less
$('.button_menu').click(function(){ $('.button_menu').click(function(){
console.log(".button_menu"); console.log(".button_menu");
// The outer container // The outer container
let buttonPane = newElement("div","button_menu_pane"); let buttonPane = newElement("div","button_menu_pane");
let postItem = $(this).parents('.post_item'); let postItem = $(this).parents('.post_item');
@ -86,6 +87,14 @@ $(document).ready(function(){
document.getElementById("back").appendChild(buttonPane); document.getElementById("back").appendChild(buttonPane);
}); });
// Move the alerts under the first header
let colSel = $(".colstack_right .colstack_head:first");
if(colSel.length > 0) {
$('.alert').insertAfter(colSel);
} else {
$('.alert').insertAfter(".rowhead:first");
}
}); });
function newElement(etype, eclass) { function newElement(etype, eclass) {

View File

@ -160,11 +160,20 @@ a {
color: var(--main-text-color); color: var(--main-text-color);
} }
.alertbox {
display: flex;
}
.alert { .alert {
padding-bottom: 12px; padding-bottom: 12px;
background-color: var(--main-block-color); background-color: var(--main-block-color);
border-left: 4px solid hsl(21, 100%, 50%);
padding: 12px; padding: 12px;
display: block; display: block;
margin-top: 8px;
margin-bottom: -3px;
margin-left: 8px;
margin-right: 8px;
width: 100%;
} }
.rowblock { .rowblock {
@ -652,25 +661,25 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.rowhead:not(.has_opt) .rowitem, .opthead .rowitem, .colstack_head .rowitem {
.has_opt {
margin-right: 0px;
display: inline-block;
float: left;
padding-right: 0px;
}
.rowhead .rowitem:not(.has_opt), .opthead .rowitem, .colstack_head .rowitem {
width: 100%; width: 100%;
} }
.opt { .optbox {
float: left; display: flex;
margin-top: 8px;
height: 30.4px;
padding-left: 5px; padding-left: 5px;
padding-top: 10.5px;
margin-top: 7px;
width: 100%; width: 100%;
background-color: var(--main-block-color); background-color: var(--main-block-color);
padding-top: 11px; }
.has_opt .rowitem {
margin-right: 0px;
display: inline-block;
padding-right: 0px;
margin-top: 7px;
padding-left: 12px;
padding-top: 12px;
} }
.opt a { .opt a {
font-size: 11px; font-size: 11px;
@ -702,7 +711,7 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
overflow: hidden; overflow: hidden;
} }
.topic_left.topic_sticky .topic_inner_left { .topic_left.topic_sticky .topic_inner_left {
border-top: 4px solid hsl(51, 100%, 50%); border-top: 4px solid hsl(41, 100%, 50%);
padding-left: 8px; padding-left: 8px;
padding-top: 8px; padding-top: 8px;
margin-top: 0px; margin-top: 0px;

View File

@ -319,17 +319,23 @@ li a {
color: hsl(0,0%,40%); color: hsl(0,0%,40%);
} }
.opthead { display: none; } .opthead {
.rowitem.has_opt { display: none;
float: left; }
.has_opt {
display: flex;
}
.has_opt .rowitem {
display: flex;
width: calc(100% - 50px); width: calc(100% - 50px);
border-right: 1px solid #ccc; border-right: 1px solid #ccc;
border-bottom: none; border-bottom: none;
} }
.optbox {
margin-left: auto;
}
.opt { .opt {
float: left;
font-size: 32px; font-size: 32px;
height: 100%;
background-color: hsl(0, 0%, 99%); background-color: hsl(0, 0%, 99%);
width: 50px; width: 50px;
text-align: center; text-align: center;

View File

@ -296,17 +296,21 @@ li a {
.opthead { .opthead {
display: none; display: none;
} }
.topic_list_title_block {
.rowitem.has_opt { display: flex;
float: left; }
width: calc(100% - 50px); .has_opt {
border-bottom: 1px solid hsl(0, 0%, 80%);
}
.has_opt .rowitem {
border-right: 1px solid hsl(0, 0%, 80%); border-right: 1px solid hsl(0, 0%, 80%);
border-bottom: none; border-bottom: none;
} }
.optbox {
margin-left: auto;
}
.opt { .opt {
float: left;
font-size: 32px; font-size: 32px;
height: 100%;
background-color: white; background-color: white;
width: 50px; width: 50px;
text-align: center; text-align: center;
@ -324,6 +328,13 @@ li a {
.locked_opt:before { .locked_opt:before {
content: '🔒︎'; content: '🔒︎';
} }
/*.mod_opt a.moderate_link:before {
content: '🔨︎';
}
.mod_opt, .mod_opt a {
color: rgb(120,120,120);
text-decoration: none;
}*/
.to_right { .to_right {
margin-left: auto; margin-left: auto;