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?
type DefaultRouteViewCounter struct {
routeBuckets []*RWMutexCounterBucket //[RouteID]count
insert *sql.Stmt
buckets []*RWMutexCounterBucket //[RouteID]count
insert *sql.Stmt
}
func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) {
@ -19,8 +19,8 @@ func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) {
routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}
}
counter := &DefaultRouteViewCounter{
routeBuckets: routeBuckets,
insert: acc.Insert("viewchunks").Columns("count, createdAt, route").Fields("?,UTC_TIMESTAMP(),?").Prepare(),
buckets: routeBuckets,
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.AddScheduledSecondTask(counter.Tick)
@ -29,7 +29,7 @@ func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) {
}
func (counter *DefaultRouteViewCounter) Tick() error {
for routeID, routeBucket := range counter.routeBuckets {
for routeID, routeBucket := range counter.buckets {
var count int
routeBucket.RLock()
count = routeBucket.counter
@ -56,11 +56,11 @@ func (counter *DefaultRouteViewCounter) insertChunk(count int, route int) error
func (counter *DefaultRouteViewCounter) Bump(route int) {
// TODO: Test this check
common.DebugDetail("counter.routeBuckets[", route, "]: ", counter.routeBuckets[route])
if len(counter.routeBuckets) <= route || route < 0 {
common.DebugDetail("counter.buckets[", route, "]: ", counter.buckets[route])
if len(counter.buckets) <= route || route < 0 {
return
}
counter.routeBuckets[route].Lock()
counter.routeBuckets[route].counter++
counter.routeBuckets[route].Unlock()
counter.buckets[route].Lock()
counter.buckets[route].counter++
counter.buckets[route].Unlock()
}

View File

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

View File

@ -1,12 +1,12 @@
// +build no_ws
package main
package common
import "errors"
import "net/http"
// 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 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
}
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
UserAgents map[string]string
OperatingSystems map[string]string
HumanLanguages map[string]string
Errors map[string]map[string]string // map[category]map[name]value
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
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 {
log.Print("Loading the language packs")
@ -75,6 +80,15 @@ func InitPhrases() error {
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")
langPacks.Store(langPack.Name, &langPack)
langPackCount++
@ -170,6 +184,14 @@ func GetOSPhrase(name string) (string, bool) {
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?
func GetErrorPhrase(category string, name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).Errors[category][name]
@ -187,6 +209,14 @@ func GetTitlePhrase(name string) string {
return res
}
func GetTmplPhrase(name string) string {
res, ok := currentLangPack.Load().(*LanguagePack).TmplPhrases[name]
if !ok {
return getPhrasePlaceholder()
}
return res
}
func getPhrasePlaceholder() string {
return "{name}"
}
@ -213,3 +243,14 @@ func ChangeLanguagePack(name string) (exists bool) {
currentLangPack.Store(pack)
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 {
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 {
rlist := theme.Resources

View File

@ -22,6 +22,7 @@ type CTmpl struct {
Imports []string
}
// TODO: Stop duplicating these bits of code
// nolint
func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) error {
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)
}
// 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?
func compileTemplates() error {
var config tmpl.CTemplateConfig
config.Minify = Config.MinifyTemplates
config.SuperDebug = Dev.TemplateDebug
var c tmpl.CTemplateSet
c.Minify(Config.MinifyTemplates)
c.SuperDebug(Dev.TemplateDebug)
c.SetConfig(config)
// Schemas to train the template compiler on what to expect
// 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?
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}
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}
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, BuildAvatar(2, ""), "", "", "", "", 42, 900, "::1", 0}
headerVars := &HeaderVars{
Site: Site,
Settings: SettingBox.Load().(SettingMap),
@ -168,12 +208,37 @@ func compileTemplates() error {
return err
}
// Let plugins register their own templates
if Dev.DebugMode {
log.Print("Registering the templates for the plugins")
loginPage := Page{"Login Page", user, headerVars, tList, nil}
loginTmpl, err := c.Compile("login.html", "templates/", "common.Page", loginPage, varList)
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 {
tmplItem := tmplfunc(user, headerVars)
varList = make(map[string]tmpl.VarItem)
@ -191,6 +256,10 @@ func compileTemplates() error {
go writeTemplate("forums", forumsTmpl)
go writeTemplate("topics", topicsTmpl)
go writeTemplate("forum", forumTmpl)
go writeTemplate("login", loginTmpl)
go writeTemplate("register", registerTmpl)
go writeTemplate("ip_search", ipSearchTmpl)
go writeTemplate("error", errorTmpl)
go func() {
err := writeFile("./template_list.go", "package main\n\n// nolint\n"+c.FragOut)
if err != nil {
@ -279,10 +348,16 @@ func InitTemplates() error {
return template.HTML(BuildWidget(dock.(string), headerVarInt.(*HeaderVars)))
}
// The interpreted templates...
if Dev.DebugMode {
log.Print("Loading the template files...")
fmap["lang"] = func(phraseNameInt interface{}) interface{} {
phraseName, ok := phraseNameInt.(string)
if !ok {
panic("phraseNameInt is not a string")
}
return GetTmplPhrase(phraseName)
}
// The interpreted templates...
DebugLog("Loading the template files...")
Templates.Funcs(fmap)
template.Must(Templates.ParseGlob("templates/*"))
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 (
"bytes"
"fmt"
"log"
"strconv"
"strings"
//"regexp"
"io/ioutil"
"log"
"path/filepath"
"reflect"
"strconv"
"strings"
"text/template/parse"
)
// TODO: Turn this file into a library
var textOverlapList = make(map[string]int)
// nolint
type VarItem struct {
Name string
Destination string
Type string
}
type VarItemReflect struct {
Name string
Destination string
Value reflect.Value
}
type CTemplateConfig struct {
Minify bool
Debug bool
SuperDebug bool
SkipHandles bool
}
// nolint
type CTemplateSet struct {
tlist map[string]*parse.Tree
dir string
templateList map[string]*parse.Tree
fileDir string
funcMap map[string]interface{}
importMap map[string]string
Fragments map[string]int
@ -39,43 +47,31 @@ type CTemplateSet struct {
localVars map[string]map[string]VarItemReflect
hasDispInt bool
localDispStructIndex int
langIndexToName []string
stats map[string]int
pVarList string
pVarPosition int
previousNode parse.NodeType
currentNode parse.NodeType
nextNode parse.NodeType
//tempVars map[string]string
doImports bool
minify bool
debug bool
superDebug bool
skipHandles bool
expectsInt interface{}
config CTemplateConfig
doImports bool
expectsInt interface{}
}
func (c *CTemplateSet) Minify(on bool) {
c.minify = on
func (c *CTemplateSet) SetConfig(config CTemplateConfig) {
c.config = config
}
func (c *CTemplateSet) Debug(on bool) {
c.debug = on
func (c *CTemplateSet) GetConfig() CTemplateConfig {
return c.config
}
func (c *CTemplateSet) SuperDebug(on bool) {
c.superDebug = on
}
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 {
func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
if c.config.Debug {
fmt.Println("Compiling template '" + name + "'")
}
c.dir = dir
c.fileDir = fileDir
c.doImports = true
c.funcMap = map[string]interface{}{
"and": "&&",
@ -92,6 +88,7 @@ func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsI
"multiply": true,
"divide": true,
"dock": true,
"lang": true,
}
c.importMap = map[string]string{
@ -107,19 +104,17 @@ func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsI
c.varList = varList
c.hasDispInt = false
c.localDispStructIndex = 0
//c.pVarList = ""
//c.pVarPosition = 0
c.stats = make(map[string]int)
c.expectsInt = expectsInt
holdreflect := reflect.ValueOf(expectsInt)
res, err := ioutil.ReadFile(dir + name)
res, err := ioutil.ReadFile(fileDir + name)
if err != nil {
return "", err
}
content := string(res)
if c.minify {
if c.config.Minify {
content = minify(content)
}
@ -133,21 +128,20 @@ func (c *CTemplateSet) Compile(name string, dir string, expects string, expectsI
out = ""
fname := strings.TrimSuffix(name, filepath.Ext(name))
c.tlist = make(map[string]*parse.Tree)
c.tlist[fname] = tree
c.templateList = map[string]*parse.Tree{fname: tree}
varholder := "tmpl_" + fname + "_vars"
c.log(c.tlist)
c.log(c.templateList)
c.localVars = make(map[string]map[string]VarItemReflect)
c.localVars[fname] = make(map[string]VarItemReflect)
c.localVars[fname]["."] = VarItemReflect{".", varholder, holdreflect}
if c.Fragments == nil {
c.Fragments = make(map[string]int)
}
c.FragmentCursor = make(map[string]int)
c.FragmentCursor[fname] = 0
c.FragmentCursor = map[string]int{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
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 += "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"
if !c.skipHandles {
if !c.config.SkipHandles {
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.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, `))
w.Write([]byte(`, " + ", -1)
@ -183,13 +190,12 @@ w.Write([]byte(`, " + ", -1)
//whitespaceWrites := regexp.MustCompile(`(?s)w.Write\(\[\]byte\(`+spstr+`\)\)`)
//fout = whitespaceWrites.ReplaceAllString(fout,"")
if c.debug {
if c.config.Debug {
for index, count := range c.stats {
fmt.Println(index+": ", strconv.Itoa(count))
}
fmt.Println(" ")
}
c.log("Output!")
c.log(fout)
return fout, nil
@ -224,26 +230,26 @@ func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value
case *parse.IfNode:
c.log("If Node:")
c.log("node.Pipe", node.Pipe)
var expr string
for _, cmd := range node.Pipe.Cmds {
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)
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.currentNode = parse.NodeList
c.nextNode = -1
out = "if " + expr + " {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.List) + "}"
if node.ElseList == nil {
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")
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:
c.log("List Node")
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) {
c.log("Range Node!")
c.log(node.Pipe)
var outVal reflect.Value
for _, cmd := range node.Pipe.Cmds {
c.log("Range Bit:", cmd)
@ -294,17 +299,15 @@ func (c *CTemplateSet) compileRangeNode(varholder string, holdreflect reflect.Va
for _, key := range outVal.MapKeys() {
item = outVal.MapIndex(key)
}
if c.debug {
fmt.Println("Range item:", item)
}
c.log("Range item:", item)
if !item.IsValid() {
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 {
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"
} else {
out = "if len(" + out + ") != 0 {\nfor _, item := range " + out + " {\n" + c.compileSwitch("item", item, templateName, node.List) + "}\n}"
out += " else {\n" + c.compileSwitch("item", item, templateName, node.ElseList) + "}\n"
}
case reflect.Slice:
if outVal.Len() == 0 {
@ -312,13 +315,13 @@ func (c *CTemplateSet) compileRangeNode(varholder string, holdreflect reflect.Va
}
item := outVal.Index(0)
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:
return ""
}
if node.ElseList != nil {
out += " else {\n" + c.compileSwitch(varholder, holdreflect, templateName, node.ElseList) + "}"
}
return out + "\n"
}
@ -328,7 +331,6 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
switch n := firstWord.(type) {
case *parse.FieldNode:
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! */
cur := holdreflect
@ -362,15 +364,14 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
c.error("Debug Data:")
c.error("Holdreflect:", holdreflect)
c.error("Holdreflect.Kind():", holdreflect.Kind())
if !c.superDebug {
if !c.config.SuperDebug {
c.error("cur.Kind():", cur.Kind().String())
}
c.error("")
if !multiline {
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)
@ -395,8 +396,8 @@ func (c *CTemplateSet) compileSubswitch(varholder string, holdreflect reflect.Va
newVarByte = ":"
c.localDispStructIndex++
}
varbit = "disp" + dispStr + " " + newVarByte + "= " + varholder + varbit + "\n"
varholder = "disp" + dispStr
varbit = varholder + " " + newVarByte + "= " + varholder + varbit + "\n"
multiline = true
} else {
continue
@ -466,7 +467,7 @@ func (c *CTemplateSet) compileVarswitch(varholder string, holdreflect reflect.Va
firstWord := node.Args[0]
switch n := firstWord.(type) {
case *parse.FieldNode:
if c.superDebug {
if c.config.SuperDebug {
fmt.Println("Field Node:", n.Ident)
for _, id := range n.Ident {
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)
case *parse.ChainNode:
c.log("Chain Node:", n.Node)
c.log("Chain Node Args:", node.Args)
c.log("Node Args:", node.Args)
case *parse.IdentifierNode:
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)
case *parse.DotNode:
return varholder
case *parse.VariableNode:
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)
case *parse.NilNode:
panic("Nil is not a command x.x")
case *parse.PipeNode:
c.log("Pipe Node!")
c.log(n)
c.log("Args:", node.Args)
c.log("Node Args:", node.Args)
out += c.compileIdentSwitchN(varholder, holdreflect, templateName, node)
c.log("Out:", out)
default:
return c.unknownNode(firstWord)
@ -539,7 +539,6 @@ func (c *CTemplateSet) simpleMath(varholder string, holdreflect reflect.Value, t
numSample := 1
val = reflect.ValueOf(numSample)
}
c.dumpSymbol(pos, node, symbol)
return leftParam + " " + symbol + " " + rightParam, val
}
@ -647,16 +646,34 @@ ArgLoop:
}
rightParam, val3 := c.compileIfVarsub(rightOperand, varholder, templateName, holdreflect)
if val3.IsValid() {
val = val3
} else {
if !val3.IsValid() {
panic("val3 is invalid")
}
val = val3
// TODO: Refactor this
out = "w.Write([]byte(common.BuildWidget(" + leftParam + "," + rightParam + ")))\n"
literal = true
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:
c.log("Variable!")
if len(node.Args) > (pos + 1) {
@ -676,7 +693,7 @@ func (c *CTemplateSet) compileReflectSwitch(varholder string, holdreflect reflec
firstWord := node.Args[0]
switch n := firstWord.(type) {
case *parse.FieldNode:
if c.superDebug {
if c.config.SuperDebug {
fmt.Println("Field Node:", n.Ident)
for _, id := range n.Ident {
fmt.Println("Field Bit:", id)
@ -759,7 +776,6 @@ func (c *CTemplateSet) compileIfVarsub(varname string, varholder string, templat
if !cur.IsValid() {
panic(out + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
c.log("Data Kind:", cur.Kind())
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?
res, err := ioutil.ReadFile(c.dir + node.Name)
res, err := ioutil.ReadFile(c.fileDir + node.Name)
if err != nil {
log.Fatal(err)
}
content := string(res)
if c.minify {
if c.config.Minify {
content = minify(content)
}
@ -904,8 +920,8 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
log.Fatal(err)
}
c.tlist[fname] = tree
subtree := c.tlist[fname]
c.templateList[fname] = tree
subtree := c.templateList[fname]
c.log("subtree.Root", subtree.Root)
c.localVars[fname] = make(map[string]VarItemReflect)
@ -916,68 +932,22 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
return out
}
// TODO: Should we rethink the way the log methods work or their names?
func (c *CTemplateSet) log(args ...interface{}) {
if c.superDebug {
if c.config.SuperDebug {
fmt.Println(args...)
}
}
func (c *CTemplateSet) logf(left string, args ...interface{}) {
if c.superDebug {
if c.config.SuperDebug {
fmt.Printf(left, args...)
}
}
func (c *CTemplateSet) error(args ...interface{}) {
if c.debug {
if c.config.Debug {
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:
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):
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(Page, http.ResponseWriter):
@ -395,6 +404,13 @@ func ResetTemplateOverrides() {
default:
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):
switch dPtr := destTmplPtr.(type) {
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:
var tmpl = *tmplO
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:
var tmpl = *tmplO
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)
case func(CreateTopicPage, http.ResponseWriter) error:
return tmplO(pi.(CreateTopicPage), w)
case func(IPSearchPage, http.ResponseWriter) error:
return tmplO(pi.(IPSearchPage), w)
case func(Page, http.ResponseWriter) error:
return tmplO(pi.(Page), w)
case string:

View File

@ -49,7 +49,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
// TODO: Add an admin version of registerStmt with more flexibility?
return &DefaultUserStore{
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 = ?", "", ""),
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 = ?", "", ""),
@ -64,7 +64,7 @@ func (mus *DefaultUserStore) DirtyGet(id int) *User {
}
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()
if err == nil {
@ -82,7 +82,7 @@ func (mus *DefaultUserStore) Get(id int) (*User, error) {
}
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()
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]
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 {
return list, err
}
for rows.Next() {
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 {
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) {
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()
return user, err
@ -182,7 +182,7 @@ func (mus *DefaultUserStore) BypassGet(id int) (*User, error) {
func (mus *DefaultUserStore) Reload(id int) error {
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 {
mus.cache.Remove(id)
return err

View File

@ -6,7 +6,7 @@
* Copyright Azareal 2017 - 2018
*
*/
package main
package common
import (
"bytes"
@ -18,7 +18,6 @@ import (
"sync"
"time"
"./common"
"github.com/Azareal/gopsutil/cpu"
"github.com/Azareal/gopsutil/mem"
"github.com/gorilla/websocket"
@ -26,60 +25,60 @@ import (
type WSUser struct {
conn *websocket.Conn
User *common.User
User *User
}
type WSHub struct {
onlineUsers map[int]*WSUser
onlineGuests map[*WSUser]bool
guests sync.RWMutex
users sync.RWMutex
OnlineUsers map[int]*WSUser
OnlineGuests map[*WSUser]bool
GuestLock sync.RWMutex
UserLock sync.RWMutex
}
// 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 errWsNouser = errors.New("This user isn't connected via WebSockets")
func init() {
adminStatsWatchers = make(map[*WSUser]bool)
wsHub = WSHub{
onlineUsers: make(map[int]*WSUser),
onlineGuests: make(map[*WSUser]bool),
WsHub = WSHub{
OnlineUsers: make(map[int]*WSUser),
OnlineGuests: make(map[*WSUser]bool),
}
}
func (hub *WSHub) guestCount() int {
defer hub.guests.RUnlock()
hub.guests.RLock()
return len(hub.onlineGuests)
func (hub *WSHub) GuestCount() int {
defer hub.GuestLock.RUnlock()
hub.GuestLock.RLock()
return len(hub.OnlineGuests)
}
func (hub *WSHub) userCount() int {
defer hub.users.RUnlock()
hub.users.RLock()
return len(hub.onlineUsers)
func (hub *WSHub) UserCount() int {
defer hub.UserLock.RUnlock()
hub.UserLock.RLock()
return len(hub.OnlineUsers)
}
func (hub *WSHub) broadcastMessage(msg string) error {
hub.users.RLock()
for _, wsUser := range hub.onlineUsers {
hub.UserLock.RLock()
for _, wsUser := range hub.OnlineUsers {
w, err := wsUser.conn.NextWriter(websocket.TextMessage)
if err != nil {
return err
}
_, _ = w.Write([]byte(msg))
}
hub.users.RUnlock()
hub.UserLock.RUnlock()
return nil
}
func (hub *WSHub) pushMessage(targetUser int, msg string) error {
hub.users.RLock()
wsUser, ok := hub.onlineUsers[targetUser]
hub.users.RUnlock()
hub.UserLock.RLock()
wsUser, ok := hub.OnlineUsers[targetUser]
hub.UserLock.RUnlock()
if !ok {
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 {
//log.Print("In pushAlert")
hub.users.RLock()
wsUser, ok := hub.onlineUsers[targetUser]
hub.users.RUnlock()
hub.UserLock.RLock()
wsUser, ok := hub.OnlineUsers[targetUser]
hub.UserLock.RUnlock()
if !ok {
return errWsNouser
}
//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 {
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 {
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
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 {
return errWsNouser
}
@ -138,7 +137,7 @@ func (hub *WSHub) pushAlerts(users []int, asid int, event string, elementType st
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 {
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?
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)
if err != nil {
return nil
}
userptr, err := common.Users.Get(user.ID)
if err != nil && err != common.ErrStoreCapacityOverflow {
userptr, err := Users.Get(user.ID)
if err != nil && err != ErrStoreCapacityOverflow {
return nil
}
wsUser := &WSUser{conn, userptr}
if user.ID == 0 {
wsHub.guests.Lock()
wsHub.onlineGuests[wsUser] = true
wsHub.guests.Unlock()
WsHub.GuestLock.Lock()
WsHub.OnlineGuests[wsUser] = true
WsHub.GuestLock.Unlock()
} else {
wsHub.users.Lock()
wsHub.onlineUsers[user.ID] = wsUser
wsHub.users.Unlock()
WsHub.UserLock.Lock()
WsHub.OnlineUsers[user.ID] = wsUser
WsHub.UserLock.Unlock()
}
//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()
if err != nil {
if user.ID == 0 {
wsHub.guests.Lock()
delete(wsHub.onlineGuests, wsUser)
wsHub.guests.Unlock()
WsHub.GuestLock.Lock()
delete(WsHub.OnlineGuests, wsUser)
WsHub.GuestLock.Unlock()
} else {
wsHub.users.Lock()
delete(wsHub.onlineUsers, user.ID)
wsHub.users.Unlock()
WsHub.UserLock.Lock()
delete(WsHub.OnlineUsers, user.ID)
WsHub.UserLock.Unlock()
}
break
}
@ -239,9 +239,9 @@ func wsPageResponses(wsUser *WSUser, page []byte) {
return
}
log.Print(wsHub.online_users)
uonline := wsHub.userCount()
gonline := wsHub.guestCount()
log.Print(WsHub.online_users)
uonline := WsHub.UserCount()
gonline := WsHub.GuestCount()
totonline := uonline + gonline
w.Write([]byte("set #dash-totonline " + strconv.Itoa(totonline) + " online\r"))
@ -321,8 +321,8 @@ AdminStatLoop:
cpuPerc, cpuerr = cpu.Percent(time.Second, true)
memres, ramerr = mem.VirtualMemory()
uonline := wsHub.userCount()
gonline := wsHub.guestCount()
uonline := WsHub.UserCount()
gonline := WsHub.GuestCount()
totonline := uonline + gonline
reqCount := 0
@ -339,9 +339,9 @@ AdminStatLoop:
onlineGuestsColour = greaterThanSwitch(gonline, 1, 10)
onlineUsersColour = greaterThanSwitch(uonline, 1, 5)
totonline, totunit = common.ConvertFriendlyUnit(totonline)
uonline, uunit = common.ConvertFriendlyUnit(uonline)
gonline, gunit = common.ConvertFriendlyUnit(gonline)
totonline, totunit = ConvertFriendlyUnit(totonline)
uonline, uunit = ConvertFriendlyUnit(uonline)
gonline, gunit = ConvertFriendlyUnit(gonline)
}
if cpuerr != nil {
@ -363,8 +363,8 @@ AdminStatLoop:
if ramerr != nil {
ramstr = "Unknown"
} else {
totalCount, totalUnit := common.ConvertByteUnit(float64(memres.Total))
usedCount := common.ConvertByteInUnit(float64(memres.Total-memres.Available), totalUnit)
totalCount, totalUnit := ConvertByteUnit(float64(memres.Total))
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
var totstr string

View File

@ -20,15 +20,11 @@ type Stmts struct {
getUserName *sql.Stmt
getEmailsByUser *sql.Stmt
getTopicBasic *sql.Stmt
getActivityEntry *sql.Stmt
forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt
getAttachment *sql.Stmt
getForumTopics *sql.Stmt
getWatchers *sql.Stmt
createReport *sql.Stmt
addActivity *sql.Stmt
notifyOne *sql.Stmt
addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt
addTheme *sql.Stmt
@ -48,7 +44,6 @@ type Stmts struct {
deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt
reportExists *sql.Stmt
notifyWatchers *sql.Stmt
getActivityFeedByWatcher *sql.Stmt
getActivityCountByWatcher *sql.Stmt
@ -152,14 +147,6 @@ func _gen_mssql() (err error) {
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.")
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 {
@ -192,14 +179,6 @@ func _gen_mssql() (err error) {
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.")
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 {
@ -208,22 +187,6 @@ func _gen_mssql() (err error) {
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.")
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) VALUES (?,?,?,?)")
if err != nil {
@ -376,13 +339,5 @@ func _gen_mssql() (err error) {
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
}

View File

@ -22,15 +22,11 @@ type Stmts struct {
getUserName *sql.Stmt
getEmailsByUser *sql.Stmt
getTopicBasic *sql.Stmt
getActivityEntry *sql.Stmt
forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt
getAttachment *sql.Stmt
getForumTopics *sql.Stmt
getWatchers *sql.Stmt
createReport *sql.Stmt
addActivity *sql.Stmt
notifyOne *sql.Stmt
addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt
addTheme *sql.Stmt
@ -50,7 +46,6 @@ type Stmts struct {
deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt
reportExists *sql.Stmt
notifyWatchers *sql.Stmt
getActivityFeedByWatcher *sql.Stmt
getActivityCountByWatcher *sql.Stmt
@ -143,13 +138,6 @@ func _gen_mysql() (err error) {
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.")
stmts.forumEntryExists, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' ORDER BY fid ASC LIMIT 0,1")
if err != nil {
@ -178,13 +166,6 @@ func _gen_mysql() (err error) {
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.")
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 {
@ -192,20 +173,6 @@ func _gen_mysql() (err error) {
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.")
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)")
if err != nil {
@ -339,12 +306,5 @@ func _gen_mysql() (err error) {
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
}

View File

@ -5,6 +5,7 @@ package main
import (
"log"
"strings"
"strconv"
"sync"
"errors"
"net/http"
@ -24,7 +25,7 @@ var RouteMap = map[string]interface{}{
"routes.ViewForum": routes.ViewForum,
"routeChangeTheme": routeChangeTheme,
"routeShowAttachment": routeShowAttachment,
"routeWebsockets": routeWebsockets,
"common.RouteWebsockets": common.RouteWebsockets,
"routeReportSubmit": routeReportSubmit,
"routes.CreateTopic": routes.CreateTopic,
"routes.TopicList": routes.TopicList,
@ -58,11 +59,13 @@ var RouteMap = map[string]interface{}{
"routePanelAnalyticsRoutes": routePanelAnalyticsRoutes,
"routePanelAnalyticsAgents": routePanelAnalyticsAgents,
"routePanelAnalyticsSystems": routePanelAnalyticsSystems,
"routePanelAnalyticsLanguages": routePanelAnalyticsLanguages,
"routePanelAnalyticsReferrers": routePanelAnalyticsReferrers,
"routePanelAnalyticsRouteViews": routePanelAnalyticsRouteViews,
"routePanelAnalyticsAgentViews": routePanelAnalyticsAgentViews,
"routePanelAnalyticsForumViews": routePanelAnalyticsForumViews,
"routePanelAnalyticsSystemViews": routePanelAnalyticsSystemViews,
"routePanelAnalyticsLanguageViews": routePanelAnalyticsLanguageViews,
"routePanelAnalyticsReferrerViews": routePanelAnalyticsReferrerViews,
"routePanelAnalyticsPosts": routePanelAnalyticsPosts,
"routePanelAnalyticsTopics": routePanelAnalyticsTopics,
@ -100,7 +103,7 @@ var RouteMap = map[string]interface{}{
"routes.MoveTopicSubmit": routes.MoveTopicSubmit,
"routeLikeTopicSubmit": routeLikeTopicSubmit,
"routes.ViewTopic": routes.ViewTopic,
"routeCreateReplySubmit": routeCreateReplySubmit,
"routes.CreateReplySubmit": routes.CreateReplySubmit,
"routes.ReplyEditSubmit": routes.ReplyEditSubmit,
"routes.ReplyDeleteSubmit": routes.ReplyDeleteSubmit,
"routeReplyLikeSubmit": routeReplyLikeSubmit,
@ -129,7 +132,7 @@ var routeMapEnum = map[string]int{
"routes.ViewForum": 4,
"routeChangeTheme": 5,
"routeShowAttachment": 6,
"routeWebsockets": 7,
"common.RouteWebsockets": 7,
"routeReportSubmit": 8,
"routes.CreateTopic": 9,
"routes.TopicList": 10,
@ -163,66 +166,68 @@ var routeMapEnum = map[string]int{
"routePanelAnalyticsRoutes": 38,
"routePanelAnalyticsAgents": 39,
"routePanelAnalyticsSystems": 40,
"routePanelAnalyticsReferrers": 41,
"routePanelAnalyticsRouteViews": 42,
"routePanelAnalyticsAgentViews": 43,
"routePanelAnalyticsForumViews": 44,
"routePanelAnalyticsSystemViews": 45,
"routePanelAnalyticsReferrerViews": 46,
"routePanelAnalyticsPosts": 47,
"routePanelAnalyticsTopics": 48,
"routePanelAnalyticsForums": 49,
"routePanelGroups": 50,
"routePanelGroupsEdit": 51,
"routePanelGroupsEditPerms": 52,
"routePanelGroupsEditSubmit": 53,
"routePanelGroupsEditPermsSubmit": 54,
"routePanelGroupsCreateSubmit": 55,
"routePanelBackups": 56,
"routePanelLogsMod": 57,
"routePanelDebug": 58,
"routePanelDashboard": 59,
"routes.AccountEditCritical": 60,
"routeAccountEditCriticalSubmit": 61,
"routeAccountEditAvatar": 62,
"routeAccountEditAvatarSubmit": 63,
"routeAccountEditUsername": 64,
"routeAccountEditUsernameSubmit": 65,
"routeAccountEditEmail": 66,
"routeAccountEditEmailTokenSubmit": 67,
"routes.ViewProfile": 68,
"routes.BanUserSubmit": 69,
"routes.UnbanUser": 70,
"routes.ActivateUser": 71,
"routes.IPSearch": 72,
"routes.CreateTopicSubmit": 73,
"routes.EditTopicSubmit": 74,
"routes.DeleteTopicSubmit": 75,
"routes.StickTopicSubmit": 76,
"routes.UnstickTopicSubmit": 77,
"routes.LockTopicSubmit": 78,
"routes.UnlockTopicSubmit": 79,
"routes.MoveTopicSubmit": 80,
"routeLikeTopicSubmit": 81,
"routes.ViewTopic": 82,
"routeCreateReplySubmit": 83,
"routes.ReplyEditSubmit": 84,
"routes.ReplyDeleteSubmit": 85,
"routeReplyLikeSubmit": 86,
"routeProfileReplyCreateSubmit": 87,
"routes.ProfileReplyEditSubmit": 88,
"routes.ProfileReplyDeleteSubmit": 89,
"routes.PollVote": 90,
"routes.PollResults": 91,
"routes.AccountLogin": 92,
"routes.AccountRegister": 93,
"routeLogout": 94,
"routes.AccountLoginSubmit": 95,
"routes.AccountRegisterSubmit": 96,
"routeDynamic": 97,
"routeUploads": 98,
"routes.StaticFile": 99,
"BadRoute": 100,
"routePanelAnalyticsLanguages": 41,
"routePanelAnalyticsReferrers": 42,
"routePanelAnalyticsRouteViews": 43,
"routePanelAnalyticsAgentViews": 44,
"routePanelAnalyticsForumViews": 45,
"routePanelAnalyticsSystemViews": 46,
"routePanelAnalyticsLanguageViews": 47,
"routePanelAnalyticsReferrerViews": 48,
"routePanelAnalyticsPosts": 49,
"routePanelAnalyticsTopics": 50,
"routePanelAnalyticsForums": 51,
"routePanelGroups": 52,
"routePanelGroupsEdit": 53,
"routePanelGroupsEditPerms": 54,
"routePanelGroupsEditSubmit": 55,
"routePanelGroupsEditPermsSubmit": 56,
"routePanelGroupsCreateSubmit": 57,
"routePanelBackups": 58,
"routePanelLogsMod": 59,
"routePanelDebug": 60,
"routePanelDashboard": 61,
"routes.AccountEditCritical": 62,
"routeAccountEditCriticalSubmit": 63,
"routeAccountEditAvatar": 64,
"routeAccountEditAvatarSubmit": 65,
"routeAccountEditUsername": 66,
"routeAccountEditUsernameSubmit": 67,
"routeAccountEditEmail": 68,
"routeAccountEditEmailTokenSubmit": 69,
"routes.ViewProfile": 70,
"routes.BanUserSubmit": 71,
"routes.UnbanUser": 72,
"routes.ActivateUser": 73,
"routes.IPSearch": 74,
"routes.CreateTopicSubmit": 75,
"routes.EditTopicSubmit": 76,
"routes.DeleteTopicSubmit": 77,
"routes.StickTopicSubmit": 78,
"routes.UnstickTopicSubmit": 79,
"routes.LockTopicSubmit": 80,
"routes.UnlockTopicSubmit": 81,
"routes.MoveTopicSubmit": 82,
"routeLikeTopicSubmit": 83,
"routes.ViewTopic": 84,
"routes.CreateReplySubmit": 85,
"routes.ReplyEditSubmit": 86,
"routes.ReplyDeleteSubmit": 87,
"routeReplyLikeSubmit": 88,
"routeProfileReplyCreateSubmit": 89,
"routes.ProfileReplyEditSubmit": 90,
"routes.ProfileReplyDeleteSubmit": 91,
"routes.PollVote": 92,
"routes.PollResults": 93,
"routes.AccountLogin": 94,
"routes.AccountRegister": 95,
"routeLogout": 96,
"routes.AccountLoginSubmit": 97,
"routes.AccountRegisterSubmit": 98,
"routeDynamic": 99,
"routeUploads": 100,
"routes.StaticFile": 101,
"BadRoute": 102,
}
var reverseRouteMapEnum = map[int]string{
0: "routeAPI",
@ -232,7 +237,7 @@ var reverseRouteMapEnum = map[int]string{
4: "routes.ViewForum",
5: "routeChangeTheme",
6: "routeShowAttachment",
7: "routeWebsockets",
7: "common.RouteWebsockets",
8: "routeReportSubmit",
9: "routes.CreateTopic",
10: "routes.TopicList",
@ -266,66 +271,68 @@ var reverseRouteMapEnum = map[int]string{
38: "routePanelAnalyticsRoutes",
39: "routePanelAnalyticsAgents",
40: "routePanelAnalyticsSystems",
41: "routePanelAnalyticsReferrers",
42: "routePanelAnalyticsRouteViews",
43: "routePanelAnalyticsAgentViews",
44: "routePanelAnalyticsForumViews",
45: "routePanelAnalyticsSystemViews",
46: "routePanelAnalyticsReferrerViews",
47: "routePanelAnalyticsPosts",
48: "routePanelAnalyticsTopics",
49: "routePanelAnalyticsForums",
50: "routePanelGroups",
51: "routePanelGroupsEdit",
52: "routePanelGroupsEditPerms",
53: "routePanelGroupsEditSubmit",
54: "routePanelGroupsEditPermsSubmit",
55: "routePanelGroupsCreateSubmit",
56: "routePanelBackups",
57: "routePanelLogsMod",
58: "routePanelDebug",
59: "routePanelDashboard",
60: "routes.AccountEditCritical",
61: "routeAccountEditCriticalSubmit",
62: "routeAccountEditAvatar",
63: "routeAccountEditAvatarSubmit",
64: "routeAccountEditUsername",
65: "routeAccountEditUsernameSubmit",
66: "routeAccountEditEmail",
67: "routeAccountEditEmailTokenSubmit",
68: "routes.ViewProfile",
69: "routes.BanUserSubmit",
70: "routes.UnbanUser",
71: "routes.ActivateUser",
72: "routes.IPSearch",
73: "routes.CreateTopicSubmit",
74: "routes.EditTopicSubmit",
75: "routes.DeleteTopicSubmit",
76: "routes.StickTopicSubmit",
77: "routes.UnstickTopicSubmit",
78: "routes.LockTopicSubmit",
79: "routes.UnlockTopicSubmit",
80: "routes.MoveTopicSubmit",
81: "routeLikeTopicSubmit",
82: "routes.ViewTopic",
83: "routeCreateReplySubmit",
84: "routes.ReplyEditSubmit",
85: "routes.ReplyDeleteSubmit",
86: "routeReplyLikeSubmit",
87: "routeProfileReplyCreateSubmit",
88: "routes.ProfileReplyEditSubmit",
89: "routes.ProfileReplyDeleteSubmit",
90: "routes.PollVote",
91: "routes.PollResults",
92: "routes.AccountLogin",
93: "routes.AccountRegister",
94: "routeLogout",
95: "routes.AccountLoginSubmit",
96: "routes.AccountRegisterSubmit",
97: "routeDynamic",
98: "routeUploads",
99: "routes.StaticFile",
100: "BadRoute",
41: "routePanelAnalyticsLanguages",
42: "routePanelAnalyticsReferrers",
43: "routePanelAnalyticsRouteViews",
44: "routePanelAnalyticsAgentViews",
45: "routePanelAnalyticsForumViews",
46: "routePanelAnalyticsSystemViews",
47: "routePanelAnalyticsLanguageViews",
48: "routePanelAnalyticsReferrerViews",
49: "routePanelAnalyticsPosts",
50: "routePanelAnalyticsTopics",
51: "routePanelAnalyticsForums",
52: "routePanelGroups",
53: "routePanelGroupsEdit",
54: "routePanelGroupsEditPerms",
55: "routePanelGroupsEditSubmit",
56: "routePanelGroupsEditPermsSubmit",
57: "routePanelGroupsCreateSubmit",
58: "routePanelBackups",
59: "routePanelLogsMod",
60: "routePanelDebug",
61: "routePanelDashboard",
62: "routes.AccountEditCritical",
63: "routeAccountEditCriticalSubmit",
64: "routeAccountEditAvatar",
65: "routeAccountEditAvatarSubmit",
66: "routeAccountEditUsername",
67: "routeAccountEditUsernameSubmit",
68: "routeAccountEditEmail",
69: "routeAccountEditEmailTokenSubmit",
70: "routes.ViewProfile",
71: "routes.BanUserSubmit",
72: "routes.UnbanUser",
73: "routes.ActivateUser",
74: "routes.IPSearch",
75: "routes.CreateTopicSubmit",
76: "routes.EditTopicSubmit",
77: "routes.DeleteTopicSubmit",
78: "routes.StickTopicSubmit",
79: "routes.UnstickTopicSubmit",
80: "routes.LockTopicSubmit",
81: "routes.UnlockTopicSubmit",
82: "routes.MoveTopicSubmit",
83: "routeLikeTopicSubmit",
84: "routes.ViewTopic",
85: "routes.CreateReplySubmit",
86: "routes.ReplyEditSubmit",
87: "routes.ReplyDeleteSubmit",
88: "routeReplyLikeSubmit",
89: "routeProfileReplyCreateSubmit",
90: "routes.ProfileReplyEditSubmit",
91: "routes.ProfileReplyDeleteSubmit",
92: "routes.PollVote",
93: "routes.PollResults",
94: "routes.AccountLogin",
95: "routes.AccountRegister",
96: "routeLogout",
97: "routes.AccountLoginSubmit",
98: "routes.AccountRegisterSubmit",
99: "routeDynamic",
100: "routeUploads",
101: "routes.StaticFile",
102: "BadRoute",
}
var osMapEnum = map[string]int{
"unknown": 0,
@ -366,12 +373,13 @@ var agentMapEnum = map[string]int{
"twitter": 19,
"cloudflare": 20,
"uptimebot": 21,
"discourse": 22,
"lynx": 23,
"blank": 24,
"malformed": 25,
"suspicious": 26,
"zgrab": 27,
"slackbot": 22,
"discourse": 23,
"lynx": 24,
"blank": 25,
"malformed": 26,
"suspicious": 27,
"zgrab": 28,
}
var reverseAgentMapEnum = map[int]string{
0: "unknown",
@ -396,12 +404,13 @@ var reverseAgentMapEnum = map[int]string{
19: "twitter",
20: "cloudflare",
21: "uptimebot",
22: "discourse",
23: "lynx",
24: "blank",
25: "malformed",
26: "suspicious",
27: "zgrab",
22: "slackbot",
23: "discourse",
24: "lynx",
25: "blank",
26: "malformed",
27: "suspicious",
28: "zgrab",
}
var markToAgent = map[string]string{
"OPR":"opera",
@ -424,6 +433,7 @@ var markToAgent = map[string]string{
"SeznamBot":"seznambot",
"CloudFlare":"cloudflare", // Track alwayson specifically in case there are other bots?
"Uptimebot":"uptimebot",
"Slackbot":"slackbot",
"Discordbot":"discord",
"Twitterbot":"twitter",
"Discourse":"discourse",
@ -497,7 +507,7 @@ func (router *GenRouter) StripNewlines(data string) string {
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
for key, value := range req.Header {
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 +
"req.Host: " + router.StripNewlines(req.Host) + "\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) {
log.Print("Suspicious Request")
router.DumpRequest(req)
counters.AgentViewCounter.Bump(26)
router.DumpRequest(req,"Suspicious Request")
counters.AgentViewCounter.Bump(27)
}
// 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 {
w.WriteHeader(200) // 400
w.Write([]byte(""))
log.Print("Malformed Request")
router.DumpRequest(req)
counters.AgentViewCounter.Bump(25)
router.DumpRequest(req,"Malformed Request")
counters.AgentViewCounter.Bump(26)
return
}
// TODO: Cover more suspicious strings and at a lower layer than this
for _, char := range req.URL.Path {
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") {
for _, char := range req.URL.Path {
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") || strings.Contains(lowerPath,".action") {
router.SuspiciousRequest(req)
}
var prefix, extraData string
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 {
log.Print("before routes.StaticFile")
router.DumpRequest(req)
router.DumpRequest(req,"before routes.StaticFile")
}
// Increment the request counter
counters.GlobalViewCounter.Bump()
if prefix == "/static" {
counters.RouteViewCounter.Bump(99)
counters.RouteViewCounter.Bump(101)
req.URL.Path += extraData
routes.StaticFile(w, req)
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
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 == "" {
counters.AgentViewCounter.Bump(24)
counters.AgentViewCounter.Bump(25)
if common.Dev.DebugMode {
log.Print("Blank UA: ", req.UserAgent())
router.DumpRequest(req)
var prepend string
for _, char := range req.UserAgent() {
prepend += strconv.Itoa(int(char)) + " "
}
router.DumpRequest(req,"Blank UA: " + prepend)
}
} else {
var runeEquals = func(a []rune, b []rune) bool {
@ -693,8 +704,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if agent == "" {
counters.AgentViewCounter.Bump(0)
if common.Dev.DebugMode {
log.Print("Unknown UA: ", req.UserAgent())
router.DumpRequest(req)
var prepend string
for _, char := range req.UserAgent() {
prepend += strconv.Itoa(int(char)) + " "
}
router.DumpRequest(req,"Blank UA: " + prepend)
}
} else {
counters.AgentViewCounter.Bump(agentMapEnum[agent])
@ -702,6 +716,15 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
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
if referrer != "" {
// ? Optimise this a little?
@ -782,7 +805,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/ws":
req.URL.Path += extraData
counters.RouteViewCounter.Bump(7)
err = routeWebsockets(w,req,user)
err = common.RouteWebsockets(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
}
@ -1043,7 +1066,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.RouteViewCounter.Bump(40)
err = routePanelAnalyticsSystems(w,req,user)
case "/panel/analytics/referrers/":
case "/panel/analytics/langs/":
err = common.ParseForm(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
@ -1051,21 +1074,33 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
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)
case "/panel/analytics/route/":
counters.RouteViewCounter.Bump(42)
counters.RouteViewCounter.Bump(43)
err = routePanelAnalyticsRouteViews(w,req,user,extraData)
case "/panel/analytics/agent/":
counters.RouteViewCounter.Bump(43)
counters.RouteViewCounter.Bump(44)
err = routePanelAnalyticsAgentViews(w,req,user,extraData)
case "/panel/analytics/forum/":
counters.RouteViewCounter.Bump(44)
counters.RouteViewCounter.Bump(45)
err = routePanelAnalyticsForumViews(w,req,user,extraData)
case "/panel/analytics/system/":
counters.RouteViewCounter.Bump(45)
err = routePanelAnalyticsSystemViews(w,req,user,extraData)
case "/panel/analytics/referrer/":
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)
case "/panel/analytics/posts/":
err = common.ParseForm(w,req,user)
@ -1074,7 +1109,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(47)
counters.RouteViewCounter.Bump(49)
err = routePanelAnalyticsPosts(w,req,user)
case "/panel/analytics/topics/":
err = common.ParseForm(w,req,user)
@ -1083,7 +1118,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(48)
counters.RouteViewCounter.Bump(50)
err = routePanelAnalyticsTopics(w,req,user)
case "/panel/analytics/forums/":
err = common.ParseForm(w,req,user)
@ -1092,16 +1127,16 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(49)
counters.RouteViewCounter.Bump(51)
err = routePanelAnalyticsForums(w,req,user)
case "/panel/groups/":
counters.RouteViewCounter.Bump(50)
counters.RouteViewCounter.Bump(52)
err = routePanelGroups(w,req,user)
case "/panel/groups/edit/":
counters.RouteViewCounter.Bump(51)
counters.RouteViewCounter.Bump(53)
err = routePanelGroupsEdit(w,req,user,extraData)
case "/panel/groups/edit/perms/":
counters.RouteViewCounter.Bump(52)
counters.RouteViewCounter.Bump(54)
err = routePanelGroupsEditPerms(w,req,user,extraData)
case "/panel/groups/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1110,7 +1145,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(53)
counters.RouteViewCounter.Bump(55)
err = routePanelGroupsEditSubmit(w,req,user,extraData)
case "/panel/groups/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1119,7 +1154,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(54)
counters.RouteViewCounter.Bump(56)
err = routePanelGroupsEditPermsSubmit(w,req,user,extraData)
case "/panel/groups/create/":
err = common.NoSessionMismatch(w,req,user)
@ -1128,7 +1163,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(55)
counters.RouteViewCounter.Bump(57)
err = routePanelGroupsCreateSubmit(w,req,user)
case "/panel/backups/":
err = common.SuperAdminOnly(w,req,user)
@ -1137,10 +1172,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(56)
counters.RouteViewCounter.Bump(58)
err = routePanelBackups(w,req,user,extraData)
case "/panel/logs/mod/":
counters.RouteViewCounter.Bump(57)
counters.RouteViewCounter.Bump(59)
err = routePanelLogsMod(w,req,user)
case "/panel/debug/":
err = common.AdminOnly(w,req,user)
@ -1149,10 +1184,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(58)
counters.RouteViewCounter.Bump(60)
err = routePanelDebug(w,req,user)
default:
counters.RouteViewCounter.Bump(59)
counters.RouteViewCounter.Bump(61)
err = routePanelDashboard(w,req,user)
}
if err != nil {
@ -1167,7 +1202,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(60)
counters.RouteViewCounter.Bump(62)
err = routes.AccountEditCritical(w,req,user)
case "/user/edit/critical/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1182,7 +1217,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(61)
counters.RouteViewCounter.Bump(63)
err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/":
err = common.MemberOnly(w,req,user)
@ -1191,7 +1226,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(62)
counters.RouteViewCounter.Bump(64)
err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/":
err = common.MemberOnly(w,req,user)
@ -1211,7 +1246,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(63)
counters.RouteViewCounter.Bump(65)
err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/":
err = common.MemberOnly(w,req,user)
@ -1220,7 +1255,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(64)
counters.RouteViewCounter.Bump(66)
err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1235,7 +1270,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(65)
counters.RouteViewCounter.Bump(67)
err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/":
err = common.MemberOnly(w,req,user)
@ -1244,7 +1279,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(66)
counters.RouteViewCounter.Bump(68)
err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/":
err = common.NoSessionMismatch(w,req,user)
@ -1259,11 +1294,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(67)
counters.RouteViewCounter.Bump(69)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default:
req.URL.Path += extraData
counters.RouteViewCounter.Bump(68)
counters.RouteViewCounter.Bump(70)
err = routes.ViewProfile(w,req,user)
}
if err != nil {
@ -1284,7 +1319,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(69)
counters.RouteViewCounter.Bump(71)
err = routes.BanUserSubmit(w,req,user,extraData)
case "/users/unban/":
err = common.NoSessionMismatch(w,req,user)
@ -1299,7 +1334,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(70)
counters.RouteViewCounter.Bump(72)
err = routes.UnbanUser(w,req,user,extraData)
case "/users/activate/":
err = common.NoSessionMismatch(w,req,user)
@ -1314,7 +1349,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(71)
counters.RouteViewCounter.Bump(73)
err = routes.ActivateUser(w,req,user,extraData)
case "/users/ips/":
err = common.MemberOnly(w,req,user)
@ -1323,7 +1358,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(72)
counters.RouteViewCounter.Bump(74)
err = routes.IPSearch(w,req,user)
}
if err != nil {
@ -1349,7 +1384,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(73)
counters.RouteViewCounter.Bump(75)
err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1364,7 +1399,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(74)
counters.RouteViewCounter.Bump(76)
err = routes.EditTopicSubmit(w,req,user,extraData)
case "/topic/delete/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1380,7 +1415,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
req.URL.Path += extraData
counters.RouteViewCounter.Bump(75)
counters.RouteViewCounter.Bump(77)
err = routes.DeleteTopicSubmit(w,req,user)
case "/topic/stick/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1395,7 +1430,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(76)
counters.RouteViewCounter.Bump(78)
err = routes.StickTopicSubmit(w,req,user,extraData)
case "/topic/unstick/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1410,7 +1445,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(77)
counters.RouteViewCounter.Bump(79)
err = routes.UnstickTopicSubmit(w,req,user,extraData)
case "/topic/lock/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1426,7 +1461,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
req.URL.Path += extraData
counters.RouteViewCounter.Bump(78)
counters.RouteViewCounter.Bump(80)
err = routes.LockTopicSubmit(w,req,user)
case "/topic/unlock/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1441,7 +1476,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(79)
counters.RouteViewCounter.Bump(81)
err = routes.UnlockTopicSubmit(w,req,user,extraData)
case "/topic/move/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1456,7 +1491,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(80)
counters.RouteViewCounter.Bump(82)
err = routes.MoveTopicSubmit(w,req,user,extraData)
case "/topic/like/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1471,10 +1506,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(81)
counters.RouteViewCounter.Bump(83)
err = routeLikeTopicSubmit(w,req,user,extraData)
default:
counters.RouteViewCounter.Bump(82)
counters.RouteViewCounter.Bump(84)
err = routes.ViewTopic(w,req,user, extraData)
}
if err != nil {
@ -1500,8 +1535,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(83)
err = routeCreateReplySubmit(w,req,user)
counters.RouteViewCounter.Bump(85)
err = routes.CreateReplySubmit(w,req,user)
case "/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -1515,7 +1550,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(84)
counters.RouteViewCounter.Bump(86)
err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1530,7 +1565,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(85)
counters.RouteViewCounter.Bump(87)
err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1545,7 +1580,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(86)
counters.RouteViewCounter.Bump(88)
err = routeReplyLikeSubmit(w,req,user,extraData)
}
if err != nil {
@ -1566,7 +1601,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(87)
counters.RouteViewCounter.Bump(89)
err = routeProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1581,7 +1616,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(88)
counters.RouteViewCounter.Bump(90)
err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -1596,7 +1631,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(89)
counters.RouteViewCounter.Bump(91)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
}
if err != nil {
@ -1617,10 +1652,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(90)
counters.RouteViewCounter.Bump(92)
err = routes.PollVote(w,req,user,extraData)
case "/poll/results/":
counters.RouteViewCounter.Bump(91)
counters.RouteViewCounter.Bump(93)
err = routes.PollResults(w,req,user,extraData)
}
if err != nil {
@ -1629,10 +1664,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/accounts":
switch(req.URL.Path) {
case "/accounts/login/":
counters.RouteViewCounter.Bump(92)
counters.RouteViewCounter.Bump(94)
err = routes.AccountLogin(w,req,user)
case "/accounts/create/":
counters.RouteViewCounter.Bump(93)
counters.RouteViewCounter.Bump(95)
err = routes.AccountRegister(w,req,user)
case "/accounts/logout/":
err = common.NoSessionMismatch(w,req,user)
@ -1647,7 +1682,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(94)
counters.RouteViewCounter.Bump(96)
err = routeLogout(w,req,user)
case "/accounts/login/submit/":
err = common.ParseForm(w,req,user)
@ -1656,7 +1691,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(95)
counters.RouteViewCounter.Bump(97)
err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/create/submit/":
err = common.ParseForm(w,req,user)
@ -1665,7 +1700,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
counters.RouteViewCounter.Bump(96)
counters.RouteViewCounter.Bump(98)
err = routes.AccountRegisterSubmit(w,req,user)
}
if err != nil {
@ -1682,7 +1717,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.NotFound(w,req,nil)
return
}
counters.RouteViewCounter.Bump(98)
counters.RouteViewCounter.Bump(100)
req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) // TODO: Count these views
@ -1725,7 +1760,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock()
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
err = handle(w,req,user)
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") {
router.SuspiciousRequest(req)
}
counters.RouteViewCounter.Bump(100)
counters.RouteViewCounter.Bump(102)
common.NotFound(w,req,nil)
}
}

View File

@ -1,9 +1,11 @@
{
"Name": "english",
"Levels": {
"Level": "Level {0}",
"LevelMax": ""
},
"GlobalPerms": {
"BanUsers": "Can ban users",
"ActivateUsers": "Can activate users",
@ -27,6 +29,7 @@
"UploadFiles": "Can upload files"
},
"LocalPerms": {
"ViewTopic": "Can view topics",
"LikeItem": "Can like items",
@ -40,9 +43,11 @@
"CloseTopic": "Can lock topics",
"MoveTopic": "Can move topics in or out"
},
"SettingLabels": {
"activation_type": "Activate All,Email Activation,Admin Approval"
},
"PermPresets": {
"all":"Public",
"announce":"Announcements",
@ -53,14 +58,17 @@
"custom":"Custom",
"unknown":"Unknown"
},
"Accounts": {
"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."
},
"Errors": {
"NoPerms": {
}
},
"PageTitles": {
"overview":"Overview",
"page":"Page",
@ -68,7 +76,7 @@
"forums":"Forum List",
"login":"Login",
"register":"Registration",
"ip-search":"IP Search",
"ip_search":"IP Search",
"panel_dashboard":"Control Panel Dashboard",
"panel_forums":"Forum Manager",
@ -114,6 +122,7 @@
"twitter":"Twitterbot",
"cloudflare":"Cloudflare Alwayson",
"uptimebot":"Uptimebot",
"slackbot":"Slackbot",
"discourse":"Discourse Forum Onebox",
"lynx":"Lynx",
@ -123,6 +132,7 @@
"blank":"Blank",
"malformed":"Malformed"
},
"OperatingSystems": {
"windows": "Microsoft Windows",
"linux":"Linux",
@ -130,5 +140,110 @@
"android": "Android",
"iphone":"iPhone",
"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 {
return err
}
counters.LangViewCounter, err = counters.NewDefaultLangViewCounter()
if err != nil {
return err
}
counters.RouteViewCounter, err = counters.NewDefaultRouteViewCounter()
if err != nil {
return err
@ -290,9 +294,9 @@ func main() {
// Run this goroutine once every half second
halfSecondTicker := time.NewTicker(time.Second / 2)
secondTicker := time.NewTicker(1 * time.Second)
secondTicker := time.NewTicker(time.Second)
fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
//hourTicker := time.NewTicker(1 * time.Hour)
//hourTicker := time.NewTicker(time.Hour)
go func() {
var runHook = func(name string) {
err := common.RunTaskHook(name)

View File

@ -1,11 +1,8 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"html"
"io"
"log"
"net/http"
"os"
"path/filepath"
@ -17,188 +14,6 @@ import (
"./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
func routeLikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
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)
}
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 {
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)
return nil
@ -308,22 +111,10 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.Us
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 {
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)
return nil
@ -338,13 +129,22 @@ func routeProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user
if err != nil {
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)
} else if err != nil {
return common.InternalError(err,w,r)
}
content := common.PreparseMessage(r.PostFormValue("reply-content"))
// 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 {
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"},
}
if enableWebsockets {
uonline := wsHub.userCount()
gonline := wsHub.guestCount()
if common.EnableWebsockets {
uonline := common.WsHub.UserCount()
gonline := common.WsHub.GuestCount()
totonline := uonline + gonline
reqCount := 0
@ -743,6 +743,9 @@ func routePanelAnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user
}
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")
acc := qgen.Builder.Accumulator()
// 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}
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)
if !ok {
friendlyAgent = agent
@ -789,10 +790,15 @@ func routePanelAnalyticsForumViews(w http.ResponseWriter, r *http.Request, user
}
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")
acc := qgen.Builder.Accumulator()
// 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 {
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}
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)
if err != nil {
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)
}
revLabelList, labelList, viewMap := panelAnalyticsTimeRangeToLabelList(timeRange)
system = html.EscapeString(system)
common.DebugLog("in routePanelAnalyticsSystemViews")
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)
if err != nil && err != ErrNoRows {
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}
common.DebugLogf("graph: %+v\n", graph)
system = html.EscapeString(system)
friendlySystem, ok := common.GetOSPhrase(system)
if !ok {
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)
}
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 {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
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)
}
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 {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
@ -1688,6 +1775,10 @@ func routePanelUsersEdit(w http.ResponseWriter, r *http.Request, user common.Use
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}
if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) {
return nil
@ -1776,7 +1867,7 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user comm
}
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
}

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("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("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) {
_, 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
}
@ -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("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("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 {
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
}

View File

@ -424,6 +424,15 @@ func createTables(adapter qgen.Adapter) error {
[]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.DBTableColumn{
qgen.DBTableColumn{"count", "int", 0, false, false, "0"},

View File

@ -200,6 +200,7 @@ func main() {
"twitter",
"cloudflare",
"uptimebot",
"slackbot",
"discourse",
"lynx",
"blank",
@ -220,6 +221,7 @@ package main
import (
"log"
"strings"
"strconv"
"sync"
"errors"
"net/http"
@ -275,6 +277,7 @@ var markToAgent = map[string]string{
"SeznamBot":"seznambot",
"CloudFlare":"cloudflare", // Track alwayson specifically in case there are other bots?
"Uptimebot":"uptimebot",
"Slackbot":"slackbot",
"Discordbot":"discord",
"Twitterbot":"twitter",
"Discourse":"discourse",
@ -348,7 +351,7 @@ func (router *GenRouter) StripNewlines(data string) string {
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
for key, value := range req.Header {
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 +
"req.Host: " + router.StripNewlines(req.Host) + "\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) {
log.Print("Suspicious Request")
router.DumpRequest(req)
router.DumpRequest(req,"Suspicious Request")
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 {
w.WriteHeader(200) // 400
w.Write([]byte(""))
log.Print("Malformed Request")
router.DumpRequest(req)
router.DumpRequest(req,"Malformed Request")
counters.AgentViewCounter.Bump({{.AllAgentMap.malformed}})
return
}
// TODO: Cover more suspicious strings and at a lower layer than this
for _, char := range req.URL.Path {
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") {
for _, char := range req.URL.Path {
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") || strings.Contains(lowerPath,".action") {
router.SuspiciousRequest(req)
}
var prefix, extraData string
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 {
log.Print("before routes.StaticFile")
router.DumpRequest(req)
router.DumpRequest(req,"before routes.StaticFile")
}
// Increment the request counter
counters.GlobalViewCounter.Bump()
@ -444,8 +445,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if ua == "" {
counters.AgentViewCounter.Bump({{.AllAgentMap.blank}})
if common.Dev.DebugMode {
log.Print("Blank UA: ", req.UserAgent())
router.DumpRequest(req)
var prepend string
for _, char := range req.UserAgent() {
prepend += strconv.Itoa(int(char)) + " "
}
router.DumpRequest(req,"Blank UA: " + prepend)
}
} else {
var runeEquals = func(a []rune, b []rune) bool {
@ -544,8 +548,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if agent == "" {
counters.AgentViewCounter.Bump({{.AllAgentMap.unknown}})
if common.Dev.DebugMode {
log.Print("Unknown UA: ", req.UserAgent())
router.DumpRequest(req)
var prepend string
for _, char := range req.UserAgent() {
prepend += strconv.Itoa(int(char)) + " "
}
router.DumpRequest(req,"Blank UA: " + prepend)
}
} else {
counters.AgentViewCounter.Bump(agentMapEnum[agent])
@ -553,6 +560,15 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
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
if referrer != "" {
// ? Optimise this a little?

View File

@ -32,7 +32,7 @@ func routes() {
buildPollRoutes()
buildAccountRoutes()
addRoute(Special("routeWebsockets", "/ws/"))
addRoute(Special("common.RouteWebsockets", "/ws/"))
}
// TODO: Test the email token route
@ -85,7 +85,7 @@ func buildReplyRoutes() {
replyGroup := newRouteGroup("/reply/")
replyGroup.Routes(
// 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.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"),
Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData"),
@ -167,11 +167,13 @@ func buildPanelRoutes() {
View("routePanelAnalyticsRoutes", "/panel/analytics/routes/").Before("ParseForm"),
View("routePanelAnalyticsAgents", "/panel/analytics/agents/").Before("ParseForm"),
View("routePanelAnalyticsSystems", "/panel/analytics/systems/").Before("ParseForm"),
View("routePanelAnalyticsLanguages", "/panel/analytics/langs/").Before("ParseForm"),
View("routePanelAnalyticsReferrers", "/panel/analytics/referrers/").Before("ParseForm"),
View("routePanelAnalyticsRouteViews", "/panel/analytics/route/", "extraData"),
View("routePanelAnalyticsAgentViews", "/panel/analytics/agent/", "extraData"),
View("routePanelAnalyticsForumViews", "/panel/analytics/forum/", "extraData"),
View("routePanelAnalyticsSystemViews", "/panel/analytics/system/", "extraData"),
View("routePanelAnalyticsLanguageViews", "/panel/analytics/lang/", "extraData"),
View("routePanelAnalyticsReferrerViews", "/panel/analytics/referrer/", "extraData"),
View("routePanelAnalyticsPosts", "/panel/analytics/posts/").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 {
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 {
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) {
return nil
}
err := common.Templates.ExecuteTemplate(w, "login.html", pi)
err := common.RunThemeTemplate(headerVars.Theme.Name, "login", pi, w)
if err != nil {
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) {
return nil
}
err := common.Templates.ExecuteTemplate(w, "register.html", pi)
err := common.RunThemeTemplate(headerVars.Theme.Name, "register", pi, w)
if err != nil {
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)
}
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) {
return nil
}
err = common.Templates.ExecuteTemplate(w, "ip-search.html", pi)
return nil
}
err = common.RunThemeTemplate(headerVars.Theme.Name, "ip_search", pi, w)
if err != nil {
return common.InternalError(err, w, r)
}

View File

@ -1,13 +1,188 @@
package routes
import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"html"
"io"
"log"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"../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: 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 {

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:
/* 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 "strconv"
import "net/http"
import "./common"
import "strconv"
var forum_Tmpl_Phrase_ID int
// nolint
func init() {
@ -102,133 +104,134 @@ w.Write(forum_8)
w.Write([]byte(tmpl_forum_vars.Title))
w.Write(forum_9)
if tmpl_forum_vars.CurrentUser.ID != 0 {
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
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([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_12)
} else {
w.Write(forum_13)
}
} else {
w.Write(forum_14)
}
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_22)
}
}
w.Write(forum_23)
if len(tmpl_forum_vars.ItemList) != 0 {
for _, item := range tmpl_forum_vars.ItemList {
w.Write(forum_23)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(forum_24)
if item.Sticky {
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(forum_25)
if item.Sticky {
w.Write(forum_26)
} else {
if item.IsClosed {
w.Write(forum_26)
}
}
w.Write(forum_27)
w.Write([]byte(item.Creator.Link))
}
}
w.Write(forum_28)
w.Write([]byte(item.Creator.Avatar))
w.Write([]byte(item.Creator.Link))
w.Write(forum_29)
w.Write([]byte(item.Creator.Name))
w.Write([]byte(item.Creator.Avatar))
w.Write(forum_30)
w.Write([]byte(item.Creator.Name))
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(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)
if item.IsClosed {
w.Write([]byte(item.Creator.Name))
w.Write(forum_36)
}
if item.Sticky {
if item.IsClosed {
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 {
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)
if item.Sticky {
w.Write(forum_42)
} else {
if item.IsClosed {
w.Write(forum_42)
}
}
w.Write(forum_43)
w.Write([]byte(item.LastUser.Link))
}
}
w.Write(forum_44)
w.Write([]byte(item.LastUser.Avatar))
w.Write([]byte(item.LastUser.Link))
w.Write(forum_45)
w.Write([]byte(item.LastUser.Name))
w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_46)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_47)
w.Write([]byte(item.LastUser.Link))
w.Write(forum_48)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_48)
w.Write([]byte(item.LastUser.Link))
w.Write(forum_49)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write([]byte(item.LastUser.Name))
w.Write(forum_50)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write(forum_51)
}
} else {
w.Write(forum_51)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
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([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_54)
}
w.Write(forum_55)
if tmpl_forum_vars.LastPage > 1 {
}
w.Write(forum_56)
if tmpl_forum_vars.Page > 1 {
if tmpl_forum_vars.LastPage > 1 {
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([]byte(strconv.Itoa(tmpl_forum_vars.Page - 1)))
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 {
for _, item := range tmpl_forum_vars.PageList {
w.Write(forum_60)
w.Write([]byte(strconv.Itoa(item)))
w.Write(forum_61)
w.Write([]byte(strconv.Itoa(item)))
w.Write(forum_62)
w.Write([]byte(strconv.Itoa(item)))
w.Write(forum_63)
}
}
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([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_65)
}
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_66)
}
w.Write(forum_67)
}
w.Write(forum_68)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_forum_vars.Header)))
w.Write(footer_1)

View File

@ -3,8 +3,10 @@
// 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"
import "net/http"
var forums_Tmpl_Phrase_ID int
// nolint
func init() {

View File

@ -3,10 +3,12 @@
// 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 "./common"
import "./extend/guilds/lib"
import "strconv"
import "net/http"
import "./common"
var guilds_guild_list_Tmpl_Phrase_ID int
// nolint
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_19 = []byte(`>
`)
var header_20 = []byte(`<div class="alert">`)
var header_21 = []byte(`</div>`)
var header_20 = []byte(`<div class="alertbox">
<div class="alert">`)
var header_21 = []byte(`</div>
</div>`)
var topic_0 = []byte(`
<form id="edit_topic_form" action='/topic/edit/submit/`)
@ -882,7 +884,7 @@ window.addEventListener("hashchange", handle_profile_hashbit, false)
`)
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="rowitem"><h1 itemprop="name">Forums</h1></div>
@ -936,31 +938,35 @@ var forums_22 = []byte(`
</main>
`)
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="rowitem topic_list_title`)
<div class="rowblock rowhead topic_list_title_block`)
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(`
<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="optbox">
`)
var topics_4 = []byte(`
<div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
<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>
`)
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>
`)
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>
`)
var topics_7 = []byte(`
var topics_8 = []byte(`
</div>
`)
var topics_8 = []byte(`
var topics_9 = []byte(`
<div class="mod_floater auto_hide">
<form method="post">
<div class="mod_floater_head">
@ -978,10 +984,10 @@ var topics_8 = []byte(`
</div>
`)
var topics_9 = []byte(`
var topics_10 = []byte(`
<div id="mod_topic_mover" class="modal_pane auto_hide">
<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" />
<div class="pane_header">
<h3>Move these topics to?</h3>
@ -989,11 +995,11 @@ var topics_10 = []byte(`" method="post">
<div class="pane_body">
<div class="pane_table">
`)
var topics_11 = []byte(`<div id="mover_fid_`)
var topics_12 = []byte(`" data-fid="`)
var topics_13 = []byte(`" class="pane_row">`)
var topics_14 = []byte(`</div>`)
var topics_15 = []byte(`
var topics_12 = []byte(`<div id="mover_fid_`)
var topics_13 = []byte(`" data-fid="`)
var topics_14 = []byte(`" class="pane_row">`)
var topics_15 = []byte(`</div>`)
var topics_16 = []byte(`
</div>
</div>
<div class="pane_buttons">
@ -1003,21 +1009,21 @@ var topics_15 = []byte(`
</div>
<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=`)
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" />
<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="topic_meta">
<div class="formrow topic_board_row real_first_child">
<div class="formitem"><select form="quick_post_form" id="topic_board_input" name="topic-board">
`)
var topics_18 = []byte(`<option `)
var topics_19 = []byte(`selected`)
var topics_20 = []byte(` value="`)
var topics_21 = []byte(`">`)
var topics_22 = []byte(`</option>`)
var topics_23 = []byte(`
var topics_19 = []byte(`<option `)
var topics_20 = []byte(`selected`)
var topics_21 = []byte(` value="`)
var topics_22 = []byte(`">`)
var topics_23 = []byte(`</option>`)
var topics_24 = []byte(`
</select></div>
</div>
<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" 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;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`)
var topics_25 = []byte(`
var topics_26 = []byte(`
<button class="formbutton close_form">Cancel</button>
</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">
`)
var topics_27 = []byte(`<div class="topic_row" data-tid="`)
var topics_28 = []byte(`">
var topics_28 = []byte(`<div class="topic_row" data-tid="`)
var topics_29 = []byte(`">
<div class="rowitem topic_left passive datarow `)
var topics_29 = []byte(`topic_sticky`)
var topics_30 = []byte(`topic_closed`)
var topics_31 = []byte(`">
var topics_30 = []byte(`topic_sticky`)
var topics_31 = []byte(`topic_closed`)
var topics_32 = []byte(`">
<span class="selector"></span>
<a href="`)
var topics_32 = []byte(`"><img src="`)
var topics_33 = []byte(`" height="64" alt="`)
var topics_34 = []byte(`'s Avatar" title="`)
var topics_35 = []byte(`'s Avatar" /></a>
var topics_33 = []byte(`"><img src="`)
var topics_34 = []byte(`" height="64" alt="`)
var topics_35 = []byte(`'s Avatar" title="`)
var topics_36 = []byte(`'s Avatar" /></a>
<span class="topic_inner_left">
<a class="rowtopic" href="`)
var topics_36 = []byte(`" itemprop="itemListElement"><span>`)
var topics_37 = []byte(`</span></a> `)
var topics_38 = []byte(`<a class="rowsmall parent_forum" href="`)
var topics_39 = []byte(`">`)
var topics_40 = []byte(`</a>`)
var topics_41 = []byte(`
var topics_37 = []byte(`" itemprop="itemListElement"><span>`)
var topics_38 = []byte(`</span></a> `)
var topics_39 = []byte(`<a class="rowsmall parent_forum" href="`)
var topics_40 = []byte(`">`)
var topics_41 = []byte(`</a>`)
var topics_42 = []byte(`
<br /><a class="rowsmall starter" href="`)
var topics_42 = []byte(`">`)
var topics_43 = []byte(`</a>
var topics_43 = []byte(`">`)
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_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_46 = []byte(`
var topics_45 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var topics_46 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_47 = []byte(`
</span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`)
var topics_47 = []byte(`</span><br />
var topics_48 = []byte(`</span><br />
<span class="likeCount">`)
var topics_48 = []byte(`</span>
var topics_49 = []byte(`</span>
</span>
</div>
<div class="rowitem topic_right passive datarow `)
var topics_49 = []byte(`topic_sticky`)
var topics_50 = []byte(`topic_closed`)
var topics_51 = []byte(`">
var topics_50 = []byte(`topic_sticky`)
var topics_51 = []byte(`topic_closed`)
var topics_52 = []byte(`">
<a href="`)
var topics_52 = []byte(`"><img src="`)
var topics_53 = []byte(`" height="64" alt="`)
var topics_54 = []byte(`'s Avatar" title="`)
var topics_55 = []byte(`'s Avatar" /></a>
var topics_53 = []byte(`"><img src="`)
var topics_54 = []byte(`" height="64" alt="`)
var topics_55 = []byte(`'s Avatar" title="`)
var topics_56 = []byte(`'s Avatar" /></a>
<span>
<a href="`)
var topics_56 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_57 = []byte(`</a><br>
var topics_57 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_58 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`)
var topics_58 = []byte(`</span>
var topics_59 = []byte(`</span>
</span>
</div>
</div>`)
var topics_59 = []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(`</div>`)
var topics_62 = []byte(`
var topics_60 = []byte(`<div class="rowitem passive rowmsg">There aren't any topics yet.`)
var topics_61 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_62 = []byte(`</div>`)
var topics_63 = []byte(`
</div>
`)
var topics_63 = []byte(`
var topics_64 = []byte(`
<div class="pageset">
`)
var topics_64 = []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_65 = []byte(`<div class="pageitem"><a href="?page=`)
var topics_66 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div>
<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=`)
var topics_68 = []byte(`">`)
var topics_69 = []byte(`</a></div>
var topics_69 = []byte(`">`)
var topics_70 = []byte(`</a></div>
`)
var topics_70 = []byte(`
var topics_71 = []byte(`
<link rel="next" href="?page=`)
var topics_71 = []byte(`" />
var topics_72 = []byte(`" />
<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(`
var topics_73 = []byte(`" rel="next" aria-label="Go to the next page">Next</a></div>`)
var topics_74 = []byte(`
</div>
`)
var topics_74 = []byte(`
var topics_75 = []byte(`
</main>
`)
@ -1151,33 +1157,37 @@ var forum_4 = []byte(`?page=`)
var forum_5 = []byte(`">&gt;</a></div>`)
var forum_6 = []byte(`
<main itemscope itemtype="http://schema.org/ItemList">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block">
<div class="rowitem forum_title`)
<main id="forumItemList" itemscope itemtype="http://schema.org/ItemList">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block`)
var forum_7 = []byte(` has_opt`)
var forum_8 = []byte(`">
<div class="rowitem forum_title">
<h1 itemprop="name">`)
var forum_9 = []byte(`</h1>
</div>
`)
var forum_10 = []byte(`
<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/`)
var forum_11 = []byte(`"></a></div>
`)
var forum_12 = []byte(`
<div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
<div class="optbox">
`)
var forum_11 = []byte(`
<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/`)
var forum_12 = []byte(`"></a></div>
`)
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>
`)
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>
`)
var forum_15 = []byte(`
var forum_16 = []byte(`
</div>
`)
var forum_16 = []byte(`
var forum_17 = []byte(`
<div class="mod_floater auto_hide">
<form method="post">
<div class="mod_floater_head">
@ -1194,13 +1204,13 @@ var forum_16 = []byte(`
</form>
</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">
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<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="`)
var forum_19 = []byte(`" type="hidden">
var forum_20 = []byte(`" type="hidden">
<div class="main_form">
<div class="topic_meta">
<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" 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;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`)
var forum_21 = []byte(`
var forum_22 = []byte(`
<button class="formbutton close_form">Cancel</button>
</div>
</div>
</div>
</div>
`)
var forum_22 = []byte(`
var forum_23 = []byte(`
<div id="forum_topic_list" class="rowblock topic_list">
`)
var forum_23 = []byte(`<div class="topic_row" data-tid="`)
var forum_24 = []byte(`">
var forum_24 = []byte(`<div class="topic_row" data-tid="`)
var forum_25 = []byte(`">
<div class="rowitem topic_left passive datarow `)
var forum_25 = []byte(`topic_sticky`)
var forum_26 = []byte(`topic_closed`)
var forum_27 = []byte(`">
var forum_26 = []byte(`topic_sticky`)
var forum_27 = []byte(`topic_closed`)
var forum_28 = []byte(`">
<span class="selector"></span>
<a href="`)
var forum_28 = []byte(`"><img src="`)
var forum_29 = []byte(`" height="64" alt="`)
var forum_30 = []byte(`'s Avatar" title="`)
var forum_31 = []byte(`'s Avatar" /></a>
var forum_29 = []byte(`"><img src="`)
var forum_30 = []byte(`" height="64" alt="`)
var forum_31 = []byte(`'s Avatar" title="`)
var forum_32 = []byte(`'s Avatar" /></a>
<span class="topic_inner_left">
<a class="rowtopic" href="`)
var forum_32 = []byte(`" itemprop="itemListElement"><span>`)
var forum_33 = []byte(`</span></a>
var forum_33 = []byte(`" itemprop="itemListElement"><span>`)
var forum_34 = []byte(`</span></a>
<br /><a class="rowsmall starter" href="`)
var forum_34 = []byte(`">`)
var forum_35 = []byte(`</a>
var forum_35 = []byte(`">`)
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_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_38 = []byte(`
var forum_37 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var forum_38 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_39 = []byte(`
</span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`)
var forum_39 = []byte(`</span><br />
var forum_40 = []byte(`</span><br />
<span class="likeCount">`)
var forum_40 = []byte(`</span>
var forum_41 = []byte(`</span>
</span>
</div>
<div class="rowitem topic_right passive datarow `)
var forum_41 = []byte(`topic_sticky`)
var forum_42 = []byte(`topic_closed`)
var forum_43 = []byte(`">
var forum_42 = []byte(`topic_sticky`)
var forum_43 = []byte(`topic_closed`)
var forum_44 = []byte(`">
<a href="`)
var forum_44 = []byte(`"><img src="`)
var forum_45 = []byte(`" height="64" alt="`)
var forum_46 = []byte(`'s Avatar" title="`)
var forum_47 = []byte(`'s Avatar" /></a>
var forum_45 = []byte(`"><img src="`)
var forum_46 = []byte(`" height="64" alt="`)
var forum_47 = []byte(`'s Avatar" title="`)
var forum_48 = []byte(`'s Avatar" /></a>
<span>
<a href="`)
var forum_48 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_49 = []byte(`</a><br>
var forum_49 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_50 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`)
var forum_50 = []byte(`</span>
var forum_51 = []byte(`</span>
</span>
</div>
</div>`)
var forum_51 = []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(`">Start one?</a>`)
var forum_54 = []byte(`</div>`)
var forum_55 = []byte(`
var forum_52 = []byte(`<div class="rowitem passive rowmsg">There aren't any topics in this forum yet.`)
var forum_53 = []byte(` <a href="/topics/create/`)
var forum_54 = []byte(`">Start one?</a>`)
var forum_55 = []byte(`</div>`)
var forum_56 = []byte(`
</div>
`)
var forum_56 = []byte(`
var forum_57 = []byte(`
<div class="pageset">
`)
var forum_57 = []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_58 = []byte(`<div class="pageitem"><a href="?page=`)
var forum_59 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div>
<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=`)
var forum_61 = []byte(`">`)
var forum_62 = []byte(`</a></div>
var forum_62 = []byte(`">`)
var forum_63 = []byte(`</a></div>
`)
var forum_63 = []byte(`
var forum_64 = []byte(`
<link rel="next" href="?page=`)
var forum_64 = []byte(`" />
var forum_65 = []byte(`" />
<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(`
var forum_66 = []byte(`" rel="next" aria-label="Go to the next page">Next</a></div>`)
var forum_67 = []byte(`
</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>
`)
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:
/* 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 "strconv"
import "net/http"
import "./common"
import "strconv"
var profile_Tmpl_Phrase_ID int
// nolint
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 "strconv"
var topic_Tmpl_Phrase_ID int
// nolint
func init() {
common.Template_topic_handle = Template_topic

View File

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

View File

@ -7,6 +7,8 @@ import "net/http"
import "./common"
import "strconv"
var topics_Tmpl_Phrase_ID int
// nolint
func init() {
common.Template_topics_handle = Template_topics
@ -86,165 +88,166 @@ w.Write(topics_1)
}
w.Write(topics_2)
if tmpl_topics_vars.CurrentUser.ID != 0 {
if len(tmpl_topics_vars.ForumList) != 0 {
w.Write(topics_3)
if len(tmpl_topics_vars.ForumList) != 0 {
w.Write(topics_4)
} else {
w.Write(topics_5)
}
} else {
w.Write(topics_6)
}
w.Write(topics_7)
if tmpl_topics_vars.CurrentUser.ID != 0 {
}
w.Write(topics_8)
if len(tmpl_topics_vars.ForumList) != 0 {
if tmpl_topics_vars.CurrentUser.ID != 0 {
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([]byte(tmpl_topics_vars.CurrentUser.Session))
w.Write(topics_11)
if len(tmpl_topics_vars.ForumList) != 0 {
for _, item := range tmpl_topics_vars.ForumList {
w.Write(topics_11)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_12)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_13)
w.Write([]byte(item.Name))
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_14)
}
}
w.Write([]byte(item.Name))
w.Write(topics_15)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
}
}
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([]byte(tmpl_topics_vars.CurrentUser.Avatar))
w.Write(topics_18)
if len(tmpl_topics_vars.ForumList) != 0 {
for _, item := range tmpl_topics_vars.ForumList {
w.Write(topics_18)
if item.ID == tmpl_topics_vars.DefaultForum {
w.Write(topics_19)
}
if item.ID == tmpl_topics_vars.DefaultForum {
w.Write(topics_20)
w.Write([]byte(strconv.Itoa(item.ID)))
}
w.Write(topics_21)
w.Write([]byte(item.Name))
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_22)
}
}
w.Write([]byte(item.Name))
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_26)
}
}
w.Write(topics_27)
if len(tmpl_topics_vars.TopicList) != 0 {
for _, item := range tmpl_topics_vars.TopicList {
w.Write(topics_27)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_28)
if item.Sticky {
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_29)
if item.Sticky {
w.Write(topics_30)
} else {
if item.IsClosed {
w.Write(topics_30)
}
}
w.Write(topics_31)
w.Write([]byte(item.Creator.Link))
}
}
w.Write(topics_32)
w.Write([]byte(item.Creator.Avatar))
w.Write([]byte(item.Creator.Link))
w.Write(topics_33)
w.Write([]byte(item.Creator.Name))
w.Write([]byte(item.Creator.Avatar))
w.Write(topics_34)
w.Write([]byte(item.Creator.Name))
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(topics_43)
if item.IsClosed {
w.Write(topics_44)
w.Write(topics_36)
w.Write([]byte(item.Link))
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_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 {
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)
if item.Sticky {
w.Write(topics_50)
} else {
if item.IsClosed {
w.Write(topics_50)
}
}
w.Write(topics_51)
w.Write([]byte(item.LastUser.Link))
}
}
w.Write(topics_52)
w.Write([]byte(item.LastUser.Avatar))
w.Write([]byte(item.LastUser.Link))
w.Write(topics_53)
w.Write([]byte(item.LastUser.Name))
w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_54)
w.Write([]byte(item.LastUser.Name))
w.Write(topics_55)
w.Write([]byte(item.LastUser.Link))
w.Write(topics_56)
w.Write([]byte(item.LastUser.Name))
w.Write(topics_56)
w.Write([]byte(item.LastUser.Link))
w.Write(topics_57)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write([]byte(item.LastUser.Name))
w.Write(topics_58)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write(topics_59)
}
} else {
w.Write(topics_59)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_60)
}
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_61)
}
w.Write(topics_62)
if tmpl_topics_vars.LastPage > 1 {
}
w.Write(topics_63)
if tmpl_topics_vars.Page > 1 {
if tmpl_topics_vars.LastPage > 1 {
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([]byte(strconv.Itoa(tmpl_topics_vars.Page - 1)))
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 {
for _, item := range tmpl_topics_vars.PageList {
w.Write(topics_67)
w.Write([]byte(strconv.Itoa(item)))
w.Write(topics_68)
w.Write([]byte(strconv.Itoa(item)))
w.Write(topics_69)
w.Write([]byte(strconv.Itoa(item)))
w.Write(topics_70)
}
}
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([]byte(strconv.Itoa(tmpl_topics_vars.Page + 1)))
w.Write(topics_72)
}
w.Write([]byte(strconv.Itoa(tmpl_topics_vars.Page + 1)))
w.Write(topics_73)
}
w.Write(topics_74)
}
w.Write(topics_75)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header)))
w.Write(footer_1)

View File

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

View File

@ -1,11 +1,11 @@
{{template "header.html" . }}
<div class="colstack account">
<div id="account_edit_avatar" class="colstack account">
{{template "account_menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Edit Avatar</h1></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>
<div class="colstack_item the_form">

View File

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

View File

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

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<main>
<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 class="rowblock">
<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 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">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block">
<div class="rowitem forum_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}">
<main id="forumItemList" itemscope itemtype="http://schema.org/ItemList">
<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">
<h1 itemprop="name">{{.Title}}</h1>
</div>
{{if ne .CurrentUser.ID 0}}
{{if .CurrentUser.Perms.CreateTopic}}
<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/{{.Forum.ID}}"></a></div>
{{/** TODO: Add a permissions check for this **/}}
<div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
<div class="optbox">
{{if .CurrentUser.Perms.CreateTopic}}
<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/{{.Forum.ID}}"></a></div>
{{/** TODO: Add a permissions check for this **/}}
<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>
{{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>
{{end}}
</div>

View File

@ -1,5 +1,5 @@
{{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="rowitem"><h1 itemprop="name">Forums</h1></div>

View File

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

View File

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

View File

@ -50,6 +50,9 @@
<div class="rowitem passive submenu">
<a href="/panel/analytics/systems/">Systems</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/langs/">Languages</a>
</div>
<div class="rowitem passive submenu">
<a href="/panel/analytics/referrers/">Referrers</a>
</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" . }}
<main id="register_page">
<div class="rowblock rowhead">
<div class="rowitem"><h1>Create Account</h1></div>
<div class="rowitem"><h1>{{lang "register_head"}}</h1></div>
</div>
<div class="rowblock">
<form action="/accounts/create/submit/" method="post">
<div class="formrow">
<div class="formitem formlabel"><a id="username_label">Account Name</a></div>
<div class="formitem"><input name="username" type="text" placeholder="Account Name" aria-labelledby="username_label" required /></div>
<div class="formitem formlabel"><a id="username_label">{{lang "register_account_name"}}</a></div>
<div class="formitem"><input name="username" type="text" placeholder="{{lang "register_account_name"}}" aria-labelledby="username_label" required /></div>
</div>
<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>
<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>
<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>
<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>
</form>
</div>

View File

@ -1,17 +1,19 @@
{{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="rowitem topic_list_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}"><h1 itemprop="name">All Topics</h1></div>
<div class="rowblock rowhead topic_list_title_block{{if ne .CurrentUser.ID 0}} has_opt{{end}}">
<div class="rowitem topic_list_title"><h1 itemprop="name">All Topics</h1></div>
{{if ne .CurrentUser.ID 0}}
<div class="optbox">
{{if .ForumList}}
<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>
{{/** TODO: Add a permissions check for this **/}}
<div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
<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>
{{/** TODO: Add a permissions check for this **/}}
<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 anywhere"><a></a></div>{{end}}
</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>
{{end}}
</div>

View File

@ -1132,11 +1132,22 @@ textarea {
margin-right: 12px;
}
.colstack_right .colstack_item:not(.rowlist):not(#profile_comments),
#profile_comments .comment {
#profile_comments .comment, .alert {
border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-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 {
margin-left: 16px;
}
@ -1268,6 +1279,10 @@ textarea {
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 {
display: none;
}

View File

@ -1,3 +1,5 @@
"use strict"
$(document).ready(function(){
// Is there we way we can append instead? Maybe, an editor plugin?
attachItemCallback = function(attachItem) {
@ -33,7 +35,6 @@ $(document).ready(function(){
// TODO: Refactor this to use `each` less
$('.button_menu').click(function(){
console.log(".button_menu");
// The outer container
let buttonPane = newElement("div","button_menu_pane");
let postItem = $(this).parents('.post_item');
@ -86,6 +87,14 @@ $(document).ready(function(){
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) {

View File

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

View File

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

View File

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