Added the Social Groups plugin. This is still under construction.

Made a few improvements to the ForumStore, including bringing it's API closer in line with the other datastores, adding stubs for future subforum functionality, and improving efficiency in a few places.
The auth interface now handles all the authentication stuff.
Renamed the debug config variable to debug_mode.
Added the PluginPerms API.
Internal Errors will now dump the stack trace in the console.
Added support for installable plugins.
Refactored the routing logic so that the router now handles the common PreRoute logic(exc. /static/)
Added the CreateTable method to the query generator. It might need some tweaking to better support other database systems.
Added the same CreateTable method to the query builder.
Began work on PostgreSQL support.

Added the string-string hook type
Added the pre_render hook type.
Added the ParentID and ParentType fields to forums.
Added the get_forum_url_prefix function.
Added a more generic build_slug function.
Added the get_topic_url_prefix function.
Added the override_perms and override_forum_perms functions for bulk setting and unsetting permissions.
Added more ExtData fields in a few structs and removed them on the Perms struct as the PluginPerms API supersedes them there.
Plugins can now see the router instance.
The plugin initialisation handlers can now throw errors.
Plugins are now initialised after all the forum's subsystems are.
Refactored the unit test logic. For instance, we now use the proper .Log method rather than fmt.Println in many cases.
Sorry, we'll have to break Github's generated file detection, as the build instructions aren't working, unless I put them at the top, and they're far, far more important than getting Github to recognise the generated code as generated code.
Fixed an issue with mysql.go's _init_database() overwriting the dbpassword variable. Not a huge issue, but it is a "gotcha" for those not expecting a ':' at the start.
Fixed an issue with forum creation where the forum permissions didn't get cached.
Fixed a bug in plugin_bbcode where negative numbers in rand would crash Gosora.
Made the outputs of plugin_markdown and plugin_bbcode more compliant with the tests.
Revamped the phrase system to make it easier for us to add language pack related features in the future.
Added the WidgetMenu widget type.
Revamped the theme again. I'm experimenting to see which approach I like most.
- Excuse the little W3C rage. Some things about CSS drive me crazy :p

Tests:
Added 22 bbcode_full_parse tests.
Added 19 bbcode_regex_parse tests.
Added 27 markdown_parse tests.
Added four UserStore tests. More to come when the test database functionality is added.
Added 18 name_to_slug tests.

Hooks:
Added the pre_render hook.
Added the pre_render_forum_list hook.
Added the pre_render_view_forum hook.
Added the pre_render_topic_list hook.
Added the pre_render_view_topic hook.
Added the pre_render_profile hook.
Added the pre_render_custom_page hook.
Added the pre_render_overview hook.
Added the pre_render_create_topic hook.

Added the pre_render_account_own_edit_critical hook.
Added the pre_render_account_own_edit_avatar hook.
Added the pre_render_account_own_edit_username hook.
Added the pre_render_account_own_edit_email hook.
Added the pre_render_login hook.
Added the pre_render_register hook.
Added the pre_render_ban hook.

Added the pre_render_panel_dashboard hook.
Added the pre_render_panel_forums hook.
Added the pre_render_panel_delete_forum hook.
Added the pre_render_panel_edit_forum hook.
Added the pre_render_panel_settings hook.
Added the pre_render_panel_setting hook.
Added the pre_render_panel_plugins hook.
Added the pre_render_panel_users hook.
Added the pre_render_panel_edit_user hook.
Added the pre_render_panel_groups hook.
Added the pre_render_panel_edit_group hook.
Added the pre_render_panel_edit_group_perms hook.
Added the pre_render_panel_themes hook.
Added the pre_render_panel_mod_log hook.
Added the pre_render_error hook.
Added the pre_render_security_error hook.

Added the create_group_preappend hook.
Added the intercept_build_widgets hook.
Added the simple_forum_check_pre_perms hook.
Added the forum_check_pre_perms hook.
This commit is contained in:
Azareal 2017-07-09 13:06:04 +01:00
parent 9a93f799bf
commit dd8f68b6d2
104 changed files with 2912 additions and 842 deletions

49
auth.go
View File

@ -15,11 +15,13 @@ var ErrMismatchedHashAndPassword = bcrypt.ErrMismatchedHashAndPassword
type Auth interface
{
Authenticate(username string, password string) (int,error)
Authenticate(username string, password string) (uid int, err error)
Logout(w http.ResponseWriter, uid int)
ForceLogout(uid int) error
SetCookies(w http.ResponseWriter, uid int, session string)
CreateSession(uid int) (string, error)
GetCookies(r *http.Request) (uid int, session string, err error)
SessionCheck(w http.ResponseWriter, r *http.Request) (user *User, halt bool)
CreateSession(uid int) (session string, err error)
}
type DefaultAuth struct
@ -100,8 +102,47 @@ func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session stri
http.SetCookie(w,&cookie)
}
func(auth *DefaultAuth) CreateSession(uid int) (string, error) {
session, err := GenerateSafeString(sessionLength)
func (auth *DefaultAuth) GetCookies(r *http.Request) (uid int, session string, err error) {
// Are there any session cookies..?
cookie, err := r.Cookie("uid")
if err != nil {
return 0, "", err
}
uid, err = strconv.Atoi(cookie.Value)
if err != nil {
return 0, "", err
}
cookie, err = r.Cookie("session")
if err != nil {
return 0, "", err
}
return uid, cookie.Value, err
}
func (auth *DefaultAuth) SessionCheck(w http.ResponseWriter, r *http.Request) (user *User, halt bool) {
uid, session, err := auth.GetCookies(r)
if err != nil {
return &guest_user, false
}
// Is this session valid..?
user, err = users.CascadeGet(uid)
if err == ErrNoRows {
return &guest_user, false
} else if err != nil {
InternalError(err,w,r)
return &guest_user, true
}
if user.Session == "" || session != user.Session {
return &guest_user, false
}
return user, false
}
func(auth *DefaultAuth) CreateSession(uid int) (session string, err error) {
session, err = GenerateSafeString(sessionLength)
if err != nil {
return "", err
}

View File

@ -5,6 +5,7 @@ const CACHE_STATIC int = 0
const CACHE_DYNAMIC int = 1
const CACHE_SQL int = 2
var ErrCacheDesync = errors.New("The cache is out of synchronisation with the database.") // TO-DO: A cross-server synchronisation mechanism
var ErrStoreCapacityOverflow = errors.New("This datastore has already reached it's max capacity")
type DataStore interface {

View File

@ -45,6 +45,6 @@ var noavatar = "https://api.adorable.io/avatars/285/{id}@" + site_url + ".png"
var items_per_page = 25
// Developer flags
var debug = false
var debug_mode = false
var super_debug = false
var profiling = false

View File

@ -7,7 +7,6 @@ import "database/sql"
var db *sql.DB
var db_version string
var db_collation string = "utf8mb4_general_ci"
var ErrNoRows = sql.ErrNoRows
@ -30,7 +29,7 @@ func init_database() (err error) {
i := 1
for ;rows.Next();i++ {
group := Group{ID: 0,}
err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.Is_Mod, &group.Is_Admin, &group.Is_Banned, &group.Tag)
err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.PluginPermsText, &group.Is_Mod, &group.Is_Admin, &group.Is_Banned, &group.Tag)
if err != nil {
return err
}
@ -45,12 +44,21 @@ func init_database() (err error) {
if err != nil {
return err
}
if debug {
if debug_mode {
log.Print(group.Name + ": ")
fmt.Printf("%+v\n", group.Perms)
}
group.Perms.ExtData = make(map[string]bool)
err = json.Unmarshal(group.PluginPermsText, &group.PluginPerms)
if err != nil {
return err
}
if debug_mode {
log.Print(group.Name + ": ")
fmt.Printf("%+v\n", group.PluginPerms)
}
//group.Perms.ExtData = make(map[string]bool)
groups = append(groups, group)
}
err = rows.Err()

View File

@ -1,14 +1,16 @@
package main
import "fmt"
import "log"
import "bytes"
import "net/http"
import "runtime/debug"
var error_internal []byte
var error_notfound []byte
func init_errors() error {
var b bytes.Buffer
user := User{0,"guest","Guest","",0,false,false,false,false,false,false,GuestPerms,"",false,"","","","","",0,0,"0.0.0.0.0"}
user := User{0,"guest","Guest","",0,false,false,false,false,false,false,GuestPerms,nil,"",false,"","","","","",0,0,"0.0.0.0.0"}
pi := Page{"Internal Server Error",user,hvars,tList,"A problem has occurred in the system."}
err := templates.ExecuteTemplate(&b,"error.html", pi)
if err != nil {
@ -27,12 +29,16 @@ func init_errors() error {
}
func LogError(err error) {
log.Fatal(err)
log.Print(err)
debug.PrintStack()
log.Fatal("")
}
func InternalError(err error, w http.ResponseWriter, r *http.Request) {
w.Write(error_internal)
log.Fatal(err)
log.Print(err)
debug.PrintStack()
log.Fatal("")
}
func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, is_js string) {
@ -42,7 +48,9 @@ func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, is_js s
} else {
w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`))
}
log.Fatal(err)
log.Print(err)
debug.PrintStack()
log.Fatal("")
}
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) {
@ -55,6 +63,11 @@ func PreError(errmsg string, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
user := User{ID:0,Group:6,Perms:GuestPerms,}
pi := Page{"Error",user,hvars,tList,errmsg}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html",pi)
fmt.Fprintln(w,b.String())
@ -63,6 +76,11 @@ func PreError(errmsg string, w http.ResponseWriter, r *http.Request) {
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(500)
pi := Page{"Local Error",user,hvars,tList,errmsg}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html",pi)
fmt.Fprintln(w,b.String())
@ -71,6 +89,11 @@ func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User
func LoginRequired(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(401)
pi := Page{"Local Error",user,hvars,tList,"You need to login to do that."}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html",pi)
fmt.Fprintln(w,b.String())
@ -81,6 +104,11 @@ func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, is_js st
if is_js == "0" {
user := User{ID:0,Group:6,Perms:GuestPerms,}
pi := Page{"Local Error",user,hvars,tList,errmsg}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())
@ -93,6 +121,11 @@ func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user U
w.WriteHeader(500)
if is_js == "0" {
pi := Page{"Local Error",user,hvars,tList,errmsg}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())
@ -109,6 +142,11 @@ func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) {
func NoPermissions(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403)
pi := Page{"Local Error",user,hvars,tList,"You don't have permission to do that."}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
errpage := b.String()
@ -119,6 +157,11 @@ func NoPermissionsJSQ(w http.ResponseWriter, r *http.Request, user User, is_js s
w.WriteHeader(403)
if is_js == "0" {
pi := Page{"Local Error",user,hvars,tList,"You don't have permission to do that."}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())
@ -130,6 +173,11 @@ func NoPermissionsJSQ(w http.ResponseWriter, r *http.Request, user User, is_js s
func Banned(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403)
pi := Page{"Banned",user,hvars,tList,"You have been banned from this site."}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())
@ -139,6 +187,11 @@ func BannedJSQ(w http.ResponseWriter, r *http.Request, user User, is_js string)
w.WriteHeader(403)
if is_js == "0" {
pi := Page{"Banned",user,hvars,tList,"You have been banned from this site."}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())
@ -151,6 +204,11 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, is_js s
w.WriteHeader(401)
if is_js == "0" {
pi := Page{"Local Error",user,hvars,tList,"You need to login to do that."}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())
@ -162,6 +220,11 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, is_js s
func SecurityError(w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(403)
pi := Page{"Security Error",user,hvars,tList,"There was a security issue with your request."}
if pre_render_hooks["pre_render_security_error"] != nil {
if run_pre_render_hook("pre_render_security_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())
@ -175,6 +238,11 @@ func NotFound(w http.ResponseWriter, r *http.Request) {
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) {
w.WriteHeader(errcode)
pi := Page{errtitle,user,hvars,tList,errmsg}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())
@ -184,6 +252,11 @@ func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.Response
w.WriteHeader(errcode)
if is_js == "0" {
pi := Page{errtitle,user,hvars,tList,errmsg}
if pre_render_hooks["pre_render_error"] != nil {
if run_pre_render_hook("pre_render_error", w, r, &user, &pi) {
return
}
}
var b bytes.Buffer
templates.ExecuteTemplate(&b,"error.html", pi)
fmt.Fprintln(w,b.String())

208
extend.go
View File

@ -1,39 +1,72 @@
/* Copyright Azareal 2016 - 2017 */
/* Copyright Azareal 2016 - 2018 */
package main
import "log"
import "net/http"
var plugins map[string]*Plugin = make(map[string]*Plugin)
var hooks map[string][]func(interface{})interface{} = make(map[string][]func(interface{})interface{})
var vhooks map[string]func(...interface{})interface{} = make(map[string]func(...interface{})interface{})
func LoadPlugins() error {
rows, err := get_plugins_stmt.Query()
if err != nil {
return err
}
defer rows.Close()
// Hooks with a single argument. Is this redundant? Might be useful for inlining, as variadics aren't inlined? Are closures even inlined to begin with?
var hooks map[string][]func(interface{})interface{} = map[string][]func(interface{})interface{}{
"forums_frow_assign": nil,
"topic_create_frow_assign": nil,
"rrow_assign": nil, // TO-DO: Rename this hook to topic_rrow_assign
}
var uname string
var active bool
for rows.Next() {
err = rows.Scan(&uname, &active)
if err != nil {
return err
}
// Hooks with a variable number of arguments
var vhooks map[string]func(...interface{})interface{} = map[string]func(...interface{})interface{}{
"simple_forum_check_pre_perms": nil,
"forum_check_pre_perms": nil,
"intercept_build_widgets": nil,
"forum_trow_assign": nil,
"topics_trow_assign": nil,
"create_group_preappend": nil, // What is this? Investigate!
"topic_create_pre_loop": nil,
}
// Was the plugin deleted at some point?
plugin, ok := plugins[uname]
if !ok {
continue
}
plugin.Active = active
plugins[uname] = plugin
}
err = rows.Err()
if err != nil {
return err
}
return nil
// Hooks which take in and spit out a string. This is usually used for parser components
var sshooks map[string][]func(string)string = map[string][]func(string)string{
"preparse_preassign": nil,
"parse_assign": nil,
}
// The hooks which run before the template is rendered for a route
var pre_render_hooks map[string][]func(http.ResponseWriter, *http.Request, *User, interface{})bool = map[string][]func(http.ResponseWriter, *http.Request, *User, interface{})bool{
"pre_render": nil,
"pre_render_forum_list": nil,
"pre_render_view_forum": nil,
"pre_render_topic_list": nil,
"pre_render_view_topic": nil,
"pre_render_profile": nil,
"pre_render_custom_page": nil,
"pre_render_overview": nil,
"pre_render_create_topic": nil,
"pre_render_account_own_edit_critical": nil,
"pre_render_account_own_edit_avatar": nil,
"pre_render_account_own_edit_username": nil,
"pre_render_account_own_edit_email": nil,
"pre_render_login": nil,
"pre_render_register": nil,
"pre_render_ban": nil,
"pre_render_panel_dashboard": nil,
"pre_render_panel_forums": nil,
"pre_render_panel_delete_forum": nil,
"pre_render_panel_edit_forum": nil,
"pre_render_panel_settings": nil,
"pre_render_panel_setting": nil,
"pre_render_panel_plugins": nil,
"pre_render_panel_users": nil,
"pre_render_panel_edit_user": nil,
"pre_render_panel_groups": nil,
"pre_render_panel_edit_group": nil,
"pre_render_panel_edit_group_perms": nil,
"pre_render_panel_themes": nil,
"pre_render_panel_mod_log": nil,
"pre_render_error": nil, // Note: This hook isn't run for a few errors whose templates are computed at startup and reused, such as InternalError. This hook is also not available in JS mode.
"pre_render_security_error": nil,
}
type Plugin struct
@ -46,14 +79,51 @@ type Plugin struct
Active bool
Tag string
Type string
Init func()
Installable bool
Installed bool
Init func()error
Activate func()error
Deactivate func()
Install func()error
Uninstall func()error
Hooks map[string]int
}
func NewPlugin(uname string, name string, author string, url string, settings string, tag string, ptype string, init func(), activate func()error, deactivate func()) *Plugin {
func LoadPlugins() error {
rows, err := get_plugins_stmt.Query()
if err != nil {
return err
}
defer rows.Close()
var uname string
var active bool
var installed bool
for rows.Next() {
err = rows.Scan(&uname, &active, &installed)
if err != nil {
return err
}
// Was the plugin deleted at some point?
plugin, ok := plugins[uname]
if !ok {
continue
}
plugin.Active = active
plugin.Installed = installed
plugins[uname] = plugin
}
err = rows.Err()
if err != nil {
return err
}
return nil
}
func NewPlugin(uname string, name string, author string, url string, settings string, tag string, ptype string, init func()error, activate func()error, deactivate func(), install func()error, uninstall func()error) *Plugin {
return &Plugin{
UName: uname,
Name: name,
@ -62,14 +132,18 @@ func NewPlugin(uname string, name string, author string, url string, settings st
Settings: settings,
Tag: tag,
Type: ptype,
Installable: (install != nil),
Init: init,
Activate: activate,
Deactivate: deactivate,
Install: install,
//Uninstall: uninstall,
/*
The Active field should never be altered by a plugin. It's used internally by the software to determine whether an admin has enabled a plugin or not and whether to run it. This will be overwritten by the user's preference.
*/
Active: false,
Installed: false,
Hooks: make(map[string]int),
}
}
@ -85,6 +159,24 @@ func (plugin *Plugin) AddHook(name string, handler interface{}) {
hooks[name] = append(hooks[name], h)
}
plugin.Hooks[name] = len(hooks[name])
case func(string)string:
if len(sshooks[name]) == 0 {
var hookSlice []func(string)string
hookSlice = append(hookSlice, h)
sshooks[name] = hookSlice
} else {
sshooks[name] = append(sshooks[name], h)
}
plugin.Hooks[name] = len(sshooks[name])
case func(http.ResponseWriter, *http.Request, *User, interface{})bool:
if len(pre_render_hooks[name]) == 0 {
var hookSlice []func(http.ResponseWriter, *http.Request, *User, interface{})bool
hookSlice = append(hookSlice, h)
pre_render_hooks[name] = hookSlice
} else {
pre_render_hooks[name] = append(pre_render_hooks[name], h)
}
plugin.Hooks[name] = len(pre_render_hooks[name])
case func(...interface{}) interface{}:
vhooks[name] = h
plugin.Hooks[name] = 0
@ -104,6 +196,24 @@ func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
hook = append(hook[:key], hook[key + 1:]...)
}
hooks[name] = hook
case func(string)string:
key := plugin.Hooks[name]
hook := sshooks[name]
if len(hook) == 1 {
hook = []func(string)string{}
} else {
hook = append(hook[:key], hook[key + 1:]...)
}
sshooks[name] = hook
case func(http.ResponseWriter, *http.Request, *User, interface{})bool:
key := plugin.Hooks[name]
hook := pre_render_hooks[name]
if len(hook) == 1 {
hook = []func(http.ResponseWriter, *http.Request, *User, interface{})bool{}
} else {
hook = append(hook[:key], hook[key + 1:]...)
}
pre_render_hooks[name] = hook
case func(...interface{}) interface{}:
delete(vhooks, name)
default:
@ -119,7 +229,10 @@ func init_plugins() {
if body.Active {
log.Print("Initialised plugin " + name)
if plugins[name].Init != nil {
plugins[name].Init()
err := plugins[name].Init()
if err != nil {
log.Print(err)
}
} else {
log.Print("Plugin " + name + " doesn't have an initialiser.")
}
@ -135,6 +248,12 @@ func run_hook(name string, data interface{}) interface{} {
return data
}
func run_hook_noreturn(name string, data interface{}) {
for _, hook := range hooks[name] {
_ = hook(data)
}
}
func run_vhook(name string, data ...interface{}) interface{} {
return vhooks[name](data...)
}
@ -142,3 +261,28 @@ func run_vhook(name string, data ...interface{}) interface{} {
func run_vhook_noreturn(name string, data ...interface{}) {
_ = vhooks[name](data...)
}
// Trying to get a teeny bit of type-safety where-ever possible, especially for such a critical set of hooks
func run_sshook(name string, data string) string {
for _, hook := range sshooks[name] {
data = hook(data)
}
return data
}
func run_pre_render_hook(name string, w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
// This hook runs on ALL pre_render hooks
for _, hook := range pre_render_hooks["pre_render"] {
if hook(w,r,user,data) {
return true
}
}
// The actual pre_render hook
for _, hook := range pre_render_hooks[name] {
if hook(w,r,user,data) {
return true
}
}
return false
}

View File

@ -49,7 +49,7 @@ func init_static_files() {
static_files["/static/" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)}
if debug {
if debug_mode {
log.Print("Added the '" + path + "' static file.")
}
return nil
@ -79,7 +79,7 @@ func add_static_file(path string, prefix string) error {
static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)}
if debug {
if debug_mode {
log.Print("Added the '" + path + "' static file")
}
return nil

View File

@ -18,11 +18,13 @@ type ForumAdmin struct
type Forum struct
{
ID int
Slug string
Link string
Name string
Desc string
Active bool
Preset string
ParentID int
ParentType string
TopicCount int
LastTopicSlug string
LastTopic string
@ -46,3 +48,7 @@ func build_forum_url(slug string, fid int) string {
}
return "/forum/" + slug + "." + strconv.Itoa(fid)
}
func get_forum_url_prefix() string {
return "/forum/"
}

View File

@ -2,8 +2,9 @@
package main
import "log"
import "sync"
import "errors"
import "sync"
//import "sync/atomic"
import "database/sql"
import "./query_gen/lib"
@ -22,6 +23,8 @@ type ForumStore interface
CascadeGet(id int) (*Forum, error)
CascadeGetCopy(id int) (Forum, error)
BypassGet(id int) (*Forum, error)
Load(id int) error
Set(forum *Forum) error
//Update(Forum) error
//CascadeUpdate(Forum) error
Delete(id int) error
@ -30,14 +33,17 @@ type ForumStore interface
DecrementTopicCount(id int) error
UpdateLastTopic(topic_name string, tid int, username string, uid int, time string, fid int) error
Exists(id int) bool
GetAll() ([]Forum,error)
GetAll() ([]*Forum,error)
GetAllIDs() ([]int,error)
//GetChildren(parentID int, parentType string) ([]*Forum,error)
//GetFirstChild(parentID int, parentType string) (*Forum,error)
CreateForum(forum_name string, forum_desc string, active bool, preset string) (int, error)
//QuickCreate(string, string, bool, string) (*Forum, error)
}
type StaticForumStore struct
{
forums []Forum // The IDs for a forum tend to be low and sequential for the most part, so we can get more performance out of using a slice instead of a map AND it has better concurrency
forums []*Forum // The IDs for a forum tend to be low and sequential for the most part, so we can get more performance out of using a slice instead of a map AND it has better concurrency
//fids []int
forumCapCount int
get *sql.Stmt
@ -45,11 +51,11 @@ type StaticForumStore struct
}
func NewStaticForumStore() *StaticForumStore {
get_stmt, err := qgen.Builder.SimpleSelect("forums","name, desc, active, preset, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime","fid = ?","","")
get_stmt, err := qgen.Builder.SimpleSelect("forums","name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime","fid = ?","","")
if err != nil {
log.Fatal(err)
}
get_all_stmt, err := qgen.Builder.SimpleSelect("forums","fid, name, desc, active, preset, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime","","fid ASC","")
get_all_stmt, err := qgen.Builder.SimpleSelect("forums","fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime","","fid ASC","")
if err != nil {
log.Fatal(err)
}
@ -60,11 +66,9 @@ func NewStaticForumStore() *StaticForumStore {
}
func (sfs *StaticForumStore) LoadForums() error {
//if debug {
log.Print("Adding the uncategorised forum")
//}
var forums []Forum = []Forum{
Forum{0,"uncategorised","Uncategorised","",uncategorised_forum_visible,"all",0,"","",0,"",0,""},
log.Print("Adding the uncategorised forum")
var forums []*Forum = []*Forum{
&Forum{0,"uncategorised","Uncategorised","",uncategorised_forum_visible,"all",0,"",0,"","",0,"",0,""},
}
rows, err := get_forums_stmt.Query()
@ -76,7 +80,7 @@ func (sfs *StaticForumStore) LoadForums() error {
var i int = 1
for ;rows.Next();i++ {
forum := Forum{ID:0,Active:true,Preset:"all"}
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
if err != nil {
return err
}
@ -88,16 +92,16 @@ func (sfs *StaticForumStore) LoadForums() error {
}
if forum.Name == "" {
if debug {
if debug_mode {
log.Print("Adding a placeholder forum")
}
} else {
log.Print("Adding the " + forum.Name + " forum")
}
forum.Slug = name_to_slug(forum.Name)
forum.LastTopicSlug = name_to_slug(forum.LastTopic)
forums = append(forums,forum)
forum.Link = build_forum_url(name_to_slug(forum.Name),forum.ID)
forum.LastTopicSlug = build_slug(name_to_slug(forum.LastTopic),forum.LastTopicID)
forums = append(forums,&forum)
}
err = rows.Err()
if err != nil {
@ -113,28 +117,28 @@ func (sfs *StaticForumStore) DirtyGet(id int) *Forum {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name!="") {
return &Forum{ID:-1,Name:""}
}
return &sfs.forums[id]
return sfs.forums[id]
}
func (sfs *StaticForumStore) Get(id int) (*Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name!="") {
return nil, err_noforum
}
return &sfs.forums[id], nil
return sfs.forums[id], nil
}
func (sfs *StaticForumStore) CascadeGet(id int) (*Forum, error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
return nil, err_noforum
}
return &sfs.forums[id], nil
return sfs.forums[id], nil
}
func (sfs *StaticForumStore) CascadeGetCopy(id int) (forum Forum, err error) {
if !((id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != "") {
return forum, err_noforum
}
return sfs.forums[id], nil
return *sfs.forums[id], nil
}
func (sfs *StaticForumStore) BypassGet(id int) (*Forum, error) {
@ -146,14 +150,55 @@ func (sfs *StaticForumStore) BypassGet(id int) (*Forum, error) {
return &forum, nil
}
func (sfs *StaticForumStore) Exists(id int) bool {
return (id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != ""
func (sfs *StaticForumStore) Load(id int) error {
var forum Forum = Forum{ID:id}
err := sfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
if err != nil {
return err
}
sfs.Set(&forum)
return nil
}
func (sfs *StaticForumStore) GetAll() ([]Forum,error) {
// TO-DO: Set should be able to add new indices not just replace existing ones for consistency with UserStore and TopicStore
func (sfs *StaticForumStore) Set(forum *Forum) error {
forum_update_mutex.Lock()
if !sfs.Exists(forum.ID) {
forum_update_mutex.Unlock()
return ErrNoRows
}
sfs.forums[forum.ID] = forum
forum_update_mutex.Unlock()
return nil
}
func (sfs *StaticForumStore) GetAll() ([]*Forum,error) {
return sfs.forums, nil
}
// TO-DO: Implement sub-forums.
/*func (sfs *StaticForumStore) GetChildren(parentID int, parentType string) ([]*Forum,error) {
return nil, nil
}
func (sfs *StaticForumStore) GetFirstChild(parentID int, parentType string) (*Forum,error) {
return nil, nil
}*/
// We can cheat slightly, as the StaticForumStore has all the IDs under the cap ;)
// Should we cache this? Well, it's only really used for superadmins right now.
func (sfs *StaticForumStore) GetAllIDs() ([]int,error) {
var max int = sfs.forumCapCount
var ids []int = make([]int,max)
for i := 0; i < max; i++ {
ids[i] = i
}
return ids, nil
}
func (sfs *StaticForumStore) Exists(id int) bool {
return (id <= sfs.forumCapCount) && (id >= 0) && sfs.forums[id].Name != ""
}
func (sfs *StaticForumStore) Delete(id int) error {
forum_update_mutex.Lock()
if !sfs.Exists(id) {
@ -240,10 +285,14 @@ func (sfs *StaticForumStore) CreateForum(forum_name string, forum_desc string, a
if err != nil {
return fid, err
}
sfs.forums[fid].Name = forum_name
sfs.forums[fid].Desc = forum_desc
sfs.forums[fid].Active = active
sfs.forums[fid].Preset = preset
forum, err := sfs.Get(fid)
if err != nil {
return 0, ErrCacheDesync
}
forum.Name = forum_name
forum.Desc = forum_desc
forum.Active = active
forum.Preset = preset
forum_update_mutex.Unlock()
return fid, nil
}
@ -260,8 +309,11 @@ func (sfs *StaticForumStore) CreateForum(forum_name string, forum_desc string, a
}
fid = int(fid64)
sfs.forums = append(sfs.forums, Forum{fid,name_to_slug(forum_name),forum_name,forum_desc,active,preset,0,"","",0,"",0,""})
sfs.forums = append(sfs.forums, &Forum{fid,name_to_slug(forum_name),forum_name,forum_desc,active,preset,0,"",0,"","",0,"",0,""})
sfs.forumCapCount++
// TO-DO: Add a GroupStore. How would it interact with the ForumStore?
permmap_to_query(preset_to_permmap(preset),fid)
forum_create_mutex.Unlock()
return fid, nil
}
@ -269,6 +321,10 @@ func (sfs *StaticForumStore) CreateForum(forum_name string, forum_desc string, a
func (sfs *StaticForumStore) fill_forum_id_gap(biggerID int, smallerID int) {
dummy := Forum{ID:0,Name:"",Active:false,Preset:"all"}
for i := smallerID; i > biggerID; i++ {
sfs.forums = append(sfs.forums, dummy)
sfs.forums = append(sfs.forums, &dummy)
}
}
// TO-DO: Work on MapForumStore
// TO-DO: Work on SqlForumStore

View File

@ -94,6 +94,7 @@ var delete_forum_stmt *sql.Stmt
var update_forum_stmt *sql.Stmt
var update_setting_stmt *sql.Stmt
var update_plugin_stmt *sql.Stmt
var update_plugin_install_stmt *sql.Stmt
var update_theme_stmt *sql.Stmt
var update_user_stmt *sql.Stmt
var update_group_perms_stmt *sql.Stmt
@ -112,7 +113,7 @@ var add_forum_perms_to_forum_members_stmt *sql.Stmt
var notify_watchers_stmt *sql.Stmt
func gen_mysql() (err error) {
if debug {
if debug_mode {
log.Print("Building the generated statements")
}
@ -165,13 +166,13 @@ func gen_mysql() (err error) {
}
log.Print("Preparing get_groups statement.")
get_groups_stmt, err = db.Prepare("SELECT `gid`,`name`,`permissions`,`is_mod`,`is_admin`,`is_banned`,`tag` FROM `users_groups`")
get_groups_stmt, err = db.Prepare("SELECT `gid`,`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag` FROM `users_groups`")
if err != nil {
return err
}
log.Print("Preparing get_forums statement.")
get_forums_stmt, err = db.Prepare("SELECT `fid`,`name`,`desc`,`active`,`preset`,`topicCount`,`lastTopic`,`lastTopicID`,`lastReplyer`,`lastReplyerID`,`lastTopicTime` FROM `forums` ORDER BY fid ASC")
get_forums_stmt, err = db.Prepare("SELECT `fid`,`name`,`desc`,`active`,`preset`,`parentID`,`parentType`,`topicCount`,`lastTopic`,`lastTopicID`,`lastReplyer`,`lastReplyerID`,`lastTopicTime` FROM `forums` ORDER BY fid ASC")
if err != nil {
return err
}
@ -183,7 +184,7 @@ func gen_mysql() (err error) {
}
log.Print("Preparing get_plugins statement.")
get_plugins_stmt, err = db.Prepare("SELECT `uname`,`active` FROM `plugins`")
get_plugins_stmt, err = db.Prepare("SELECT `uname`,`active`,`installed` FROM `plugins`")
if err != nil {
return err
}
@ -435,7 +436,7 @@ func gen_mysql() (err error) {
}
log.Print("Preparing add_plugin statement.")
add_plugin_stmt, err = db.Prepare("INSERT INTO `plugins`(`uname`,`active`) VALUES (?,?)")
add_plugin_stmt, err = db.Prepare("INSERT INTO `plugins`(`uname`,`active`,`installed`) VALUES (?,?,?)")
if err != nil {
return err
}
@ -644,6 +645,12 @@ func gen_mysql() (err error) {
return err
}
log.Print("Preparing update_plugin_install statement.")
update_plugin_install_stmt, err = db.Prepare("UPDATE `plugins` SET `installed` = ? WHERE `uname` = ?")
if err != nil {
return err
}
log.Print("Preparing update_theme statement.")
update_theme_stmt, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?")
if err != nil {

View File

@ -3,32 +3,48 @@
package main
//import "fmt"
import "sync"
import "strings"
import "sync"
import "errors"
import "net/http"
var ErrNoRoute = errors.New("That route doesn't exist.")
type GenRouter struct {
UploadHandler func(http.ResponseWriter, *http.Request)
sync.RWMutex // Temporary Fallback
old_routes map[string]func(http.ResponseWriter, *http.Request) // Temporary Fallback
extra_routes map[string]func(http.ResponseWriter, *http.Request, User)
sync.RWMutex
}
func NewGenRouter(uploads http.Handler) *GenRouter {
return &GenRouter{
UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP,
old_routes: make(map[string]func(http.ResponseWriter, *http.Request)),
extra_routes: make(map[string]func(http.ResponseWriter, *http.Request, User)),
}
}
func (router *GenRouter) Handle(_ string, _ http.Handler) {
}
func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request)) {
func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request, User)) {
router.Lock()
router.old_routes[pattern] = handle
router.extra_routes[pattern] = handle
router.Unlock()
}
func (router *GenRouter) RemoveFunc(pattern string) error {
router.Lock()
_, ok := router.extra_routes[pattern]
if !ok {
router.Unlock()
return ErrNoRoute
}
delete(router.extra_routes,pattern)
router.Unlock()
return nil
}
func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
//if req.URL.Path == "/" {
// default_route(w,req)
@ -49,117 +65,129 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
//fmt.Println("prefix:",prefix)
//fmt.Println("req.URL.Path:",req.URL.Path)
//fmt.Println("extra_data:",extra_data)
if prefix == "/static" {
req.URL.Path += extra_data
route_static(w,req)
return
}
// Deal with the session stuff, etc.
user, ok := PreRoute(w,req)
if !ok {
return
}
switch(prefix) {
case "/api":
route_api(w,req)
return
case "/static":
req.URL.Path += extra_data
route_static(w,req)
route_api(w,req,user)
return
case "/overview":
route_overview(w,req)
route_overview(w,req,user)
return
case "/forums":
route_forums(w,req)
route_forums(w,req,user)
return
case "/forum":
route_forum(w,req,extra_data)
route_forum(w,req,user,extra_data)
return
case "/report":
switch(req.URL.Path) {
case "/report/submit/":
route_report_submit(w,req,extra_data)
route_report_submit(w,req,user,extra_data)
return
}
case "/topics":
switch(req.URL.Path) {
case "/topics/create/":
route_topic_create(w,req,extra_data)
route_topic_create(w,req,user,extra_data)
return
default:
route_topics(w,req)
route_topics(w,req,user)
return
}
case "/panel":
switch(req.URL.Path) {
case "/panel/forums/":
route_panel_forums(w,req)
route_panel_forums(w,req,user)
return
case "/panel/forums/create/":
route_panel_forums_create_submit(w,req)
route_panel_forums_create_submit(w,req,user)
return
case "/panel/forums/delete/":
route_panel_forums_delete(w,req,extra_data)
route_panel_forums_delete(w,req,user,extra_data)
return
case "/panel/forums/delete/submit/":
route_panel_forums_delete_submit(w,req,extra_data)
route_panel_forums_delete_submit(w,req,user,extra_data)
return
case "/panel/forums/edit/":
route_panel_forums_edit(w,req,extra_data)
route_panel_forums_edit(w,req,user,extra_data)
return
case "/panel/forums/edit/submit/":
route_panel_forums_edit_submit(w,req,extra_data)
route_panel_forums_edit_submit(w,req,user,extra_data)
return
case "/panel/forums/edit/perms/submit/":
route_panel_forums_edit_perms_submit(w,req,extra_data)
route_panel_forums_edit_perms_submit(w,req,user,extra_data)
return
case "/panel/settings/":
route_panel_settings(w,req)
route_panel_settings(w,req,user)
return
case "/panel/settings/edit/":
route_panel_setting(w,req,extra_data)
route_panel_setting(w,req,user,extra_data)
return
case "/panel/settings/edit/submit/":
route_panel_setting_edit(w,req,extra_data)
route_panel_setting_edit(w,req,user,extra_data)
return
case "/panel/themes/":
route_panel_themes(w,req)
route_panel_themes(w,req,user)
return
case "/panel/themes/default/":
route_panel_themes_default(w,req,extra_data)
route_panel_themes_default(w,req,user,extra_data)
return
case "/panel/plugins/":
route_panel_plugins(w,req)
route_panel_plugins(w,req,user)
return
case "/panel/plugins/activate/":
route_panel_plugins_activate(w,req,extra_data)
route_panel_plugins_activate(w,req,user,extra_data)
return
case "/panel/plugins/deactivate/":
route_panel_plugins_deactivate(w,req,extra_data)
route_panel_plugins_deactivate(w,req,user,extra_data)
return
case "/panel/plugins/install/":
route_panel_plugins_install(w,req,user,extra_data)
return
case "/panel/users/":
route_panel_users(w,req)
route_panel_users(w,req,user)
return
case "/panel/users/edit/":
route_panel_users_edit(w,req,extra_data)
route_panel_users_edit(w,req,user,extra_data)
return
case "/panel/users/edit/submit/":
route_panel_users_edit_submit(w,req,extra_data)
route_panel_users_edit_submit(w,req,user,extra_data)
return
case "/panel/groups/":
route_panel_groups(w,req)
route_panel_groups(w,req,user)
return
case "/panel/groups/edit/":
route_panel_groups_edit(w,req,extra_data)
route_panel_groups_edit(w,req,user,extra_data)
return
case "/panel/groups/edit/perms/":
route_panel_groups_edit_perms(w,req,extra_data)
route_panel_groups_edit_perms(w,req,user,extra_data)
return
case "/panel/groups/edit/submit/":
route_panel_groups_edit_submit(w,req,extra_data)
route_panel_groups_edit_submit(w,req,user,extra_data)
return
case "/panel/groups/edit/perms/submit/":
route_panel_groups_edit_perms_submit(w,req,extra_data)
route_panel_groups_edit_perms_submit(w,req,user,extra_data)
return
case "/panel/groups/create/":
route_panel_groups_create_submit(w,req)
route_panel_groups_create_submit(w,req,user)
return
case "/panel/logs/mod/":
route_panel_logs_mod(w,req)
route_panel_logs_mod(w,req,user)
return
default:
route_panel(w,req)
route_panel(w,req,user)
return
}
case "/uploads":
@ -171,19 +199,19 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.UploadHandler(w,req)
return
case "":
default_route(w,req)
default_route(w,req,user)
return
//default: NotFound(w,req)
}
// A fallback for the routes which haven't been converted to the new router yet
// A fallback for the routes which haven't been converted to the new router yet or plugins
router.RLock()
handle, ok := router.old_routes[req.URL.Path]
handle, ok := router.extra_routes[req.URL.Path]
router.RUnlock()
if ok {
req.URL.Path += extra_data
handle(w,req)
handle(w,req,user)
return
}
NotFound(w,req)

View File

@ -2,7 +2,6 @@ package main
import (
"os"
"fmt"
"log"
"bytes"
"strings"
@ -12,6 +11,7 @@ import (
"time"
"net/http"
"net/http/httptest"
"html/template"
"io/ioutil"
"database/sql"
"runtime/pprof"
@ -21,11 +21,12 @@ import (
//"github.com/husobee/vestigo"
)
var db_test, db_prod *sql.DB
var gloinited bool = false
var db_test *sql.DB
var db_prod *sql.DB
var gloinited bool
func gloinit() {
debug = false
debug_mode = false
//nogrouplog = true
// init_database is a little noisy for a benchmark
@ -64,8 +65,6 @@ func gloinit() {
init_static_files()
external_sites["YT"] = "https://www.youtube.com/"
hooks["trow_assign"] = nil
hooks["rrow_assign"] = nil
//log.SetOutput(os.Stdout)
gloinited = true
}
@ -77,26 +76,34 @@ func init() {
func BenchmarkTopicTemplateSerial(b *testing.B) {
b.ReportAllocs()
user := User{0,"Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"Admin","admin@localhost",0,true,true,true,true,true,false,AllPerms,"",false,"","","","","",-1,58,"127.0.0.1"}
noticeList := []string{"test"}
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,"",false,"","","","","",-1,58,"127.0.0.1"}
topic := TopicUser{Title: "Lol",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"}
var replyList []Reply
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry","Jerry",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry2","Jerry2",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry3","Jerry3",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry4","Jerry4",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry5","Jerry5",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry6","Jerry6",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry7","Jerry7",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry8","Jerry8",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry9","Jerry9",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry10","Jerry10",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
tpage := TopicPage{"Topic Blah",user,noticeList,replyList,topic,1,1,false}
tpage2 := TopicPage{"Topic Blah",admin,noticeList,replyList,topic,1,1,false}
headerVars := HeaderVars{
NoticeList:[]string{"test"},
Stylesheets:[]string{"panel"},
Scripts:[]string{"whatever"},
Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
}
tpage := TopicPage{"Topic Blah",user,headerVars,replyList,topic,1,1,extData}
tpage2 := TopicPage{"Topic Blah",admin,headerVars,replyList,topic,1,1,extData}
w := ioutil.Discard
b.Run("compiled_useradmin", func(b *testing.B) {
@ -157,25 +164,33 @@ func BenchmarkTopicTemplateSerial(b *testing.B) {
func BenchmarkTopicsTemplateSerial(b *testing.B) {
b.ReportAllocs()
user := User{0,"Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"Admin","admin@localhost",0,true,true,true,true,true,false,AllPerms,"",false,"","","","","",-1,58,"127.0.0.1"}
noticeList := []string{"test"}
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,"",false,"","","","","",-1,58,"127.0.0.1"}
var topicList []TopicsRow
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserSlug:"admin-alice",CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
headerVars := HeaderVars{
NoticeList:[]string{"test"},
Stylesheets:[]string{"panel"},
Scripts:[]string{"whatever"},
Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
}
w := ioutil.Discard
tpage := TopicsPage{"Topic Blah",user,noticeList,topicList,nil}
tpage2 := TopicsPage{"Topic Blah",admin,noticeList,topicList,nil}
tpage := TopicsPage{"Topic Blah",user,headerVars,topicList,extData}
tpage2 := TopicsPage{"Topic Blah",admin,headerVars,topicList,extData}
b.Run("compiled_useradmin", func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -1206,7 +1221,7 @@ func TestLevels(t *testing.T) {
levels := getLevels(40)
for level, score := range levels {
sscore := strconv.FormatFloat(score, 'f', -1, 64)
log.Print("Level: " + strconv.Itoa(level) + " Score: " + sscore)
t.Log("Level: " + strconv.Itoa(level) + " Score: " + sscore)
}
}
@ -1224,10 +1239,8 @@ func TestStaticRoute(t *testing.T) {
static_handler.ServeHTTP(static_w,static_req)
if static_w.Code != 200 {
fmt.Println(static_w.Body)
panic("HTTP Error!")
t.Fatal(static_w.Body)
}
fmt.Println("No problems found in the static route!")
}
/*func TestTopicAdminRoute(t *testing.T) {
@ -1294,10 +1307,10 @@ func TestForumsAdminRoute(t *testing.T) {
admin, err := users.CascadeGet(1)
if err != nil {
panic(err)
t.Fatal(err)
}
if !admin.Is_Admin {
panic("UID1 is not an admin")
t.Fatal("UID1 is not an admin")
}
admin_uid_cookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year}
admin_session_cookie := http.Cookie{Name:"session",Value: admin.Session,Path:"/",MaxAge: year}
@ -1311,10 +1324,8 @@ func TestForumsAdminRoute(t *testing.T) {
forums_handler.ServeHTTP(forums_w,forums_req_admin)
if forums_w.Code != 200 {
fmt.Println(forums_w.Body)
panic("HTTP Error!")
t.Fatal(forums_w.Body)
}
fmt.Println("No problems found in the forums-admin route!")
}
func TestForumsGuestRoute(t *testing.T) {
@ -1331,10 +1342,8 @@ func TestForumsGuestRoute(t *testing.T) {
forums_handler.ServeHTTP(forums_w,forums_req)
if forums_w.Code != 200 {
fmt.Println(forums_w.Body)
panic("HTTP Error!")
t.Fatal(forums_w.Body)
}
fmt.Println("No problems found in the forums-guest route!")
}
/*func TestForumAdminRoute(t *testing.T) {
@ -1419,35 +1428,29 @@ func TestForumsGuestRoute(t *testing.T) {
db = db_prod
}*/
/*func TestRoute(t *testing.T) {
}*/
func TestSplittyThing(t *testing.T) {
fmt.Println("Splitty thing test")
var extra_data string
var path string = "/pages/hohoho"
fmt.Println("Raw Path:",path)
t.Log("Raw Path:",path)
if path[len(path) - 1] != '/' {
extra_data = path[strings.LastIndexByte(path,'/') + 1:]
path = path[:strings.LastIndexByte(path,'/') + 1]
}
fmt.Println("Path:", path)
fmt.Println("Extra Data:", extra_data)
fmt.Println("Path Bytes:", []byte(path))
fmt.Println("Extra Data Bytes:", []byte(extra_data))
t.Log("Path:", path)
t.Log("Extra Data:", extra_data)
t.Log("Path Bytes:", []byte(path))
t.Log("Extra Data Bytes:", []byte(extra_data))
fmt.Println("Splitty thing test")
t.Log("Splitty thing test")
path = "/topics/"
extra_data = ""
fmt.Println("Raw Path:",path)
t.Log("Raw Path:",path)
if path[len(path) - 1] != '/' {
extra_data = path[strings.LastIndexByte(path,'/') + 1:]
path = path[:strings.LastIndexByte(path,'/') + 1]
}
fmt.Println("Path:", path)
fmt.Println("Extra Data:", extra_data)
fmt.Println("Path Bytes:", []byte(path))
fmt.Println("Extra Data Bytes:", []byte(extra_data))
t.Log("Path:", path)
t.Log("Extra Data:", extra_data)
t.Log("Path Bytes:", []byte(path))
t.Log("Extra Data Bytes:", []byte(extra_data))
}

View File

@ -25,6 +25,8 @@ type Group struct
Tag string
Perms Perms
PermissionsText []byte
PluginPerms map[string]bool // Custom permissions defined by plugins. What if two plugins declare the same permission, but they handle them in incompatible ways? Very unlikely, we probably don't need to worry about this, the plugin authors should be aware of each other to some extent
PluginPermsText []byte
Forums []ForumPerms
CanSee []int // The IDs of the forums this group can see
}
@ -69,10 +71,17 @@ func create_group(group_name string, tag string, is_admin bool, is_mod bool, is_
return 0, err
}
gid = int(gid64)
perms := BlankPerms
var perms Perms = BlankPerms
var blankForums []ForumPerms
var blankIntList []int
groups = append(groups, Group{gid,group_name,is_mod,is_admin,is_banned,tag,perms,[]byte(permstr),blankForums,blankIntList})
var plugin_perms map[string]bool = make(map[string]bool)
var plugin_perms_bytes []byte = []byte("{}")
if vhooks["create_group_preappend"] != nil {
run_vhook("create_group_preappend", &plugin_perms, &plugin_perms_bytes)
}
groups = append(groups, Group{gid,group_name,is_mod,is_admin,is_banned,tag,perms,[]byte(permstr),plugin_perms,plugin_perms_bytes,blankForums,blankIntList})
group_create_mutex.Unlock()
// Generate the forum permissions based on the presets...
@ -117,5 +126,5 @@ func create_group(group_name string, tag string, is_admin bool, is_mod bool, is_
}
func group_exists(gid int) bool {
return (gid <= groupCapCount) && (gid > 0) && groups[gid].Name!=""
return (gid <= groupCapCount) && (gid > 0) && groups[gid].Name != ""
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 269 KiB

View File

@ -1,5 +1,7 @@
echo "Installing the MySQL Driver"
go get -u github.com/go-sql-driver/mysql
echo "Installing the PostgreSQL Driver"
go get -u github.com/lib/pq
echo "Installing bcrypt"
go get -u golang.org/x/crypto/bcrypt
echo "Installing gopsutil"

View File

@ -5,6 +5,11 @@ if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
go get -u github.com/lib/pq
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
go get -u golang.org/x/crypto/bcrypt
if %errorlevel% neq 0 (
pause

View File

@ -180,7 +180,7 @@ var noavatar = "https://api.adorable.io/avatars/285/{id}@" + site_url + ".png"
var items_per_page = 25
// Developer flag
var debug = true
var debug_mode = true
var super_debug = false
var profiling = false
`)

22
main.go
View File

@ -25,6 +25,7 @@ const saltLength int = 32
const sessionLength int = 80
var enable_websockets bool = false // Don't change this, the value is overwritten by an initialiser
var router *GenRouter
var startTime time.Time
var timeLocation *time.Location
var templates = template.New("")
@ -46,7 +47,7 @@ var template_create_topic_handle func(CreateTopicPage,io.Writer) = nil
func compile_templates() error {
var c CTemplateSet
user := User{62,"fake-user","Fake User","compiler@localhost",0,false,false,false,false,false,false,GuestPerms,"",false,"","","","","",0,0,"0.0.0.0.0"}
user := User{62,"fake-user","Fake User","compiler@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"0.0.0.0.0"}
headerVars := HeaderVars{
NoticeList:[]string{"test"},
Stylesheets:[]string{"panel"},
@ -79,7 +80,7 @@ func compile_templates() error {
for _, forum := range forums {
if forum.Active {
forumList = append(forumList,forum)
forumList = append(forumList,*forum)
}
}
varList = make(map[string]VarItem)
@ -87,13 +88,13 @@ func compile_templates() error {
forums_tmpl := c.compile_template("forums.html","templates/","ForumsPage",forums_page,varList)
var topicsList []TopicsRow
topicsList = append(topicsList,TopicsRow{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","admin-alice","Admin Alice","","",0,"","","","",58,"General"})
topicsList = append(topicsList,TopicsRow{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","admin-alice","Admin Alice","","",0,"","","","",58,"General","/forum/general.2"})
topics_page := TopicsPage{"Topic List",user,headerVars,topicsList,extData}
topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage",topics_page,varList)
var topicList []TopicUser
topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",default_group,"","",0,"","","","",58,false})
forum_item := Forum{1,"general","General Forum","Where the general stuff happens",true,"all",0,"","",0,"",0,""}
forum_item := Forum{1,"general","General Forum","Where the general stuff happens",true,"all",0,"",0,"","",0,"",0,""}
forum_page := ForumPage{"General Forum",user,headerVars,topicList,forum_item,1,1,extData}
forum_tmpl := c.compile_template("forum.html","templates/","ForumPage",forum_page,varList)
@ -117,7 +118,7 @@ func write_template(name string, content string) {
}
func init_templates() {
if debug {
if debug_mode {
log.Print("Initialising the template system")
}
compile_templates()
@ -133,7 +134,7 @@ func init_templates() {
fmap["divide"] = filler_func
// The interpreted templates...
if debug {
if debug_mode {
log.Print("Loading the template files...")
}
templates.Funcs(fmap)
@ -177,9 +178,6 @@ func main(){
init_static_files()
external_sites["YT"] = "https://www.youtube.com/"
hooks["trow_assign"] = nil
hooks["rrow_assign"] = nil
init_plugins()
log.Print("Initialising the widgets")
err = init_widgets()
@ -191,7 +189,7 @@ func main(){
auth = NewDefaultAuth()
log.Print("Initialising the router")
router := NewGenRouter(http.FileServer(http.Dir("./uploads")))
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
///router.HandleFunc("/static/", route_static)
///router.HandleFunc("/overview/", route_overview)
///router.HandleFunc("/topics/create/", route_topic_create)
@ -276,6 +274,10 @@ func main(){
//router.HandleFunc("/exit/", route_exit)
///router.HandleFunc("/", default_route)
router.HandleFunc("/ws/", route_websockets)
log.Print("Initialising the plugins")
init_plugins()
defer db.Close()
//if profiling {

74
misc_test.go Normal file
View File

@ -0,0 +1,74 @@
package main
import "testing"
// TO-DO: Generate a test database to work with rather than a live one
func TestUserStore(t *testing.T) {
if !gloinited {
gloinit()
}
if !plugins_inited {
init_plugins()
}
var user *User
var err error
user, err = users.CascadeGet(-1)
if err == nil {
t.Error("UID #-1 shouldn't exist")
} else if err != ErrNoRows {
t.Fatal(err)
}
user, err = users.CascadeGet(0)
if err == nil {
t.Error("UID #0 shouldn't exist")
} else if err != ErrNoRows {
t.Fatal(err)
}
user, err = users.CascadeGet(1)
if err == ErrNoRows {
t.Error("Couldn't find UID #1")
} else if err != nil {
t.Fatal(err)
}
if user.ID != 1 {
t.Error("user.ID doesn't not match the requested UID")
}
}
func TestSlugs(t *testing.T) {
var res string
var msgList []ME_Pair
msgList = addMEPair(msgList,"Unknown","unknown")
msgList = addMEPair(msgList,"Unknown2","unknown2")
msgList = addMEPair(msgList,"Unknown ","unknown")
msgList = addMEPair(msgList,"Unknown 2","unknown-2")
msgList = addMEPair(msgList,"Unknown 2","unknown-2")
msgList = addMEPair(msgList,"Admin Alice","admin-alice")
msgList = addMEPair(msgList,"Admin_Alice","adminalice")
msgList = addMEPair(msgList,"Admin_Alice-","adminalice")
msgList = addMEPair(msgList,"-Admin_Alice-","adminalice")
msgList = addMEPair(msgList,"-Admin@Alice-","adminalice")
msgList = addMEPair(msgList,"-Admin😀Alice-","adminalice")
msgList = addMEPair(msgList,"u","u")
msgList = addMEPair(msgList,"","untitled")
msgList = addMEPair(msgList," ","untitled")
msgList = addMEPair(msgList,"-","untitled")
msgList = addMEPair(msgList,"--","untitled")
msgList = addMEPair(msgList,"é","é")
msgList = addMEPair(msgList,"-é-","é")
for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'")
res = name_to_slug(item.Msg)
if res != item.Expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",item.Expects)
}
}
}

View File

@ -9,7 +9,7 @@ import (
"html"
)
func route_edit_topic(w http.ResponseWriter, r *http.Request) {
func route_edit_topic(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
@ -36,7 +36,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,old_topic.ParentID)
ok := SimpleForumSessionCheck(w,r,&user,old_topic.ParentID)
if !ok {
return
}
@ -109,7 +109,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) {
}
}
func route_delete_topic(w http.ResponseWriter, r *http.Request) {
func route_delete_topic(w http.ResponseWriter, r *http.Request, user User) {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/delete/submit/"):])
if err != nil {
PreError("The provided TopicID is not a valid number.",w,r)
@ -125,7 +125,7 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,topic.ParentID)
ok := SimpleForumSessionCheck(w,r,&user,topic.ParentID)
if !ok {
return
}
@ -176,7 +176,7 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) {
topics.Remove(tid)
}
func route_stick_topic(w http.ResponseWriter, r *http.Request) {
func route_stick_topic(w http.ResponseWriter, r *http.Request, user User) {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/stick/submit/"):])
if err != nil {
PreError("The provided TopicID is not a valid number.",w,r)
@ -192,7 +192,7 @@ func route_stick_topic(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,topic.ParentID)
ok := SimpleForumSessionCheck(w,r,&user,topic.ParentID)
if !ok {
return
}
@ -231,7 +231,7 @@ func route_stick_topic(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r,"/topic/" + strconv.Itoa(tid),http.StatusSeeOther)
}
func route_unstick_topic(w http.ResponseWriter, r *http.Request) {
func route_unstick_topic(w http.ResponseWriter, r *http.Request, user User) {
tid, err := strconv.Atoi(r.URL.Path[len("/topic/unstick/submit/"):])
if err != nil {
PreError("The provided TopicID is not a valid number.",w,r)
@ -247,7 +247,7 @@ func route_unstick_topic(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,topic.ParentID)
ok := SimpleForumSessionCheck(w,r,&user,topic.ParentID)
if !ok {
return
}
@ -286,7 +286,7 @@ func route_unstick_topic(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r,"/topic/" + strconv.Itoa(tid),http.StatusSeeOther)
}
func route_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
func route_reply_edit_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
@ -328,7 +328,7 @@ func route_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,fid)
ok := SimpleForumSessionCheck(w,r,&user,fid)
if !ok {
return
}
@ -344,7 +344,7 @@ func route_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
}
}
func route_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
func route_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
@ -380,7 +380,7 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
return
}
user, ok := SimpleForumSessionCheck(w,r,fid)
ok := SimpleForumSessionCheck(w,r,&user,fid)
if !ok {
return
}
@ -430,12 +430,7 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
}
}
func route_profile_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_profile_reply_edit_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
LocalError("Bad Form",w,r,user)
@ -479,12 +474,7 @@ func route_profile_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
}
}
func route_profile_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_profile_reply_delete_submit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm()
if err != nil {
LocalError("Bad Form",w,r,user)
@ -530,8 +520,8 @@ func route_profile_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
}
}
func route_ban(w http.ResponseWriter, r *http.Request) {
user, headerVars, ok := SessionCheck(w,r)
func route_ban(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
@ -560,14 +550,15 @@ func route_ban(w http.ResponseWriter, r *http.Request) {
yousure := AreYouSure{"/users/ban/submit/" + strconv.Itoa(uid),confirm_msg}
pi := Page{"Ban User",user,headerVars,tList,yousure}
if pre_render_hooks["pre_render_ban"] != nil {
if run_pre_render_hook("pre_render_ban", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"areyousure.html",pi)
}
func route_ban_submit(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_ban_submit(w http.ResponseWriter, r *http.Request, user User) {
if !user.Perms.BanUsers {
NoPermissions(w,r,user)
return
@ -637,11 +628,7 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther)
}
func route_unban(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_unban(w http.ResponseWriter, r *http.Request, user User) {
if !user.Perms.BanUsers {
NoPermissions(w,r,user)
return
@ -701,11 +688,7 @@ func route_unban(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther)
}
func route_activate(w http.ResponseWriter, r *http.Request) {
user, ok := SimpleSessionCheck(w,r)
if !ok {
return
}
func route_activate(w http.ResponseWriter, r *http.Request, user User) {
if !user.Perms.ActivateUsers {
NoPermissions(w,r,user)
return

View File

@ -1,5 +1,6 @@
/* Copyright Azareal 2016 - 2017 */
// +build !pgsql !sqlite !mssql
/* Copyright Azareal 2016 - 2018 */
package main
import "log"
@ -7,6 +8,7 @@ import "database/sql"
import _ "github.com/go-sql-driver/mysql"
import "./query_gen/lib"
var db_collation string = "utf8mb4_general_ci"
var get_activity_feed_by_watcher_stmt *sql.Stmt
var get_activity_count_by_watcher_stmt *sql.Stmt
var todays_post_count_stmt *sql.Stmt
@ -15,12 +17,13 @@ var todays_report_count_stmt *sql.Stmt
var todays_newuser_count_stmt *sql.Stmt
func _init_database() (err error) {
var _dbpassword string
if(dbpassword != ""){
dbpassword = ":" + dbpassword
_dbpassword = ":" + dbpassword
}
// Open the database connection
db, err = sql.Open("mysql",dbuser + dbpassword + "@tcp(" + dbhost + ":" + dbport + ")/" + dbname + "?collation=" + db_collation)
db, err = sql.Open("mysql",dbuser + _dbpassword + "@tcp(" + dbhost + ":" + dbport + ")/" + dbname + "?collation=" + db_collation)
if err != nil {
return err
}

View File

@ -29,6 +29,7 @@ CREATE TABLE `users_groups`(
`gid` int not null AUTO_INCREMENT,
`name` varchar(100) not null,
`permissions` text not null,
`plugin_perms` text not null,
`is_mod` tinyint DEFAULT 0 not null,
`is_admin` tinyint DEFAULT 0 not null,
`is_banned` tinyint DEFAULT 0 not null,
@ -50,6 +51,8 @@ CREATE TABLE `forums`(
`active` tinyint DEFAULT 1 not null,
`topicCount` int DEFAULT 0 not null,
`preset` varchar(100) DEFAULT '' not null,
`parentID` int DEFAULT 0 not null, /* TO-DO: Add support for subforums */
`parentType` varchar(50) DEFAULT '' not null,
`lastTopic` varchar(100) DEFAULT '' not null,
`lastTopicID` int DEFAULT 0 not null,
`lastReplyer` varchar(100) DEFAULT '' not null,
@ -165,6 +168,7 @@ CREATE TABLE `settings`(
CREATE TABLE `plugins`(
`uname` varchar(200) not null,
`active` tinyint DEFAULT 0 not null,
`installed` tinyint DEFAULT 0 not null,
unique(`uname`)
);
@ -251,12 +255,12 @@ PinTopic
CloseTopic
*/
INSERT INTO users_groups(`name`,`permissions`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}',1,1,"Admin");
INSERT INTO users_groups(`name`,`permissions`,`is_mod`,`tag`) VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":false,"EditUser":true,"EditUserEmail":false,"EditUserGroup":true,"ViewIPs":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}',1,"Mod");
INSERT INTO users_groups(`name`,`permissions`) VALUES ('Member','{"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}');
INSERT INTO users_groups(`name`,`permissions`,`is_banned`) VALUES ('Banned','{"ViewTopic":true}',1);
INSERT INTO users_groups(`name`,`permissions`) VALUES ('Awaiting Activation','{"ViewTopic":true}');
INSERT INTO users_groups(`name`,`permissions`,`tag`) VALUES ('Not Loggedin','{"ViewTopic":true}','Guest');
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,"Admin");
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`is_mod`,`tag`) VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":false,"EditUser":true,"EditUserEmail":false,"EditUserGroup":true,"ViewIPs":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,"Mod");
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`) VALUES ('Member','{"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}');
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`is_banned`) VALUES ('Banned','{"ViewTopic":true}','{}',1);
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`) VALUES ('Awaiting Activation','{"ViewTopic":true}','{}');
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`tag`) VALUES ('Not Loggedin','{"ViewTopic":true}','{}','Guest');
INSERT INTO forums(`name`,`active`) VALUES ('Reports',0);
INSERT INTO forums(`name`,`lastTopicTime`) VALUES ('General',NOW());

View File

@ -36,5 +36,5 @@ func(hub *WS_Hub) push_alerts(_ []int, _ string, _ string, _ int, _ int, _ int)
return ws_nouser
}
func route_websockets(_ http.ResponseWriter, _ *http.Request) {
func route_websockets(_ http.ResponseWriter, _ *http.Request, _ User) {
}

View File

@ -14,6 +14,7 @@ type HeaderVars struct
Scripts []string
Stylesheets []string
Widgets PageWidgets
ExtData ExtData
}
type PageWidgets struct
@ -22,6 +23,11 @@ type PageWidgets struct
RightSidebar template.HTML
}
/*type UnsafeExtData struct
{
items map[string]interface{} // Key: pluginname
}*/
type ExtData struct
{
items map[string]interface{} // Key: pluginname
@ -365,9 +371,8 @@ func shortcode_to_unicode(msg string) string {
}
func preparse_message(msg string) string {
if hooks["preparse_preassign"] != nil {
out := run_hook("preparse_preassign", msg)
msg = out.(string)
if sshooks["preparse_preassign"] != nil {
msg = run_sshook("preparse_preassign", msg)
}
return shortcode_to_unicode(msg)
}
@ -516,9 +521,9 @@ func parse_message(msg string/*, user User*/) string {
//fmt.Println(string(msgbytes))
//fmt.Println(msgbytes)
//fmt.Println(msgbytes[lastItem - 1])
//fmt.Println(lastItem - 1)
//fmt.Println(msgbytes[lastItem])
//fmt.Println(lastItem)
//fmt.Println("lastItem - 1",lastItem - 1)
//fmt.Println("msgbytes[lastItem]",msgbytes[lastItem])
//fmt.Println("lastItem",lastItem)
} else if msgbytes[i]=='h' || msgbytes[i]=='f' || msgbytes[i]=='g' {
//fmt.Println("IN hfg")
if msgbytes[i + 1]=='t' && msgbytes[i + 2]=='t' && msgbytes[i + 3]=='p' {
@ -556,8 +561,7 @@ func parse_message(msg string/*, user User*/) string {
}
if lastItem != i && len(outbytes) != 0 {
//fmt.Println("lastItem:")
//fmt.Println(msgbytes[lastItem])
//fmt.Println("lastItem:",msgbytes[lastItem])
//fmt.Println("lastItem index:")
//fmt.Println(lastItem)
//fmt.Println("i:")
@ -576,9 +580,8 @@ func parse_message(msg string/*, user User*/) string {
//fmt.Println(`"`+msg+`"`)
msg = strings.Replace(msg,"\n","<br>",-1)
if hooks["parse_assign"] != nil {
out := run_hook("parse_assign", msg)
msg = out.(string)
if sshooks["parse_assign"] != nil {
msg = run_sshook("parse_assign", msg)
}
return msg
}
@ -589,9 +592,8 @@ func regex_parse_message(msg string) string {
msg = strings.Replace(msg,":P","😛",-1)
msg = url_reg.ReplaceAllString(msg,"<a href=\"$2$3//$4\" rel=\"nofollow\">$2$3//$4</a>")
msg = strings.Replace(msg,"\n","<br>",-1)
if hooks["parse_assign"] != nil {
out := run_hook("parse_assign", msg)
msg = out.(string)
if sshooks["parse_assign"] != nil {
msg = run_sshook("parse_assign", msg)
}
return msg
}

View File

@ -17,8 +17,8 @@ import (
"github.com/shirou/gopsutil/mem"
)
func route_panel(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -184,11 +184,16 @@ func route_panel(w http.ResponseWriter, r *http.Request){
gridElements = append(gridElements, GridElement{"dash-postsperuser","5 posts / user / week",14,"grid_stat stat_disabled","","","Coming Soon!"/*"The average number of posts made by each active user over the past week"*/})
pi := PanelDashboardPage{"Control Panel Dashboard",user,headerVars,gridElements,extData}
if pre_render_hooks["pre_render_panel_dashboard"] != nil {
if run_pre_render_hook("pre_render_panel_dashboard", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"panel-dashboard.html",pi)
}
func route_panel_forums(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_forums(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -205,7 +210,7 @@ func route_panel_forums(w http.ResponseWriter, r *http.Request){
}
for _, forum := range forums {
if forum.Name != "" {
if forum.Name != "" && forum.ParentID == 0 {
fadmin := ForumAdmin{forum.ID,forum.Name,forum.Desc,forum.Active,forum.Preset,forum.TopicCount,preset_to_lang(forum.Preset)}
if fadmin.Preset == "" {
fadmin.Preset = "custom"
@ -214,14 +219,19 @@ func route_panel_forums(w http.ResponseWriter, r *http.Request){
}
}
pi := Page{"Forum Manager",user,headerVars,forumList,nil}
if pre_render_hooks["pre_render_panel_forums"] != nil {
if run_pre_render_hook("pre_render_panel_forums", w, r, &user, &pi) {
return
}
}
err = templates.ExecuteTemplate(w,"panel-forums.html",pi)
if err != nil {
InternalError(err,w,r)
}
}
func route_panel_forums_create_submit(w http.ResponseWriter, r *http.Request){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_forums_create_submit(w http.ResponseWriter, r *http.Request, user User){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -246,18 +256,17 @@ func route_panel_forums_create_submit(w http.ResponseWriter, r *http.Request){
factive := r.PostFormValue("forum-name")
active := (factive == "on" || factive == "1" )
fid, err := fstore.CreateForum(fname,fdesc,active,fpreset)
_, err = fstore.CreateForum(fname,fdesc,active,fpreset)
if err != nil {
InternalError(err,w,r)
return
}
permmap_to_query(preset_to_permmap(fpreset),fid)
http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther)
}
func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, sfid string){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, user User, sfid string){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -289,11 +298,16 @@ func route_panel_forums_delete(w http.ResponseWriter, r *http.Request, sfid stri
yousure := AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid),confirm_msg}
pi := Page{"Delete Forum",user,headerVars,tList,yousure}
if pre_render_hooks["pre_render_panel_delete_forum"] != nil {
if run_pre_render_hook("pre_render_panel_delete_forum", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"areyousure.html",pi)
}
func route_panel_forums_delete_submit(w http.ResponseWriter, r *http.Request, sfid string) {
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_forums_delete_submit(w http.ResponseWriter, r *http.Request, user User, sfid string) {
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -324,8 +338,8 @@ func route_panel_forums_delete_submit(w http.ResponseWriter, r *http.Request, sf
http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther)
}
func route_panel_forums_edit(w http.ResponseWriter, r *http.Request, sfid string) {
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_forums_edit(w http.ResponseWriter, r *http.Request, user User, sfid string) {
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -363,14 +377,19 @@ func route_panel_forums_edit(w http.ResponseWriter, r *http.Request, sfid string
}
pi := EditForumPage{"Forum Editor",user,headerVars,forum.ID,forum.Name,forum.Desc,forum.Active,forum.Preset,gplist,extData}
if pre_render_hooks["pre_render_panel_edit_forum"] != nil {
if run_pre_render_hook("pre_render_panel_edit_forum", w, r, &user, &pi) {
return
}
}
err = templates.ExecuteTemplate(w,"panel-forum-edit.html",pi)
if err != nil {
InternalError(err,w,r)
}
}
func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request, sfid string) {
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request, user User, sfid string) {
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -455,8 +474,8 @@ func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request, sfid
}
}
func route_panel_forums_edit_perms_submit(w http.ResponseWriter, r *http.Request, sfid string){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_forums_edit_perms_submit(w http.ResponseWriter, r *http.Request, user User, sfid string){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -537,8 +556,8 @@ func route_panel_forums_edit_perms_submit(w http.ResponseWriter, r *http.Request
}
}
func route_panel_settings(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_settings(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -588,11 +607,16 @@ func route_panel_settings(w http.ResponseWriter, r *http.Request){
}
pi := Page{"Setting Manager",user,headerVars,tList,settingList}
if pre_render_hooks["pre_render_panel_settings"] != nil {
if run_pre_render_hook("pre_render_panel_settings", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"panel-settings.html",pi)
}
func route_panel_setting(w http.ResponseWriter, r *http.Request, sname string){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_setting(w http.ResponseWriter, r *http.Request, user User, sname string){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -636,11 +660,16 @@ func route_panel_setting(w http.ResponseWriter, r *http.Request, sname string){
}
pi := Page{"Edit Setting",user,headerVars,itemList,setting}
if pre_render_hooks["pre_render_panel_setting"] != nil {
if run_pre_render_hook("pre_render_panel_setting", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"panel-setting.html",pi)
}
func route_panel_setting_edit(w http.ResponseWriter, r *http.Request, sname string) {
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_setting_edit(w http.ResponseWriter, r *http.Request, user User, sname string) {
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -693,8 +722,8 @@ func route_panel_setting_edit(w http.ResponseWriter, r *http.Request, sname stri
http.Redirect(w,r,"/panel/settings/",http.StatusSeeOther)
}
func route_panel_plugins(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_plugins(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -705,15 +734,22 @@ func route_panel_plugins(w http.ResponseWriter, r *http.Request){
var pluginList []interface{}
for _, plugin := range plugins {
//fmt.Println("plugin.Name",plugin.Name)
//fmt.Println("plugin.Installed",plugin.Installed)
pluginList = append(pluginList,plugin)
}
pi := Page{"Plugin Manager",user,headerVars,pluginList,nil}
if pre_render_hooks["pre_render_panel_plugins"] != nil {
if run_pre_render_hook("pre_render_panel_plugins", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"panel-plugins.html",pi)
}
func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request, uname string){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request, user User, uname string){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -726,18 +762,25 @@ func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request, uname
return
}
//fmt.Println("uname","'"+uname+"'")
plugin, ok := plugins[uname]
if !ok {
LocalError("The plugin isn't registered in the system",w,r,user)
return
}
if plugin.Installable && !plugin.Installed {
LocalError("You can't activate this plugin without installing it first",w,r,user)
return
}
var active bool
err := is_plugin_active_stmt.QueryRow(uname).Scan(&active)
if err != nil && err != ErrNoRows {
InternalError(err,w,r)
return
}
var has_plugin bool = (err == nil)
if plugins[uname].Activate != nil {
err = plugins[uname].Activate()
@ -747,19 +790,22 @@ func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request, uname
}
}
has_plugin := err != ErrNoRows
//fmt.Println("err",err)
//fmt.Println("active",active)
if has_plugin {
if active {
LocalError("The plugin is already active",w,r,user)
return
}
//fmt.Println("update_plugin")
_, err = update_plugin_stmt.Exec(1,uname)
if err != nil {
InternalError(err,w,r)
return
}
} else {
_, err := add_plugin_stmt.Exec(uname,1)
//fmt.Println("add_plugin")
_, err := add_plugin_stmt.Exec(uname,1,0)
if err != nil {
InternalError(err,w,r)
return
@ -769,12 +815,17 @@ func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request, uname
log.Print("Activating plugin '" + plugin.Name + "'")
plugin.Active = true
plugins[uname] = plugin
plugins[uname].Init()
err = plugins[uname].Init()
if err != nil {
LocalError(err.Error(),w,r,user)
return
}
http.Redirect(w,r,"/panel/plugins/",http.StatusSeeOther)
}
func route_panel_plugins_deactivate(w http.ResponseWriter, r *http.Request, uname string){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_plugins_deactivate(w http.ResponseWriter, r *http.Request, user User, uname string){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -821,8 +872,95 @@ func route_panel_plugins_deactivate(w http.ResponseWriter, r *http.Request, unam
http.Redirect(w,r,"/panel/plugins/",http.StatusSeeOther)
}
func route_panel_users(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_plugins_install(w http.ResponseWriter, r *http.Request, user User, uname string){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
if !user.Perms.ManagePlugins {
NoPermissions(w,r,user)
return
}
if r.FormValue("session") != user.Session {
SecurityError(w,r,user)
return
}
plugin, ok := plugins[uname]
if !ok {
LocalError("The plugin isn't registered in the system",w,r,user)
return
}
if !plugin.Installable {
LocalError("This plugin is not installable",w,r,user)
return
}
if plugin.Installed {
LocalError("This plugin has already been installed",w,r,user)
return
}
var active bool
err := is_plugin_active_stmt.QueryRow(uname).Scan(&active)
if err != nil && err != ErrNoRows {
InternalError(err,w,r)
return
}
var has_plugin bool = (err == nil)
if plugins[uname].Install != nil {
err = plugins[uname].Install()
if err != nil {
LocalError(err.Error(),w,r,user)
return
}
}
if plugins[uname].Activate != nil {
err = plugins[uname].Activate()
if err != nil {
LocalError(err.Error(),w,r,user)
return
}
}
if has_plugin {
_, err = update_plugin_install_stmt.Exec(1,uname)
if err != nil {
InternalError(err,w,r)
return
}
_, err = update_plugin_stmt.Exec(1,uname)
if err != nil {
InternalError(err,w,r)
return
}
} else {
_, err := add_plugin_stmt.Exec(uname,1,1)
if err != nil {
InternalError(err,w,r)
return
}
}
log.Print("Installing plugin '" + plugin.Name + "'")
plugin.Active = true
plugin.Installed = true
plugins[uname] = plugin
err = plugins[uname].Init()
if err != nil {
LocalError(err.Error(),w,r,user)
return
}
http.Redirect(w,r,"/panel/plugins/",http.StatusSeeOther)
}
func route_panel_users(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -866,14 +1004,19 @@ func route_panel_users(w http.ResponseWriter, r *http.Request){
}
pi := Page{"User Manager",user,headerVars,userList,nil}
if pre_render_hooks["pre_render_panel_users"] != nil {
if run_pre_render_hook("pre_render_panel_users", w, r, &user, &pi) {
return
}
}
err = templates.ExecuteTemplate(w,"panel-users.html",pi)
if err != nil {
InternalError(err,w,r)
}
}
func route_panel_users_edit(w http.ResponseWriter, r *http.Request,suid string){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_users_edit(w http.ResponseWriter, r *http.Request, user User, suid string){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -915,14 +1058,19 @@ func route_panel_users_edit(w http.ResponseWriter, r *http.Request,suid string){
}
pi := Page{"User Editor",user,headerVars,groupList,targetUser}
if pre_render_hooks["pre_render_panel_edit_user"] != nil {
if run_pre_render_hook("pre_render_panel_edit_user", w, r, &user, &pi) {
return
}
}
err = templates.ExecuteTemplate(w,"panel-user-edit.html",pi)
if err != nil {
InternalError(err,w,r)
}
}
func route_panel_users_edit_submit(w http.ResponseWriter, r *http.Request, suid string){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_users_edit_submit(w http.ResponseWriter, r *http.Request, user User, suid string){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1016,8 +1164,8 @@ func route_panel_users_edit_submit(w http.ResponseWriter, r *http.Request, suid
http.Redirect(w,r,"/panel/users/edit/" + strconv.Itoa(targetUser.ID),http.StatusSeeOther)
}
func route_panel_groups(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_groups(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1052,11 +1200,16 @@ func route_panel_groups(w http.ResponseWriter, r *http.Request){
//fmt.Printf("%+v\n", groupList)
pi := Page{"Group Manager",user,headerVars,groupList,nil}
if pre_render_hooks["pre_render_panel_groups"] != nil {
if run_pre_render_hook("pre_render_panel_groups", w, r, &user, &pi) {
return
}
}
templates.ExecuteTemplate(w,"panel-groups.html",pi)
}
func route_panel_groups_edit(w http.ResponseWriter, r *http.Request, sgid string){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_groups_edit(w http.ResponseWriter, r *http.Request, user User, sgid string){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1103,14 +1256,19 @@ func route_panel_groups_edit(w http.ResponseWriter, r *http.Request, sgid string
disable_rank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6)
pi := EditGroupPage{"Group Editor",user,headerVars,group.ID,group.Name,group.Tag,rank,disable_rank,extData}
if pre_render_hooks["pre_render_panel_edit_group"] != nil {
if run_pre_render_hook("pre_render_panel_edit_group", w, r, &user, &pi) {
return
}
}
err = templates.ExecuteTemplate(w,"panel-group-edit.html",pi)
if err != nil {
InternalError(err,w,r)
}
}
func route_panel_groups_edit_perms(w http.ResponseWriter, r *http.Request, sgid string){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_groups_edit_perms(w http.ResponseWriter, r *http.Request, user User, sgid string){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1176,14 +1334,19 @@ func route_panel_groups_edit_perms(w http.ResponseWriter, r *http.Request, sgid
globalPerms = append(globalPerms, NameLangToggle{"ViewIPs",GetGlobalPermPhrase("ViewIPs"),group.Perms.ViewIPs})
pi := EditGroupPermsPage{"Group Editor",user,headerVars,group.ID,group.Name,localPerms,globalPerms,extData}
if pre_render_hooks["pre_render_panel_edit_group_perms"] != nil {
if run_pre_render_hook("pre_render_panel_edit_group_perms", w, r, &user, &pi) {
return
}
}
err = templates.ExecuteTemplate(w,"panel-group-edit-perms.html",pi)
if err != nil {
InternalError(err,w,r)
}
}
func route_panel_groups_edit_submit(w http.ResponseWriter, r *http.Request, sgid string){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_groups_edit_submit(w http.ResponseWriter, r *http.Request, user User, sgid string){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1314,8 +1477,8 @@ func route_panel_groups_edit_submit(w http.ResponseWriter, r *http.Request, sgid
http.Redirect(w,r,"/panel/groups/edit/" + strconv.Itoa(gid),http.StatusSeeOther)
}
func route_panel_groups_edit_perms_submit(w http.ResponseWriter, r *http.Request, sgid string){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_groups_edit_perms_submit(w http.ResponseWriter, r *http.Request, user User, sgid string){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1335,7 +1498,7 @@ func route_panel_groups_edit_perms_submit(w http.ResponseWriter, r *http.Request
}
if !group_exists(gid) {
//fmt.Println("aaaaa monsters")
//fmt.Println("aaaaa monsters o.o")
NotFound(w,r)
return
}
@ -1390,8 +1553,8 @@ func route_panel_groups_edit_perms_submit(w http.ResponseWriter, r *http.Request
http.Redirect(w,r,"/panel/groups/edit/perms/" + strconv.Itoa(gid),http.StatusSeeOther)
}
func route_panel_groups_create_submit(w http.ResponseWriter, r *http.Request){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_groups_create_submit(w http.ResponseWriter, r *http.Request, user User){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1441,8 +1604,8 @@ func route_panel_groups_create_submit(w http.ResponseWriter, r *http.Request){
http.Redirect(w,r,"/panel/groups/edit/" + strconv.Itoa(gid),http.StatusSeeOther)
}
func route_panel_themes(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_themes(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1465,14 +1628,19 @@ func route_panel_themes(w http.ResponseWriter, r *http.Request){
}
pi := ThemesPage{"Theme Manager",user,headerVars,pThemeList,vThemeList,extData}
if pre_render_hooks["pre_render_panel_themes"] != nil {
if run_pre_render_hook("pre_render_panel_themes", w, r, &user, &pi) {
return
}
}
err := templates.ExecuteTemplate(w,"panel-themes.html",pi)
if err != nil {
log.Print(err)
}
}
func route_panel_themes_default(w http.ResponseWriter, r *http.Request, uname string){
user, ok := SimplePanelSessionCheck(w,r)
func route_panel_themes_default(w http.ResponseWriter, r *http.Request, user User, uname string){
ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1549,8 +1717,8 @@ func route_panel_themes_default(w http.ResponseWriter, r *http.Request, uname st
http.Redirect(w,r,"/panel/themes/",http.StatusSeeOther)
}
func route_panel_logs_mod(w http.ResponseWriter, r *http.Request){
user, headerVars, ok := PanelSessionCheck(w,r)
func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
@ -1642,6 +1810,11 @@ func route_panel_logs_mod(w http.ResponseWriter, r *http.Request){
}
pi := LogsPage{"Moderation Logs",user,headerVars,logs,extData}
if pre_render_hooks["pre_render_panel_mod_log"] != nil {
if run_pre_render_hook("pre_render_panel_mod_log", w, r, &user, &pi) {
return
}
}
err = templates.ExecuteTemplate(w,"panel-modlogs.html",pi)
if err != nil {
log.Print(err)

View File

@ -15,6 +15,7 @@ var ReadReplyForumPerms ForumPerms
var ReadWriteForumPerms ForumPerms
var AllPerms Perms
var AllForumPerms ForumPerms
var AllPluginPerms map[string]bool = make(map[string]bool)
var LocalPermList []string = []string{
"ViewTopic",
@ -90,7 +91,7 @@ type Perms struct
CloseTopic bool
//CloseOwnTopic bool
ExtData map[string]bool
//ExtData map[string]bool
}
/* Inherit from group permissions for ones we don't have */
@ -116,7 +117,7 @@ type ForumPerms struct
func init() {
BlankPerms = Perms{
ExtData: make(map[string]bool),
//ExtData: make(map[string]bool),
}
BlankForumPerms = ForumPerms{
@ -125,7 +126,7 @@ func init() {
GuestPerms = Perms{
ViewTopic: true,
ExtData: make(map[string]bool),
//ExtData: make(map[string]bool),
}
AllPerms = Perms{
@ -160,7 +161,7 @@ func init() {
PinTopic: true,
CloseTopic: true,
ExtData: make(map[string]bool),
//ExtData: make(map[string]bool),
}
AllForumPerms = ForumPerms{
@ -204,7 +205,7 @@ func init() {
guest_user.Perms = GuestPerms
if debug {
if debug_mode {
fmt.Printf("Guest Perms: ")
fmt.Printf("%+v\n", GuestPerms)
fmt.Printf("All Perms: ")
@ -303,7 +304,7 @@ func permmap_to_query(permmap map[string]ForumPerms, fid int) error {
}
func rebuild_forum_permissions(fid int) error {
if debug {
if debug_mode {
log.Print("Loading the forum permissions")
}
forums, err := fstore.GetAll()
@ -317,7 +318,7 @@ func rebuild_forum_permissions(fid int) error {
}
defer rows.Close()
if debug {
if debug_mode {
log.Print("Updating the forum permissions")
}
for rows.Next() {
@ -341,7 +342,7 @@ func rebuild_forum_permissions(fid int) error {
forum_perms[gid][fid] = pperms
}
for gid, _ := range groups {
if debug {
if debug_mode {
log.Print("Updating the forum permissions for Group #" + strconv.Itoa(gid))
}
var blank_list []ForumPerms
@ -389,7 +390,7 @@ func build_forum_permissions() error {
}
defer rows.Close()
if debug {
if debug_mode {
log.Print("Adding the forum permissions")
}
// Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice
@ -415,7 +416,7 @@ func build_forum_permissions() error {
forum_perms[gid][fid] = pperms
}
for gid, _ := range groups {
if debug {
if debug_mode {
log.Print("Adding the forum permissions for Group #" + strconv.Itoa(gid) + " - " + groups[gid].Name)
}
//groups[gid].Forums = append(groups[gid].Forums,BlankForumPerms) // GID 0. No longer needed now that Uncategorised occupies that slot
@ -533,7 +534,9 @@ func rebuild_group_permissions(gid int) error {
return err
}
tmp_perms := Perms{ExtData: make(map[string]bool)}
tmp_perms := Perms{
//ExtData: make(map[string]bool),
}
err = json.Unmarshal(permstr, &tmp_perms)
if err != nil {
return err
@ -542,3 +545,33 @@ func rebuild_group_permissions(gid int) error {
groups[gid].Perms = tmp_perms
return nil
}
func override_perms(perms *Perms, status bool) {
if status {
*perms = AllPerms
} else {
*perms = BlankPerms
}
}
// TO-DO: We need a better way of overriding forum perms rather than setting them one by one
func override_forum_perms(perms *Perms, status bool) {
perms.ViewTopic = status
perms.LikeItem = status
perms.CreateTopic = status
perms.EditTopic = status
perms.DeleteTopic = status
perms.CreateReply = status
perms.EditReply = status
perms.DeleteReply = status
perms.PinTopic = status
perms.CloseTopic = status
}
func register_plugin_perm(name string) {
AllPluginPerms[name] = true
}
func deregister_plugin_perm(name string) {
delete(AllPluginPerms,name)
}

58
pgsql.go Normal file
View File

@ -0,0 +1,58 @@
// +build pgsql
/* Copyright Azareal 2016 - 2018 */
/* Super experimental and incomplete. DON'T USE IT YET! */
package main
import "strings"
import "database/sql"
import _ "github.com/lib/pq"
import "./query_gen/lib"
// TO-DO: Add support for SSL for all database drivers, not just pgsql
var db_sslmode = "disable" // verify-full
var get_activity_feed_by_watcher_stmt *sql.Stmt
var get_activity_count_by_watcher_stmt *sql.Stmt
var todays_post_count_stmt *sql.Stmt
var todays_topic_count_stmt *sql.Stmt
var todays_report_count_stmt *sql.Stmt
var todays_newuser_count_stmt *sql.Stmt
// Note to self: PostgreSQL listens on a different port than MySQL does
func _init_database() (err error) {
// TO-DO: Investigate connect_timeout to see what it does exactly and whether it's relevant to us
var _dbpassword string
if(dbpassword != ""){
_dbpassword = " password='" + _escape_bit(dbpassword) + "'"
}
db, err = sql.Open("postgres", "host='" + _escape_bit(dbhost) + "' port='" + _escape_bit(dbport) + "' user='" + _escape_bit(dbuser) + "' dbname='" + _escape_bit(dbname) + "'" + _dbpassword + " sslmode='" + db_sslmode + "'")
if err != nil {
return err
}
// TO-DO: Get the version number
// Set the number of max open connections. How many do we need? Might need to do some tests.
db.SetMaxOpenConns(64)
err = gen_pgsql()
if err != nil {
return err
}
// Ready the query builder
qgen.Builder.SetConn(db)
err = qgen.Builder.SetAdapter("pgsql")
if err != nil {
return err
}
// TO-DO Handle the queries which the query generator can't handle yet
return nil
}
func _escape_bit(bit string) string {
// TO-DO: Write a custom parser, so that backslashes work properly in the sql.Open string. Do something similar for the database driver, if possible?
return strings.Replace(bit,"'","\\'",-1)
}

View File

@ -5,59 +5,97 @@ import "sync"
// I wish we had constant maps x.x
var phrase_mutex sync.RWMutex
var perm_phrase_mutex sync.RWMutex
var phrases map[string]string
var global_perm_phrases map[string]string = map[string]string{
"BanUsers": "Can ban users",
"ActivateUsers": "Can activate users",
"EditUser": "Can edit users",
"EditUserEmail": "Can change a user's email",
"EditUserPassword": "Can change a user's password",
"EditUserGroup": "Can change a user's group",
"EditUserGroupSuperMod": "Can edit super-mods",
"EditUserGroupAdmin": "Can edit admins",
"EditGroup": "Can edit groups",
"EditGroupLocalPerms": "Can edit a group's minor perms",
"EditGroupGlobalPerms": "Can edit a group's global perms",
"EditGroupSuperMod": "Can edit super-mod groups",
"EditGroupAdmin": "Can edit admin groups",
"ManageForums": "Can manage forums",
"EditSettings": "Can edit settings",
"ManageThemes": "Can manage themes",
"ManagePlugins": "Can manage plugins",
"ViewAdminLogs": "Can view the administrator action logs",
"ViewIPs": "Can view IP addresses",
var change_langpack_mutex sync.Mutex
var currentLanguage string = "english"
var currentLangPack *LanguagePack
type LevelPhrases struct
{
Level string
LevelMax string
// Override the phrase for individual levels, if the phrases exist
Levels []string // index = level
}
var local_perm_phrases map[string]string = map[string]string{
"ViewTopic": "Can view topics",
"LikeItem": "Can like items",
"CreateTopic": "Can create topics",
"EditTopic": "Can edit topics",
"DeleteTopic": "Can delete topics",
"CreateReply": "Can create replies",
"EditReply": "Can edit replies",
"DeleteReply": "Can delete replies",
"PinTopic": "Can pin topics",
"CloseTopic": "Can lock topics",
type LanguagePack struct
{
Name string
Phrases map[string]string // Should we use a sync map or a struct for these? It would be nice, if we could keep all the phrases consistent.
LevelPhrases LevelPhrases
GlobalPermPhrases map[string]string
LocalPermPhrases map[string]string
}
// TO-DO: Move the english language pack into it's own file and just keep the common logic here
var langpacks map[string]*LanguagePack = map[string]*LanguagePack{
"english": &LanguagePack{
Name: "english",
// We'll be implementing the level phrases in the software proper very very soon!
LevelPhrases: LevelPhrases{
Level: "Level {0}",
LevelMax: "", // Add a max level setting?
},
GlobalPermPhrases: map[string]string{
"BanUsers": "Can ban users",
"ActivateUsers": "Can activate users",
"EditUser": "Can edit users",
"EditUserEmail": "Can change a user's email",
"EditUserPassword": "Can change a user's password",
"EditUserGroup": "Can change a user's group",
"EditUserGroupSuperMod": "Can edit super-mods",
"EditUserGroupAdmin": "Can edit admins",
"EditGroup": "Can edit groups",
"EditGroupLocalPerms": "Can edit a group's minor perms",
"EditGroupGlobalPerms": "Can edit a group's global perms",
"EditGroupSuperMod": "Can edit super-mod groups",
"EditGroupAdmin": "Can edit admin groups",
"ManageForums": "Can manage forums",
"EditSettings": "Can edit settings",
"ManageThemes": "Can manage themes",
"ManagePlugins": "Can manage plugins",
"ViewAdminLogs": "Can view the administrator action logs",
"ViewIPs": "Can view IP addresses",
},
LocalPermPhrases: map[string]string{
"ViewTopic": "Can view topics",
"LikeItem": "Can like items",
"CreateTopic": "Can create topics",
"EditTopic": "Can edit topics",
"DeleteTopic": "Can delete topics",
"CreateReply": "Can create replies",
"EditReply": "Can edit replies",
"DeleteReply": "Can delete replies",
"PinTopic": "Can pin topics",
"CloseTopic": "Can lock topics",
},
},
}
func init() {
currentLangPack = langpacks[currentLanguage]
}
// We might not need to use a mutex for this, we shouldn't need to change the phrases after start-up, and when we do we could overwrite the entire map
func GetPhrase(name string) (string,bool) {
phrase_mutex.RLock()
defer perm_phrase_mutex.RUnlock()
res, ok := phrases[name]
res, ok := currentLangPack.Phrases[name]
return res, ok
}
func GetPhraseUnsafe(name string) (string,bool) {
res, ok := phrases[name]
res, ok := currentLangPack.Phrases[name]
return res, ok
}
func GetGlobalPermPhrase(name string) string {
perm_phrase_mutex.RLock()
defer perm_phrase_mutex.RUnlock()
res, ok := global_perm_phrases[name]
res, ok := currentLangPack.GlobalPermPhrases[name]
if !ok {
return "{name}"
}
@ -67,7 +105,7 @@ func GetGlobalPermPhrase(name string) string {
func GetLocalPermPhrase(name string) string {
perm_phrase_mutex.RLock()
defer perm_phrase_mutex.RUnlock()
res, ok := local_perm_phrases[name]
res, ok := currentLangPack.LocalPermPhrases[name]
if !ok {
return "{name}"
}
@ -75,9 +113,21 @@ func GetLocalPermPhrase(name string) string {
}
func AddPhrase() {
}
func DeletePhrase() {
}
func ChangeLanguagePack(name string) (exists bool) {
change_langpack_mutex.Lock()
pack, ok := langpacks[name]
if !ok {
change_langpack_mutex.Unlock()
return false
}
currentLangPack = pack
change_langpack_mutex.Unlock()
return true
}

View File

@ -1,4 +1,5 @@
package main
//import "log"
//import "fmt"
import "bytes"
@ -10,6 +11,7 @@ import "math/rand"
var random *rand.Rand
var bbcode_invalid_number []byte
var bbcode_no_negative []byte
var bbcode_missing_tag []byte
var bbcode_bold *regexp.Regexp
@ -19,18 +21,20 @@ var bbcode_strikethrough *regexp.Regexp
var bbcode_url *regexp.Regexp
var bbcode_url_label *regexp.Regexp
var bbcode_quotes *regexp.Regexp
var bbcode_code *regexp.Regexp
func init() {
plugins["bbcode"] = NewPlugin("bbcode","BBCode","Azareal","http://github.com/Azareal","","","",init_bbcode,nil,deactivate_bbcode)
plugins["bbcode"] = NewPlugin("bbcode","BBCode","Azareal","http://github.com/Azareal","","","",init_bbcode,nil,deactivate_bbcode,nil,nil)
}
func init_bbcode() {
func init_bbcode() error {
//plugins["bbcode"].AddHook("parse_assign", bbcode_parse_without_code)
plugins["bbcode"].AddHook("parse_assign", bbcode_full_parse)
bbcode_invalid_number = []byte("<span style='color: red;'>[Invalid Number]</span>")
bbcode_no_negative = []byte("<span style='color: red;'>[No Negative Numbers]</span>")
bbcode_missing_tag = []byte("<span style='color: red;'>[Missing Tag]</span>")
bbcode_bold = regexp.MustCompile(`(?s)\[b\](.*)\[/b\]`)
bbcode_italic = regexp.MustCompile(`(?s)\[i\](.*)\[/i\]`)
bbcode_underline = regexp.MustCompile(`(?s)\[u\](.*)\[/u\]`)
@ -39,8 +43,10 @@ func init_bbcode() {
bbcode_url = regexp.MustCompile(`\[url\]` + urlpattern + `\[/url\]`)
bbcode_url_label = regexp.MustCompile(`(?s)\[url=` + urlpattern + `\](.*)\[/url\]`)
bbcode_quotes = regexp.MustCompile(`\[quote\](.*)\[/quote\]`)
bbcode_code = regexp.MustCompile(`\[code\](.*)\[/code\]`)
random = rand.New(rand.NewSource(time.Now().UnixNano()))
return nil
}
func deactivate_bbcode() {
@ -48,26 +54,22 @@ func deactivate_bbcode() {
plugins["bbcode"].RemoveHook("parse_assign", bbcode_full_parse)
}
func bbcode_regex_parse(data interface{}) interface{} {
msg := data.(string)
func bbcode_regex_parse(msg string) string {
msg = bbcode_bold.ReplaceAllString(msg,"<b>$1</b>")
msg = bbcode_italic.ReplaceAllString(msg,"<i>$1</i>")
msg = bbcode_underline.ReplaceAllString(msg,"<u>$1</u>")
msg = bbcode_strikethrough.ReplaceAllString(msg,"<s>$1</s>")
msg = bbcode_url.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"nofollow\">$1$2//$3</i>")
msg = bbcode_url_label.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"nofollow\">$4</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class=\"postQuote\">$1</span>")
msg = bbcode_url.ReplaceAllString(msg,"<a href=''$1$2//$3' rel='nofollow'>$1$2//$3</i>")
msg = bbcode_url_label.ReplaceAllString(msg,"<a href=''$1$2//$3' rel='nofollow'>$4</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class='postQuote'>$1</span>")
//msg = bbcode_code.ReplaceAllString(msg,"<span class='codequotes'>$1</span>")
return msg
}
// Only does the simple BBCode like [u], [b], [i] and [s]
func bbcode_simple_parse(data interface{}) interface{} {
msg := data.(string)
func bbcode_simple_parse(msg string) string {
var has_u, has_b, has_i, has_s bool
msgbytes := []byte(msg)
has_u := false
has_b := false
has_i := false
has_s := false
for i := 0; (i + 2) < len(msgbytes); i++ {
if msgbytes[i] == '[' && msgbytes[i + 2] == ']' {
if msgbytes[i + 1] == 'b' && !has_b {
@ -90,24 +92,35 @@ func bbcode_simple_parse(data interface{}) interface{} {
i += 2
}
}
// There's an unclosed tag in there x.x
if has_i || has_u || has_b || has_s {
closer := []byte("</u></i></b></s>")
msgbytes = append(msgbytes, closer...)
close_under := []byte("</u>")
close_italic := []byte("</i>")
close_bold := []byte("</b>")
close_strike := []byte("</s>")
if has_i {
msgbytes = append(msgbytes, close_italic...)
}
if has_u {
msgbytes = append(msgbytes, close_under...)
}
if has_b {
msgbytes = append(msgbytes, close_bold...)
}
if has_s {
msgbytes = append(msgbytes, close_strike...)
}
}
return string(msgbytes)
}
// Here for benchmarking purposes. Might add a plugin setting for disabling [code] as it has it's paws everywhere
func bbcode_parse_without_code(data interface{}) interface{} {
msg := data.(string)
func bbcode_parse_without_code(msg string) string {
var has_u, has_b, has_i, has_s bool
var complex_bbc bool
msgbytes := []byte(msg)
has_u := false
has_b := false
has_i := false
has_s := false
complex_bbc := false
for i := 0; (i + 3) < len(msgbytes); i++ {
if msgbytes[i] == '[' {
if msgbytes[i + 2] != ']' {
@ -159,42 +172,49 @@ func bbcode_parse_without_code(data interface{}) interface{} {
}
}
}
// There's an unclosed tag in there x.x
if has_i || has_u || has_b || has_s {
closer := []byte("</u></i></b></s>")
msgbytes = append(msgbytes, closer...)
close_under := []byte("</u>")
close_italic := []byte("</i>")
close_bold := []byte("</b>")
close_strike := []byte("</s>")
if has_i {
msgbytes = append(bytes.TrimSpace(msgbytes), close_italic...)
}
if has_u {
msgbytes = append(bytes.TrimSpace(msgbytes), close_under...)
}
if has_b {
msgbytes = append(bytes.TrimSpace(msgbytes), close_bold...)
}
if has_s {
msgbytes = append(bytes.TrimSpace(msgbytes), close_strike...)
}
}
// Copy the new complex parser over once the rough edges have been smoothed over
if complex_bbc {
msg = bbcode_url.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"nofollow\">$1$2//$3</i>")
msg = bbcode_url_label.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"nofollow\">$4</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class=\"postQuote\">$1</span>")
msg = bbcode_url.ReplaceAllString(msg,"<a href='$1$2//$3' rel='nofollow'>$1$2//$3</i>")
msg = bbcode_url_label.ReplaceAllString(msg,"<a href='$1$2//$3' rel='nofollow'>$4</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class='postQuote'>$1</span>")
msg = bbcode_code.ReplaceAllString(msg,"<span class='codequotes'>$1</span>")
}
return string(msgbytes)
}
// Does every type of BBCode
func bbcode_full_parse(data interface{}) interface{} {
msg := data.(string)
//fmt.Println("BBCode PrePre String:")
//fmt.Println("`"+msg+"`")
//fmt.Println("----")
func bbcode_full_parse(msg string) string {
var has_u, has_b, has_i, has_s, has_c bool
var complex_bbc bool
msgbytes := []byte(msg)
has_u := false
has_b := false
has_i := false
has_s := false
has_c := false
complex_bbc := false
msgbytes = append(msgbytes,space_gap...)
//fmt.Println("BBCode Simple Pre:")
//fmt.Println("`"+string(msgbytes)+"`")
//fmt.Println("----")
for i := 0; i < len(msgbytes); i++ {
if msgbytes[i] == '[' {
if msgbytes[i + 2] != ']' {
@ -266,13 +286,28 @@ func bbcode_full_parse(data interface{}) interface{} {
}
}
}
// There's an unclosed tag in there x.x
// There's an unclosed tag in there somewhere x.x
if has_i || has_u || has_b || has_s {
closer := []byte("</u></i></b></s>")
msgbytes = append(msgbytes, closer...)
close_under := []byte("</u>")
close_italic := []byte("</i>")
close_bold := []byte("</b>")
close_strike := []byte("</s>")
if has_i {
msgbytes = append(bytes.TrimSpace(msgbytes), close_italic...)
}
if has_u {
msgbytes = append(bytes.TrimSpace(msgbytes), close_under...)
}
if has_b {
msgbytes = append(bytes.TrimSpace(msgbytes), close_bold...)
}
if has_s {
msgbytes = append(bytes.TrimSpace(msgbytes), close_strike...)
}
msgbytes = append(msgbytes,space_gap...)
}
if complex_bbc {
i := 0
var start, lastTag int
@ -290,7 +325,6 @@ func bbcode_full_parse(data interface{}) interface{} {
outbytes = append(outbytes, msgbytes[lastTag:i]...)
i = start
i += partial_url_bytes_len(msgbytes[start:])
//fmt.Println("Partial Bytes:")
//fmt.Println(string(msgbytes[start:]))
//fmt.Println("-----")
@ -301,7 +335,7 @@ func bbcode_full_parse(data interface{}) interface{} {
outbytes = append(outbytes, invalid_url...)
goto MainLoop
}
outbytes = append(outbytes, url_open...)
outbytes = append(outbytes, msgbytes[start:i]...)
outbytes = append(outbytes, url_open2...)
@ -327,13 +361,19 @@ func bbcode_full_parse(data interface{}) interface{} {
goto OuterComplex
}
}
number, err := strconv.ParseInt(string(msgbytes[start:i]),10,64)
if err != nil {
outbytes = append(outbytes, bbcode_invalid_number...)
goto MainLoop
}
// TO-DO: Add support for negative numbers?
if number < 0 {
outbytes = append(outbytes, bbcode_no_negative...)
goto MainLoop
}
dat := []byte(strconv.FormatInt((random.Int63n(number)),10))
outbytes = append(outbytes, dat...)
//log.Print("Outputted the random number")
@ -346,28 +386,25 @@ func bbcode_full_parse(data interface{}) interface{} {
//fmt.Println(string(outbytes))
if lastTag != i {
outbytes = append(outbytes, msgbytes[lastTag:]...)
//fmt.Println("Outbytes:")
//fmt.Println(`"`+string(outbytes)+`"`)
//fmt.Println("Outbytes:",`"`+string(outbytes)+`"`)
//fmt.Println("----")
}
if len(outbytes) != 0 {
//fmt.Println("BBCode Post:")
//fmt.Println(`"`+string(outbytes[0:len(outbytes) - 10])+`"`)
//fmt.Println("BBCode Post:",`"`+string(outbytes[0:len(outbytes) - 10])+`"`)
//fmt.Println("----")
msg = string(outbytes[0:len(outbytes) - 10])
} else {
msg = string(msgbytes[0:len(msgbytes) - 10])
}
//msg = bbcode_url.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"nofollow\">$1$2//$3</i>")
msg = bbcode_url_label.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"nofollow\">$4</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class=\"postQuote\">$1</span>")
// Convert [code] into class="codequotes"
//fmt.Println("guuuaaaa")
msg = bbcode_url_label.ReplaceAllString(msg,"<a href='$1$2//$3' rel='nofollow'>$4</i>")
msg = bbcode_quotes.ReplaceAllString(msg,"<span class='postQuote'>$1</span>")
msg = bbcode_code.ReplaceAllString(msg,"<span class='codequotes'>$1</span>")
} else {
msg = string(msgbytes[0:len(msgbytes) - 10])
}
return msg
}

View File

@ -1,13 +1,13 @@
package main
func init() {
plugins["helloworld"] = NewPlugin("helloworld","Hello World","Azareal","http://github.com/Azareal","","","",init_helloworld,nil,deactivate_helloworld)
plugins["helloworld"] = NewPlugin("helloworld","Hello World","Azareal","http://github.com/Azareal","","","",init_helloworld,nil,deactivate_helloworld,nil,nil)
}
// init_helloworld is separate from init() as we don't want the plugin to run if the plugin is disabled
func init_helloworld() {
func init_helloworld() error {
plugins["helloworld"].AddHook("rrow_assign", helloworld_reply)
// TO-DO: Add a route injection example here
return nil
}
func deactivate_helloworld() {
@ -15,9 +15,9 @@ func deactivate_helloworld() {
}
func helloworld_reply(data interface{}) interface{} {
reply := data.(Reply)
reply := data.(*Reply)
reply.Content = "Hello World!"
reply.ContentHtml = "Hello World!"
reply.Tag = "Auto"
return reply
return nil
}

View File

@ -2,7 +2,7 @@ package main
//import "fmt"
import "regexp"
//import "strings"
import "strings"
var markdown_max_depth int = 25 // How deep the parser will go when parsing Markdown strings
var markdown_unclosed_element []byte
@ -19,10 +19,10 @@ var markdown_strike *regexp.Regexp
var markdown_underline *regexp.Regexp
func init() {
plugins["markdown"] = NewPlugin("markdown","Markdown","Azareal","http://github.com/Azareal","","","",init_markdown,nil,deactivate_markdown)
plugins["markdown"] = NewPlugin("markdown","Markdown","Azareal","http://github.com/Azareal","","","",init_markdown,nil,deactivate_markdown,nil,nil)
}
func init_markdown() {
func init_markdown() error {
//plugins["markdown"].AddHook("parse_assign", markdown_regex_parse)
plugins["markdown"].AddHook("parse_assign", markdown_parse)
@ -44,6 +44,7 @@ func init_markdown() {
markdown_strike = regexp.MustCompile(`\~(.*)\~`)
//markdown_underline = regexp.MustCompile(`\_\_(.*)\_\_`)
markdown_underline = regexp.MustCompile(`\_(.*)\_`)
return nil
}
func deactivate_markdown() {
@ -51,8 +52,7 @@ func deactivate_markdown() {
plugins["markdown"].RemoveHook("parse_assign", markdown_parse)
}
func markdown_regex_parse(data interface{}) interface{} {
msg := data.(string)
func markdown_regex_parse(msg string) string {
msg = markdown_bold_italic.ReplaceAllString(msg,"<i><b>$1</b></i>")
msg = markdown_bold.ReplaceAllString(msg,"<b>$1</b>")
msg = markdown_italic.ReplaceAllString(msg,"<i>$1</i>")
@ -64,8 +64,8 @@ func markdown_regex_parse(data interface{}) interface{} {
// An adapter for the parser, so that the parser can call itself recursively.
// This is less for the simple Markdown elements like bold and italics and more for the really complicated ones I plan on adding at some point.
func markdown_parse(data interface{}) interface{} {
return _markdown_parse(data.(string) + " ",0)
func markdown_parse(msg string) string {
return strings.TrimSpace(_markdown_parse(msg + " ",0))
}
// Under Construction!
@ -277,12 +277,12 @@ func _markdown_parse(msg string, n int) string {
outbytes = append(outbytes, msg[sIndex:lIndex]...)
if bold {
outbytes = append(outbytes, markdown_bold_tag_close...)
}
if italic {
outbytes = append(outbytes, markdown_italic_tag_close...)
}
if bold {
outbytes = append(outbytes, markdown_bold_tag_close...)
}
lastElement = index
index--

View File

@ -3,31 +3,35 @@ package main
func init() {
/*
The UName field should match the name in the URL minus plugin_ and the file extension. The same name as the map index. Please choose a unique name which won't clash with any other plugins.
The Name field is for the friendly name of the plugin shown to the end-user.
The Author field is the author of this plugin. The one who created it.
The URL field is for the URL pointing to the location where you can download this plugin.
The Settings field points to the route for managing the settings for this plugin. Coming soon.
The Tag field is for providing a tiny snippet of information separate from the description.
The Type field is for the type of the plugin. This gets changed to "go" automatically and we would suggest leaving "".
The Init field is for the initialisation handler which is called by the software to run this plugin. This expects a function. You should add your hooks, init logic, initial queries, etc. in said function.
The Activate field is for the handler which is called by the software when the admin hits the Activate button in the control panel. This is separate from the Init handler which is called upon the start of the server and upon activation. Use nil if you don't have a handler for this.
The Deactivate field is for the handler which is called by the software when the admin hits the Deactivate button in the control panel. You should clean-up any resources you have allocated, remove any hooks, close any statements, etc. within this handler.
The Installation field is for one-off installation logic such as creating tables. You will need to run the seperate uninstallation function for that.
That Uninstallation field which is currently unused is for not only deactivating this plugin, but for purging any data associated with it such a new tables or data produced by the end-user.
*/
plugins["skeleton"] = NewPlugin("skeleton","Skeleton","Azareal","","","","",init_skeleton, activate_skeleton, deactivate_skeleton)
plugins["skeleton"] = NewPlugin("skeleton","Skeleton","Azareal","","","","",init_skeleton, activate_skeleton, deactivate_skeleton,nil,nil)
}
func init_skeleton() {}
func init_skeleton() error { return nil; }
/* Any errors encountered while trying to activate the plugin are reported back to the admin and the activation is aborted */
// Any errors encountered while trying to activate the plugin are reported back to the admin and the activation is aborted
func activate_skeleton() error { return nil; }
func deactivate_skeleton() {}
func deactivate_skeleton() {}

643
plugin_socialgroups.go Normal file
View File

@ -0,0 +1,643 @@
package main
import (
//"fmt"
"bytes"
"strings"
"strconv"
"errors"
"context"
"net/http"
"html"
"html/template"
"database/sql"
"./query_gen/lib"
)
var socialgroups_list_stmt *sql.Stmt
var socialgroups_member_list_stmt *sql.Stmt
var socialgroups_member_list_join_stmt *sql.Stmt
var socialgroups_get_member_stmt *sql.Stmt
var socialgroups_get_group_stmt *sql.Stmt
var socialgroups_create_group_stmt *sql.Stmt
var socialgroups_attach_forum_stmt *sql.Stmt
var socialgroups_unattach_forum_stmt *sql.Stmt
var socialgroups_add_member_stmt *sql.Stmt
// TO-DO: Add a better way of splitting up giant plugins like this
type SocialGroup struct
{
ID int
Link string
Name string
Desc string
Active bool
Privacy int /* 0: Public, 1: Protected, 2: Private */
MemberCount int
Owner int
Backdrop string
CreatedAt string
LastUpdateTime string
MainForum *Forum
Forums []*Forum
ExtData ExtData
}
type SocialGroupPage struct
{
Title string
CurrentUser User
Header HeaderVars
ItemList []TopicUser
Forum Forum
SocialGroup SocialGroup
Page int
LastPage int
ExtData ExtData
}
type SocialGroupListPage struct
{
Title string
CurrentUser User
Header HeaderVars
GroupList []SocialGroup
ExtData ExtData
}
type SocialGroupMemberListPage struct
{
Title string
CurrentUser User
Header HeaderVars
ItemList []SocialGroupMember
SocialGroup SocialGroup
Page int
LastPage int
ExtData ExtData
}
type SocialGroupMember struct
{
Link string
Rank int /* 0: Member. 1: Mod. 2: Admin. */
RankString string /* Member, Mod, Admin, Owner */
PostCount int
JoinedAt string
Offline bool // TO-DO: Need to track the online states of members when WebSockets are enabled
User User
}
func init() {
plugins["socialgroups"] = NewPlugin("socialgroups","Social Groups","Azareal","http://github.com/Azareal","","","",init_socialgroups,nil,deactivate_socialgroups,install_socialgroups,nil)
}
func init_socialgroups() (err error) {
plugins["socialgroups"].AddHook("intercept_build_widgets", socialgroups_widgets)
plugins["socialgroups"].AddHook("trow_assign", socialgroups_trow_assign)
plugins["socialgroups"].AddHook("topic_create_pre_loop", socialgroups_topic_create_pre_loop)
plugins["socialgroups"].AddHook("pre_render_view_forum", socialgroups_pre_render_view_forum)
plugins["socialgroups"].AddHook("simple_forum_check_pre_perms", socialgroups_forum_check)
plugins["socialgroups"].AddHook("forum_check_pre_perms", socialgroups_forum_check)
// TO-DO: Auto-grant this perm to admins upon installation?
register_plugin_perm("CreateSocialGroup")
router.HandleFunc("/groups/", socialgroups_group_list)
router.HandleFunc("/group/", socialgroups_view_group)
router.HandleFunc("/group/create/", socialgroups_create_group)
router.HandleFunc("/group/create/submit/", socialgroups_create_group_submit)
router.HandleFunc("/group/members/", socialgroups_member_list)
socialgroups_list_stmt, err = qgen.Builder.SimpleSelect("socialgroups","sgid, name, desc, active, privacy, owner, memberCount, createdAt, lastUpdateTime","","","")
if err != nil {
return err
}
socialgroups_get_group_stmt, err = qgen.Builder.SimpleSelect("socialgroups","name, desc, active, privacy, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime","sgid = ?","","")
if err != nil {
return err
}
socialgroups_member_list_stmt, err = qgen.Builder.SimpleSelect("socialgroups_members","sgid, uid, rank, posts, joinedAt","","","")
if err != nil {
return err
}
socialgroups_member_list_join_stmt, err = qgen.Builder.SimpleLeftJoin("socialgroups_members","users","users.uid, socialgroups_members.rank, socialgroups_members.posts, socialgroups_members.joinedAt, users.name, users.avatar","socialgroups_members.uid = users.uid","socialgroups_members.sgid = ?","socialgroups_members.rank DESC, socialgroups_members.joinedat ASC","")
if err != nil {
return err
}
socialgroups_get_member_stmt, err = qgen.Builder.SimpleSelect("socialgroups_members","rank, posts, joinedAt","sgid = ? AND uid = ?","","")
if err != nil {
return err
}
socialgroups_create_group_stmt, err = qgen.Builder.SimpleInsert("socialgroups","name, desc, active, privacy, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime","?,?,?,?,?,1,?,'',NOW(),NOW()")
if err != nil {
return err
}
socialgroups_attach_forum_stmt, err = qgen.Builder.SimpleUpdate("forums","parentID = ?, parentType = 'socialgroup'","fid = ?")
if err != nil {
return err
}
socialgroups_unattach_forum_stmt, err = qgen.Builder.SimpleUpdate("forums","parentID = 0, parentType = ''","fid = ?")
if err != nil {
return err
}
socialgroups_add_member_stmt, err = qgen.Builder.SimpleInsert("socialgroups_members","sgid, uid, rank, posts, joinedAt","?,?,?,0,NOW()")
if err != nil {
return err
}
return nil
}
func deactivate_socialgroups() {
plugins["socialgroups"].RemoveHook("intercept_build_widgets", socialgroups_widgets)
plugins["socialgroups"].RemoveHook("trow_assign", socialgroups_trow_assign)
plugins["socialgroups"].RemoveHook("topic_create_pre_loop", socialgroups_topic_create_pre_loop)
plugins["socialgroups"].RemoveHook("pre_render_view_forum", socialgroups_pre_render_view_forum)
plugins["socialgroups"].RemoveHook("simple_forum_check_pre_perms", socialgroups_forum_check)
plugins["socialgroups"].RemoveHook("forum_check_pre_perms", socialgroups_forum_check)
deregister_plugin_perm("CreateSocialGroup")
_ = router.RemoveFunc("/groups/")
_ = router.RemoveFunc("/group/")
_ = router.RemoveFunc("/group/create/")
_ = router.RemoveFunc("/group/create/submit/")
_ = socialgroups_list_stmt.Close()
_ = socialgroups_member_list_stmt.Close()
_ = socialgroups_member_list_join_stmt.Close()
_ = socialgroups_get_member_stmt.Close()
_ = socialgroups_get_group_stmt.Close()
_ = socialgroups_create_group_stmt.Close()
_ = socialgroups_attach_forum_stmt.Close()
_ = socialgroups_unattach_forum_stmt.Close()
_ = socialgroups_add_member_stmt.Close()
}
// TO-DO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process
func install_socialgroups() error {
sg_table_stmt, err := qgen.Builder.CreateTable("socialgroups","utf8mb4","utf8mb4_general_ci",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"sgid","int",0,false,true,""},
qgen.DB_Table_Column{"name","varchar",100,false,false,""},
qgen.DB_Table_Column{"desc","varchar",200,false,false,""},
qgen.DB_Table_Column{"active","tinyint",1,false,false,""},
qgen.DB_Table_Column{"privacy","tinyint",1,false,false,""},
qgen.DB_Table_Column{"owner","int",0,false,false,""},
qgen.DB_Table_Column{"memberCount","int",0,false,false,""},
qgen.DB_Table_Column{"mainForum","int",0,false,false,"0"}, // The board the user lands on when they click ona group, we'll make it possible for group admins to change what users land on
//qgen.DB_Table_Column{"boards","varchar",200,false,false,""}, // Cap the max number of boards at 8 to avoid overflowing the confines of a 64-bit integer?
qgen.DB_Table_Column{"backdrop","varchar",200,false,false,""}, // File extension for the uploaded file, or an external link
qgen.DB_Table_Column{"createdAt","createdAt",0,false,false,""},
qgen.DB_Table_Column{"lastUpdateTime","datetime",0,false,false,""},
},
[]qgen.DB_Table_Key{
qgen.DB_Table_Key{"sgid","primary"},
},
)
if err != nil {
return err
}
_, err = sg_table_stmt.Exec()
if err != nil {
return err
}
sg_members_table_stmt, err := qgen.Builder.CreateTable("socialgroups_members","","",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"sgid","int",0,false,false,""},
qgen.DB_Table_Column{"uid","int",0,false,false,""},
qgen.DB_Table_Column{"rank","int",0,false,false,"0"}, /* 0: Member. 1: Mod. 2: Admin. */
qgen.DB_Table_Column{"posts","int",0,false,false,"0"}, /* Per-Group post count. Should we do some sort of score system? */
qgen.DB_Table_Column{"joinedAt","datetime",0,false,false,""},
},
[]qgen.DB_Table_Key{},
)
if err != nil {
return err
}
_, err = sg_members_table_stmt.Exec()
return err
}
// TO-DO; Implement an uninstallation system into Gosora. And a better installation system.
func uninstall_socialgroups() error {
return nil
}
// TO-DO: Do this properly via the widget system
func socialgroups_common_area_widgets(headerVars *HeaderVars) {
// TO-DO: Hot Groups? Featured Groups? Official Groups?
var b bytes.Buffer
var menu WidgetMenu = WidgetMenu{"Social Groups",[]WidgetMenuItem{
WidgetMenuItem{"Create Group","/group/create/",false},
}}
err := templates.ExecuteTemplate(&b,"widget_menu.html",menu)
if err != nil {
LogError(err)
return
}
if themes[defaultTheme].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[defaultTheme].Sidebars == "right" || themes[defaultTheme].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
}
}
// TO-DO: Do this properly via the widget system
// TO-DO: Make a better more customisable group widget system
func socialgroups_group_widgets(headerVars *HeaderVars, sgItem SocialGroup) (success bool) {
return false // Disabled until the next commit
var b bytes.Buffer
var menu WidgetMenu = WidgetMenu{"Group Options",[]WidgetMenuItem{
WidgetMenuItem{"Join","/group/join/" + strconv.Itoa(sgItem.ID),false},
WidgetMenuItem{"Members","/group/members/" + strconv.Itoa(sgItem.ID),false},
}}
err := templates.ExecuteTemplate(&b,"widget_menu.html",menu)
if err != nil {
LogError(err)
return false
}
if themes[defaultTheme].Sidebars == "left" {
headerVars.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[defaultTheme].Sidebars == "right" || themes[defaultTheme].Sidebars == "both" {
headerVars.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
} else {
return false
}
return true
}
/*
Custom Pages
*/
func socialgroups_group_list(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
socialgroups_common_area_widgets(&headerVars)
rows, err := socialgroups_list_stmt.Query()
if err != nil && err != ErrNoRows {
InternalError(err,w,r)
return
}
var sgList []SocialGroup
for rows.Next() {
sgItem := SocialGroup{ID:0}
err := rows.Scan(&sgItem.ID, &sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Owner, &sgItem.MemberCount, &sgItem.CreatedAt, &sgItem.LastUpdateTime)
if err != nil {
InternalError(err,w,r)
return
}
sgItem.Link = socialgroups_build_group_url(name_to_slug(sgItem.Name),sgItem.ID)
sgList = append(sgList,sgItem)
}
err = rows.Err()
if err != nil {
InternalError(err,w,r)
return
}
rows.Close()
pi := SocialGroupListPage{"Group List",user,headerVars,sgList,extData}
err = templates.ExecuteTemplate(w,"socialgroups_group_list.html", pi)
if err != nil {
InternalError(err,w,r)
}
}
func socialgroups_view_group(w http.ResponseWriter, r *http.Request, user User) {
// SEO URLs...
halves := strings.Split(r.URL.Path[len("/group/"):],".")
if len(halves) < 2 {
halves = append(halves,halves[0])
}
sgid, err := strconv.Atoi(halves[1])
if err != nil {
PreError("Not a valid group ID",w,r)
return
}
var sgItem SocialGroup = SocialGroup{ID:sgid}
var mainForum int
err = socialgroups_get_group_stmt.QueryRow(sgid).Scan(&sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Owner, &sgItem.MemberCount, &mainForum, &sgItem.Backdrop, &sgItem.CreatedAt, &sgItem.LastUpdateTime)
if err != nil {
LocalError("Bad group",w,r,user)
return
}
if !sgItem.Active {
NotFound(w,r)
}
// Re-route the request to route_forums
var ctx context.Context = context.WithValue(r.Context(),"socialgroups_current_group",sgItem)
route_forum(w,r.WithContext(ctx),user,strconv.Itoa(mainForum))
}
func socialgroups_create_group(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
// TO-DO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] {
NoPermissions(w,r,user)
return
}
socialgroups_common_area_widgets(&headerVars)
pi := Page{"Create Group",user,headerVars,tList,nil}
err := templates.ExecuteTemplate(w,"socialgroups_create_group.html", pi)
if err != nil {
InternalError(err,w,r)
}
}
func socialgroups_create_group_submit(w http.ResponseWriter, r *http.Request, user User) {
// TO-DO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateSocialGroup"] {
NoPermissions(w,r,user)
return
}
var group_active bool = true
var group_name string = html.EscapeString(r.PostFormValue("group_name"))
var group_desc string = html.EscapeString(r.PostFormValue("group_desc"))
var gprivacy string = r.PostFormValue("group_privacy")
var group_privacy int
switch(gprivacy) {
case "0": group_privacy = 0 // Public
case "1": group_privacy = 1 // Protected
case "2": group_privacy = 2 // private
default: group_privacy = 0
}
// Create the backing forum
fid, err := fstore.CreateForum(group_name,"",true,"")
if err != nil {
InternalError(err,w,r)
return
}
res, err := socialgroups_create_group_stmt.Exec(group_name, group_desc, group_active, group_privacy, user.ID, fid)
if err != nil {
InternalError(err,w,r)
return
}
lastId, err := res.LastInsertId()
if err != nil {
InternalError(err,w,r)
return
}
// Add the main backing forum to the forum list
err = socialgroups_attach_forum(int(lastId),fid)
if err != nil {
InternalError(err,w,r)
return
}
_, err = socialgroups_add_member_stmt.Exec(lastId,user.ID,2)
if err != nil {
InternalError(err,w,r)
return
}
http.Redirect(w,r,socialgroups_build_group_url(name_to_slug(group_name),int(lastId)), http.StatusSeeOther)
}
func socialgroups_member_list(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
}
// SEO URLs...
halves := strings.Split(r.URL.Path[len("/group/members/"):],".")
if len(halves) < 2 {
halves = append(halves,halves[0])
}
sgid, err := strconv.Atoi(halves[1])
if err != nil {
PreError("Not a valid group ID",w,r)
return
}
var sgItem SocialGroup = SocialGroup{ID:sgid}
var mainForum int // Unused
err = socialgroups_get_group_stmt.QueryRow(sgid).Scan(&sgItem.Name, &sgItem.Desc, &sgItem.Active, &sgItem.Privacy, &sgItem.Owner, &sgItem.MemberCount, &mainForum, &sgItem.Backdrop, &sgItem.CreatedAt, &sgItem.LastUpdateTime)
if err != nil {
LocalError("Bad group",w,r,user)
return
}
sgItem.Link = socialgroups_build_group_url(name_to_slug(sgItem.Name),sgItem.ID)
socialgroups_group_widgets(&headerVars, sgItem)
rows, err := socialgroups_member_list_join_stmt.Query(sgid)
if err != nil && err != ErrNoRows {
InternalError(err,w,r)
return
}
var sgMembers []SocialGroupMember
for rows.Next() {
sgMember := SocialGroupMember{PostCount:0}
err := rows.Scan(&sgMember.User.ID,&sgMember.Rank,&sgMember.PostCount,&sgMember.JoinedAt,&sgMember.User.Name, &sgMember.User.Avatar)
if err != nil {
InternalError(err,w,r)
return
}
sgMember.Link = build_profile_url(name_to_slug(sgMember.User.Name),sgMember.User.ID)
if sgMember.User.Avatar != "" {
if sgMember.User.Avatar[0] == '.' {
sgMember.User.Avatar = "/uploads/avatar_" + strconv.Itoa(sgMember.User.ID) + sgMember.User.Avatar
}
} else {
sgMember.User.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(sgMember.User.ID),1)
}
sgMember.JoinedAt, _ = relative_time(sgMember.JoinedAt)
if sgItem.Owner == sgMember.User.ID {
sgMember.RankString = "Owner"
} else {
switch(sgMember.Rank) {
case 0: sgMember.RankString = "Member"
case 1: sgMember.RankString = "Mod"
case 2: sgMember.RankString = "Admin"
}
}
sgMembers = append(sgMembers,sgMember)
}
err = rows.Err()
if err != nil {
InternalError(err,w,r)
return
}
rows.Close()
pi := SocialGroupMemberListPage{"Group Member List",user,headerVars,sgMembers,sgItem,0,0,extData}
// A plugin with plugins. Pluginception!
if pre_render_hooks["pre_render_socialgroups_member_list"] != nil {
if run_pre_render_hook("pre_render_socialgroups_member_list", w, r, &user, &pi) {
return
}
}
err = templates.ExecuteTemplate(w,"socialgroups_member_list.html", pi)
if err != nil {
InternalError(err,w,r)
}
}
func socialgroups_attach_forum(sgid int, fid int) error {
_, err := socialgroups_attach_forum_stmt.Exec(sgid,fid)
return err
}
func socialgroups_unattach_forum(fid int) error {
_, err := socialgroups_attach_forum_stmt.Exec(fid)
return err
}
func socialgroups_build_group_url(slug string, id int) string {
if slug == "" {
return "/group/" + slug + "." + strconv.Itoa(id)
}
return "/group/" + strconv.Itoa(id)
}
/*
Hooks
*/
func socialgroups_pre_render_view_forum(w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
pi := data.(*ForumPage)
if pi.Header.ExtData.items != nil {
if sgData, ok := pi.Header.ExtData.items["socialgroups_current_group"]; ok {
sgItem := sgData.(SocialGroup)
sgpi := SocialGroupPage{pi.Title,pi.CurrentUser,pi.Header,pi.ItemList,pi.Forum,sgItem,pi.Page,pi.LastPage,pi.ExtData}
err := templates.ExecuteTemplate(w,"socialgroups_view_group.html", sgpi)
if err != nil {
LogError(err)
return false
}
return true
}
}
return false
}
func socialgroups_trow_assign(args ...interface{}) interface{} {
var forum *Forum = args[1].(*Forum)
if forum.ParentType == "socialgroup" {
var topicItem *TopicsRow = args[0].(*TopicsRow)
topicItem.ForumLink = "/group/" + strings.TrimPrefix(topicItem.ForumLink,get_forum_url_prefix())
}
return nil
}
// TO-DO: It would be nice, if you could select one of the boards in the group from that drop-down rather than just the one you got linked from
func socialgroups_topic_create_pre_loop(args ...interface{}) interface{} {
var fid int = args[2].(int)
if fstore.DirtyGet(fid).ParentType == "socialgroup" {
var strictmode *bool = args[5].(*bool)
*strictmode = true
}
return nil
}
// TO-DO: Permissions Override. It doesn't quite work yet.
func socialgroups_forum_check(args ...interface{}) (skip interface{}) {
var r = args[1].(*http.Request)
var fid *int = args[3].(*int)
if fstore.DirtyGet(*fid).ParentType == "socialgroup" {
sgItem, ok := r.Context().Value("socialgroups_current_group").(SocialGroup)
if !ok {
LogError(errors.New("Unable to find a parent group in the context data"))
return false
}
//run_vhook("simple_forum_check_pre_perms", w, r, user, &fid, &success).(bool)
var w = args[0].(http.ResponseWriter)
var user *User = args[2].(*User)
var success *bool = args[4].(*bool)
var rank int
var posts int
var joinedAt string
// TO-DO: Group privacy settings. For now, groups are all globally visible
// Clear the default group permissions
// TO-DO: Do this more efficiently, doing it quick and dirty for now to get this out quickly
override_forum_perms(&user.Perms, false)
user.Perms.ViewTopic = true
err := socialgroups_get_member_stmt.QueryRow(sgItem.ID,user.ID).Scan(&rank,&posts,&joinedAt)
if err != nil && err != ErrNoRows {
*success = false
InternalError(err,w,r)
return false
} else if err != nil {
return false
}
// TO-DO: Implement bans properly by adding the Local Ban API in the next commit
if rank < 0 {
return false
}
// Basic permissions for members, more complicated permissions coming in the next commit!
if sgItem.Owner == user.ID {
override_forum_perms(&user.Perms,true)
} else if rank == 0 {
user.Perms.LikeItem = true
user.Perms.CreateTopic = true
user.Perms.CreateReply = true
} else {
override_forum_perms(&user.Perms,true)
}
}
return false
}
// TO-DO: Override redirects? I don't think this is needed quite yet
func socialgroups_widgets(args ...interface{}) interface{} {
var zone string = args[0].(string)
var headerVars *HeaderVars = args[2].(*HeaderVars)
var request *http.Request = args[3].(*http.Request)
if zone != "view_forum" {
return false
}
var forum *Forum = args[1].(*Forum)
if forum.ParentType == "socialgroup" {
// This is why I hate using contexts, all the daisy chains and interface casts x.x
sgItem, ok := request.Context().Value("socialgroups_current_group").(SocialGroup)
if !ok {
LogError(errors.New("Unable to find a parent group in the context data"))
return false
}
if headerVars.ExtData.items == nil {
headerVars.ExtData.items = make(map[string]interface{})
}
headerVars.ExtData.items["socialgroups_current_group"] = sgItem
return socialgroups_group_widgets(headerVars,sgItem)
}
return false
}

132
plugin_test.go Normal file
View File

@ -0,0 +1,132 @@
package main
import "strconv"
import "testing"
// go test -v
type ME_Pair struct
{
Msg string
Expects string
}
func addMEPair(msgList []ME_Pair, msg string, expects string) []ME_Pair {
return append(msgList,ME_Pair{msg,expects})
}
func TestBBCodeRender(t *testing.T) {
var res string
var msgList []ME_Pair
msgList = addMEPair(msgList,"hi","hi")
msgList = addMEPair(msgList,"😀","😀")
msgList = addMEPair(msgList,"[b]😀[/b]","<b>😀</b>")
msgList = addMEPair(msgList,"[b]😀😀😀[/b]","<b>😀😀😀</b>")
msgList = addMEPair(msgList,"[b]hi[/b]","<b>hi</b>")
msgList = addMEPair(msgList,"[u]hi[/u]","<u>hi</u>")
msgList = addMEPair(msgList,"[i]hi[/i]","<i>hi</i>")
msgList = addMEPair(msgList,"[s]hi[/s]","<s>hi</s>")
msgList = addMEPair(msgList,"[c]hi[/c]","[c]hi[/c]")
msgList = addMEPair(msgList,"[b]hi[/i]","[b]hi[/i]")
msgList = addMEPair(msgList,"[/b]hi[b]","[/b]hi[b]")
msgList = addMEPair(msgList,"[/b]hi[/b]","[/b]hi[/b]")
msgList = addMEPair(msgList,"[/b]hi","[/b]hi")
msgList = addMEPair(msgList,"[code]hi[/code]","<span class='codequotes'>hi</span>")
msgList = addMEPair(msgList,"[code][b]hi[/b][/code]","<span class='codequotes'>[b]hi[/b]</span>")
msgList = addMEPair(msgList,"[quote]hi[/quote]","<span class='postQuote'>hi</span>")
msgList = addMEPair(msgList,"[quote][b]hi[/b][/quote]","<span class='postQuote'><b>hi</b></span>")
msgList = addMEPair(msgList,"[quote][b]h[/b][/quote]","<span class='postQuote'><b>h</b></span>")
msgList = addMEPair(msgList,"[quote][b][/b][/quote]","<span class='postQuote'><b></b></span>")
t.Log("Testing bbcode_full_parse")
for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'")
res = bbcode_full_parse(item.Msg).(string)
if res != item.Expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",item.Expects)
}
}
var msg, expects string
var err error
msg = "[rand][/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand][/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg).(string)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
}
msg = "[rand]-1[/rand]"
expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-1[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg).(string)
if res != expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",expects)
}
var conv int
msg = "[rand]1[/rand]"
t.Log("Testing string '"+msg+"'")
res = bbcode_full_parse(msg).(string)
conv, err = strconv.Atoi(res)
if err != nil && (conv > 1 || conv < 0) {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected a number in the range 0-1")
}
t.Log("Testing bbcode_regex_parse")
for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'")
res = bbcode_regex_parse(item.Msg).(string)
if res != item.Expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",item.Expects)
}
}
}
func TestMarkdownRender(t *testing.T) {
var res string
var msgList []ME_Pair
msgList = addMEPair(msgList,"hi","hi")
msgList = addMEPair(msgList,"**hi**","<b>hi</b>")
msgList = addMEPair(msgList,"_hi_","<u>hi</u>")
msgList = addMEPair(msgList,"*hi*","<i>hi</i>")
msgList = addMEPair(msgList,"~hi~","<s>hi</s>")
msgList = addMEPair(msgList,"*hi**","<i>hi</i>*")
msgList = addMEPair(msgList,"**hi***","<b>hi</b>*")
msgList = addMEPair(msgList,"**hi*","*<i>hi</i>")
msgList = addMEPair(msgList,"***hi***","*<b><i>hi</i></b>")
msgList = addMEPair(msgList,"\\*hi\\*","*hi*")
msgList = addMEPair(msgList,"*~hi~*","<i><s>hi</s></i>")
msgList = addMEPair(msgList,"**","**")
msgList = addMEPair(msgList,"***","***")
msgList = addMEPair(msgList,"****","****")
msgList = addMEPair(msgList,"*****","*****")
msgList = addMEPair(msgList,"******","******")
msgList = addMEPair(msgList,"*******","*******")
msgList = addMEPair(msgList,"~~","~~")
msgList = addMEPair(msgList,"~~~","~~~")
msgList = addMEPair(msgList,"~~~~","~~~~")
msgList = addMEPair(msgList,"~~~~~","~~~~~")
msgList = addMEPair(msgList,"__","__")
msgList = addMEPair(msgList,"___","___")
msgList = addMEPair(msgList,"_ _","<u> </u>")
msgList = addMEPair(msgList,"* *","<i> </i>")
msgList = addMEPair(msgList,"** **","<b> </b>")
msgList = addMEPair(msgList,"*** ***","<b><i> </i></b>")
for _, item := range msgList {
t.Log("Testing string '"+item.Msg+"'")
res = markdown_parse(item.Msg).(string)
if res != item.Expects {
t.Error("Bad output:","'"+res+"'")
t.Error("Expected:",item.Expects)
}
}
}

View File

@ -56,6 +56,14 @@ func (build *builder) SimpleInnerJoin(table1 string, table2 string, columns stri
return build.conn.Prepare(res)
}
func (build *builder) CreateTable(table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (stmt *sql.Stmt, err error) {
res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys)
if err != nil {
return stmt, err
}
return build.conn.Prepare(res)
}
func (build *builder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) {
res, err := build.adapter.SimpleInsert("_builder", table, columns, fields)
if err != nil {

View File

@ -3,6 +3,7 @@ package qgen
//import "fmt"
import "strings"
import "strconv"
import "errors"
func init() {
@ -30,6 +31,74 @@ func (adapter *Mysql_Adapter) GetStmts() map[string]string {
return adapter.Buffer
}
func (adapter *Mysql_Adapter) CreateTable(name string, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("You can't have a table with no columns")
}
var querystr string = "CREATE TABLE `" + table + "` ("
for _, column := range columns {
// Make it easier to support Cassandra in the future
if column.Type == "createdAt" {
column.Type = "datetime"
}
var size string
if column.Size > 0 {
size = "(" + strconv.Itoa(column.Size) + ")"
}
var end string
if column.Default != "" {
end = " DEFAULT "
if adapter.stringy_type(column.Type) {
end += "'" + column.Default + "'"
} else {
end += column.Default
}
}
if column.Null {
end += " null"
} else {
end += " not null"
}
if column.Auto_Increment {
end += " AUTO_INCREMENT"
}
querystr += "\n\t`"+column.Name+"` " + column.Type + size + end + ","
}
if len(keys) > 0 {
for _, key := range keys {
querystr += "\n\t" + key.Type + " key("
for _, column := range strings.Split(key.Columns,",") {
querystr += "`" + column + "`,"
}
querystr = querystr[0:len(querystr) - 1] + "),"
}
}
querystr = querystr[0:len(querystr) - 1] + "\n)"
if charset != "" {
querystr += " CHARSET=" + charset
}
if collation != "" {
querystr += " COLLATE " + collation
}
adapter.push_statement(name,querystr + ";")
return querystr + ";", nil
}
func (adapter *Mysql_Adapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
@ -761,7 +830,7 @@ import "database/sql"
` + stmts + `
func gen_mysql() (err error) {
if debug {
if debug_mode {
log.Print("Building the generated statements")
}
` + body + `
@ -771,8 +840,13 @@ func gen_mysql() (err error) {
return write_file("./gen_mysql.go", out)
}
// Internal method, not exposed in the interface
func (adapter *Mysql_Adapter) push_statement(name string, querystr string ) {
// Internal methods, not exposed in the interface
func (adapter *Mysql_Adapter) push_statement(name string, querystr string) {
adapter.Buffer[name] = querystr
adapter.BufferOrder = append(adapter.BufferOrder,name)
}
func (adapter *Mysql_Adapter) stringy_type(ctype string) bool {
ctype = strings.ToLower(ctype)
return ctype == "varchar" || ctype == "tinytext" || ctype == "text" || ctype == "mediumtext" || ctype == "longtext" || ctype == "char" || ctype == "datetime" || ctype == "timestamp" || ctype == "time" || ctype == "date"
}

View File

@ -6,6 +6,22 @@ import "errors"
var DB_Registry []DB_Adapter
var No_Adapter = errors.New("This adapter doesn't exist")
type DB_Table_Column struct
{
Name string
Type string
Size int
Null bool
Auto_Increment bool
Default string
}
type DB_Table_Key struct
{
Columns string
Type string
}
type DB_Select struct
{
Table string
@ -84,7 +100,8 @@ type DB_Limit struct {
type DB_Adapter interface {
GetName() string
SimpleInsert(string,string,string,string) (string, error)
CreateTable(name string, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (string, error)
SimpleInsert(name string, table string, columns string, fields string) (string, error)
SimpleReplace(string,string,string,string) (string, error)
SimpleUpdate(string,string,string,string) (string, error)
SimpleDelete(string,string,string) (string, error)

View File

@ -93,13 +93,13 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("get_full_settings","settings","name, content, type, constraints","","","")
adapter.SimpleSelect("get_groups","users_groups","gid, name, permissions, is_mod, is_admin, is_banned, tag","","","")
adapter.SimpleSelect("get_groups","users_groups","gid, name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag","","","")
adapter.SimpleSelect("get_forums","forums","fid, name, desc, active, preset, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime","","fid ASC","")
adapter.SimpleSelect("get_forums","forums","fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime","","fid ASC","")
adapter.SimpleSelect("get_forums_permissions","forums_permissions","gid, fid, permissions","","gid ASC, fid ASC","")
adapter.SimpleSelect("get_plugins","plugins","uname, active","","","")
adapter.SimpleSelect("get_plugins","plugins","uname, active, installed","","","")
adapter.SimpleSelect("get_themes","themes","uname, default","","","")
@ -107,6 +107,8 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("is_plugin_active","plugins","active","uname = ?","","")
//adapter.SimpleSelect("is_plugin_installed","plugins","installed","uname = ?","","")
adapter.SimpleSelect("get_users","users","uid, name, group, active, is_super_admin, avatar","","","")
adapter.SimpleSelect("is_theme_default","themes","default","uname = ?","","")
@ -195,7 +197,7 @@ func write_inserts(adapter qgen.DB_Adapter) error {
adapter.SimpleInsert("add_forum_perms_to_forum","forums_permissions","gid,fid,preset,permissions","?,?,?,?")
adapter.SimpleInsert("add_plugin","plugins","uname,active","?,?")
adapter.SimpleInsert("add_plugin","plugins","uname, active, installed","?,?,?")
adapter.SimpleInsert("add_theme","themes","uname,default","?,?")
@ -275,6 +277,8 @@ func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("update_plugin","plugins","active = ?","uname = ?")
adapter.SimpleUpdate("update_plugin_install","plugins","installed = ?","uname = ?")
adapter.SimpleUpdate("update_theme","themes","default = ?","uname = ?")
adapter.SimpleUpdate("update_user","users","name = ?, email = ?, group = ?","uid = ?")

View File

@ -2,6 +2,8 @@
package main
import "html/template"
// Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be seperate?
type Reply struct /* Should probably rename this to ReplyUser and rename ReplyShort to Reply */
{
ID int

View File

@ -1,3 +1,4 @@
/* Obsoleted by gen_router.go :( */
package main
//import "fmt"
@ -5,6 +6,7 @@ import "strings"
import "sync"
import "net/http"
// TO-DO: Support the new handler signatures created by our efforts to move the PreRoute middleware into the generated router
type Router struct {
sync.RWMutex
routes map[string]func(http.ResponseWriter, *http.Request)

View File

@ -29,7 +29,7 @@ func main() {
if route.Before != "" {
out += "\n\t\t\t" + route.Before
}
out += "\n\t\t\t" + route.Name + "(w,req"
out += "\n\t\t\t" + route.Name + "(w,req,user"
for _, item := range route.Vars {
out += "," + item
}
@ -57,7 +57,7 @@ func main() {
if route.Before != "" {
out += "\n\t\t\t\t\t" + route.Before
}
out += "\n\t\t\t\t\t" + route.Name + "(w,req"
out += "\n\t\t\t\t\t" + route.Name + "(w,req,user"
for _, item := range route.Vars {
out += "," + item
}
@ -69,7 +69,7 @@ func main() {
if default_route.Before != "" {
out += "\n\t\t\t\t\t" + default_route.Before
}
out += "\n\t\t\t\t\t" + default_route.Name + "(w,req"
out += "\n\t\t\t\t\t" + default_route.Name + "(w,req,user"
for _, item := range default_route.Vars {
out += ", " + item
}
@ -81,32 +81,48 @@ func main() {
fdata += `package main
//import "fmt"
import "sync"
import "strings"
import "sync"
import "errors"
import "net/http"
var ErrNoRoute = errors.New("That route doesn't exist.")
type GenRouter struct {
UploadHandler func(http.ResponseWriter, *http.Request)
sync.RWMutex // Temporary Fallback
old_routes map[string]func(http.ResponseWriter, *http.Request) // Temporary Fallback
extra_routes map[string]func(http.ResponseWriter, *http.Request, User)
sync.RWMutex
}
func NewGenRouter(uploads http.Handler) *GenRouter {
return &GenRouter{
UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP,
old_routes: make(map[string]func(http.ResponseWriter, *http.Request)),
extra_routes: make(map[string]func(http.ResponseWriter, *http.Request, User)),
}
}
func (router *GenRouter) Handle(_ string, _ http.Handler) {
}
func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request)) {
func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request, User)) {
router.Lock()
router.old_routes[pattern] = handle
router.extra_routes[pattern] = handle
router.Unlock()
}
func (router *GenRouter) RemoveFunc(pattern string) error {
router.Lock()
_, ok := router.extra_routes[pattern]
if !ok {
router.Unlock()
return ErrNoRoute
}
delete(router.extra_routes,pattern)
router.Unlock()
return nil
}
func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
//if req.URL.Path == "/" {
// default_route(w,req)
@ -127,6 +143,19 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
//fmt.Println("prefix:",prefix)
//fmt.Println("req.URL.Path:",req.URL.Path)
//fmt.Println("extra_data:",extra_data)
if prefix == "/static" {
req.URL.Path += extra_data
route_static(w,req)
return
}
// Deal with the session stuff, etc.
user, ok := PreRoute(w,req)
if !ok {
return
}
switch(prefix) {` + out + `
case "/uploads":
if extra_data == "" {
@ -137,19 +166,19 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.UploadHandler(w,req)
return
case "":
default_route(w,req)
default_route(w,req,user)
return
//default: NotFound(w,req)
}
// A fallback for the routes which haven't been converted to the new router yet
// A fallback for the routes which haven't been converted to the new router yet or plugins
router.RLock()
handle, ok := router.old_routes[req.URL.Path]
handle, ok := router.extra_routes[req.URL.Path]
router.RUnlock()
if ok {
req.URL.Path += extra_data
handle(w,req)
handle(w,req,user)
return
}
NotFound(w,req)

View File

@ -23,7 +23,7 @@ func addRouteGroup(path string, routes ...Route) {
func routes() {
//addRoute("default_route","","")
addRoute("route_api","/api/","")
addRoute("route_static","/static/","req.URL.Path += extra_data")
///addRoute("route_static","/static/","req.URL.Path += extra_data")
addRoute("route_overview","/overview/","")
//addRoute("route_custom_page","/pages/",""/*,"&extra_data"*/)
addRoute("route_forums","/forums/",""/*,"&forums"*/)
@ -61,6 +61,7 @@ func routes() {
Route{"route_panel_plugins","/panel/plugins/","",[]string{}},
Route{"route_panel_plugins_activate","/panel/plugins/activate/","",[]string{"extra_data"}},
Route{"route_panel_plugins_deactivate","/panel/plugins/deactivate/","",[]string{"extra_data"}},
Route{"route_panel_plugins_install","/panel/plugins/install/","",[]string{"extra_data"}},
Route{"route_panel_users","/panel/users/","",[]string{}},
Route{"route_panel_users_edit","/panel/users/edit/","",[]string{"extra_data"}},

View File

@ -2,6 +2,7 @@ package main
import "strconv"
import "strings"
// TO-DO: Move this into the phrase system
var settingLabels map[string]string
type OptionLabel struct

View File

@ -83,65 +83,71 @@ w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_7)
}
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_9)
}
w.Write(forum_10)
} else {
w.Write([]byte(tmpl_forum_vars.Title))
w.Write(forum_11)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
if tmpl_forum_vars.CurrentUser.ID != 0 {
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_12)
}
}
} else {
w.Write(forum_13)
}
w.Write(forum_14)
}
w.Write(forum_15)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_16)
if len(tmpl_forum_vars.ItemList) != 0 {
for _, item := range tmpl_forum_vars.ItemList {
w.Write(forum_14)
if item.Avatar != "" {
w.Write(forum_15)
w.Write([]byte(item.Avatar))
w.Write(forum_16)
}
if item.Sticky {
w.Write(forum_17)
} else {
if item.Is_Closed {
if item.Sticky {
w.Write(forum_18)
}
}
w.Write(forum_19)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_20)
w.Write([]byte(item.LastReplyAt))
w.Write(forum_21)
w.Write([]byte(item.Slug))
w.Write(forum_22)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(forum_23)
w.Write([]byte(item.Title))
w.Write(forum_24)
w.Write([]byte(item.UserSlug))
w.Write(forum_25)
w.Write([]byte(strconv.Itoa(item.CreatedBy)))
w.Write(forum_26)
w.Write([]byte(item.CreatedByName))
w.Write(forum_27)
if item.Is_Closed {
w.Write(forum_28)
}
w.Write(forum_29)
}
} else {
if item.Is_Closed {
w.Write(forum_19)
}
}
w.Write(forum_20)
if item.Avatar != "" {
w.Write(forum_21)
w.Write([]byte(item.Avatar))
w.Write(forum_22)
}
w.Write(forum_23)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_24)
w.Write([]byte(item.LastReplyAt))
w.Write(forum_25)
w.Write([]byte(item.Slug))
w.Write(forum_26)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(forum_27)
w.Write([]byte(item.Title))
w.Write(forum_28)
w.Write([]byte(item.UserSlug))
w.Write(forum_29)
w.Write([]byte(strconv.Itoa(item.CreatedBy)))
w.Write(forum_30)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write([]byte(item.CreatedByName))
w.Write(forum_31)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
if item.Is_Closed {
w.Write(forum_32)
}
w.Write(forum_33)
}
} else {
w.Write(forum_34)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_35)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_36)
}
w.Write(forum_37)
}
w.Write(forum_38)
w.Write(footer_0)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)

View File

@ -74,51 +74,43 @@ w.Write(forums_2)
w.Write(forums_3)
if item.Desc != "" {
w.Write(forums_4)
w.Write([]byte(item.Slug))
w.Write([]byte(item.Link))
w.Write(forums_5)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write([]byte(item.Name))
w.Write(forums_6)
w.Write([]byte(item.Name))
w.Write(forums_7)
w.Write([]byte(item.Desc))
w.Write(forums_7)
} else {
if item.LastTopicTime != "" {
w.Write(forums_8)
} else {
if item.LastTopicTime != "" {
w.Write([]byte(item.Link))
w.Write(forums_9)
w.Write([]byte(item.Slug))
w.Write([]byte(item.Name))
w.Write(forums_10)
w.Write([]byte(strconv.Itoa(item.ID)))
} else {
w.Write(forums_11)
w.Write([]byte(item.Name))
w.Write([]byte(item.Link))
w.Write(forums_12)
} else {
w.Write(forums_13)
w.Write([]byte(item.Slug))
w.Write(forums_14)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(forums_15)
w.Write([]byte(item.Name))
w.Write(forums_16)
w.Write(forums_13)
}
}
w.Write(forums_17)
w.Write(forums_14)
w.Write([]byte(item.LastTopicSlug))
w.Write(forums_18)
w.Write([]byte(strconv.Itoa(item.LastTopicID)))
w.Write(forums_19)
w.Write(forums_15)
w.Write([]byte(item.LastTopic))
w.Write(forums_20)
w.Write(forums_16)
if item.LastTopicTime != "" {
w.Write(forums_21)
w.Write(forums_17)
w.Write([]byte(item.LastTopicTime))
w.Write(forums_22)
w.Write(forums_18)
}
w.Write(forums_23)
w.Write(forums_19)
}
} else {
w.Write(forums_24)
w.Write(forums_20)
}
w.Write(forums_25)
w.Write(forums_21)
w.Write(footer_0)
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)

View File

@ -84,10 +84,10 @@ var topic_7 []byte = []byte(`">&gt;</a>
</div>`)
var topic_8 []byte = []byte(`
<div class="rowblock topic_block">
<div class="rowblock rowhead topic_block">
<form action='/topic/edit/submit/`)
var topic_9 []byte = []byte(`' method="post">
<div class="rowitem rowhead topic_item"`)
<div class="rowitem topic_item"`)
var topic_10 []byte = []byte(` style="background-color:#FFFFEA;"`)
var topic_11 []byte = []byte(` style="background-color:#eaeaea;"`)
var topic_12 []byte = []byte(`>
@ -249,10 +249,10 @@ var topic_alt_6 []byte = []byte(`?page=`)
var topic_alt_7 []byte = []byte(`">&gt;</a></div>`)
var topic_alt_8 []byte = []byte(`
<div class="rowblock topic_block">
<div class="rowblock rowhead topic_block">
<form action='/topic/edit/submit/`)
var topic_alt_9 []byte = []byte(`' method="post">
<div class="rowitem topic_item rowhead`)
<div class="rowitem topic_item`)
var topic_alt_10 []byte = []byte(` topic_sticky_head`)
var topic_alt_11 []byte = []byte(` topic_closed_head`)
var topic_alt_12 []byte = []byte(`">
@ -441,8 +441,8 @@ var profile_16 []byte = []byte(`&type=user" class="profile_menu_item report_item
</div>
</div>
<div class="colblock_right" style="width: calc(95% - 210px);">
<div class="rowitem rowhead"><a>Comments</a></div>
<div class="colblock_right rowhead" style="width: calc(95% - 210px);">
<div class="rowitem"><a>Comments</a></div>
</div>
<div id="profile_comments" class="colblock_right" style="overflow: hidden;border-top: none;width:calc(95% - 210px);">`)
var profile_17 []byte = []byte(`
@ -499,7 +499,7 @@ var profile_39 []byte = []byte(`
`)
var forums_0 []byte = []byte(`
<div class="rowblock opthead">
<div class="rowitem rowhead"><a>Forums</a></div>
<div class="rowitem"><a>Forums</a></div>
</div>
<div class="rowblock">
`)
@ -508,85 +508,82 @@ var forums_2 []byte = []byte(`datarow`)
var forums_3 []byte = []byte(`">
`)
var forums_4 []byte = []byte(`<span style="float: left;">
<a href="/forum/`)
var forums_5 []byte = []byte(`.`)
var forums_6 []byte = []byte(`" style="">`)
var forums_7 []byte = []byte(`</a>
<a href="`)
var forums_5 []byte = []byte(`" style="">`)
var forums_6 []byte = []byte(`</a>
<br /><span class="rowsmall">`)
var forums_8 []byte = []byte(`</span>
var forums_7 []byte = []byte(`</span>
</span>`)
var forums_9 []byte = []byte(`<span style="float: left;padding-top: 8px;font-size: 18px;">
<a href="/forum/`)
var forums_10 []byte = []byte(`.`)
var forums_11 []byte = []byte(`">`)
var forums_12 []byte = []byte(`</a>
var forums_8 []byte = []byte(`<span style="float: left;padding-top: 8px;font-size: 18px;">
<a href="`)
var forums_9 []byte = []byte(`">`)
var forums_10 []byte = []byte(`</a>
</span>`)
var forums_13 []byte = []byte(`<span style="float: left;">
<a href="/forum/`)
var forums_14 []byte = []byte(`.`)
var forums_15 []byte = []byte(`">`)
var forums_16 []byte = []byte(`</a>
var forums_11 []byte = []byte(`<span style="float: left;">
<a href="`)
var forums_12 []byte = []byte(`">`)
var forums_13 []byte = []byte(`</a>
</span>`)
var forums_17 []byte = []byte(`
var forums_14 []byte = []byte(`
<span style="float: right;">
<a href="/topic/`)
var forums_18 []byte = []byte(`.`)
var forums_19 []byte = []byte(`" style="float: right;font-size: 14px;">`)
var forums_20 []byte = []byte(`</a>
var forums_15 []byte = []byte(`" style="float: right;font-size: 14px;">`)
var forums_16 []byte = []byte(`</a>
`)
var forums_21 []byte = []byte(`<br /><span class="rowsmall">`)
var forums_22 []byte = []byte(`</span>`)
var forums_23 []byte = []byte(`
var forums_17 []byte = []byte(`<br /><span class="rowsmall">`)
var forums_18 []byte = []byte(`</span>`)
var forums_19 []byte = []byte(`
</span>
<div style="clear: both;"></div>
</div>
`)
var forums_24 []byte = []byte(`<div class="rowitem passive">You don't have access to any forums.</div>`)
var forums_25 []byte = []byte(`
var forums_20 []byte = []byte(`<div class="rowitem passive">You don't have access to any forums.</div>`)
var forums_21 []byte = []byte(`
</div>
`)
var topics_0 []byte = []byte(`
<div class="rowblock">
<div class="rowitem rowhead"><a>Topic List</a></div>
<div class="rowblock rowhead">
<div class="rowitem"><a>Topic List</a></div>
</div>
<div id="topic_list" class="rowblock topic_list">
`)
var topics_1 []byte = []byte(`<div class="rowitem passive datarow" style="`)
var topics_2 []byte = []byte(`background-image: url(`)
var topics_3 []byte = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var topics_4 []byte = []byte(`background-color: #FFFFCC;`)
var topics_5 []byte = []byte(`background-color: #eaeaea;`)
var topics_6 []byte = []byte(`">
var topics_1 []byte = []byte(`<div class="rowitem passive datarow `)
var topics_2 []byte = []byte(`topic_sticky`)
var topics_3 []byte = []byte(`topic_closed`)
var topics_4 []byte = []byte(`" style="`)
var topics_5 []byte = []byte(`background-image: url(`)
var topics_6 []byte = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var topics_7 []byte = []byte(`">
<span class="rowsmall" style="float: right;">
<span class="replyCount">`)
var topics_7 []byte = []byte(` replies</span><br />
var topics_8 []byte = []byte(` replies</span><br />
<span class="lastReplyAt">`)
var topics_8 []byte = []byte(`</span>
var topics_9 []byte = []byte(`</span>
</span>
<span>
<a class="rowtopic" href="/topic/`)
var topics_9 []byte = []byte(`.`)
var topics_10 []byte = []byte(`">`)
var topics_11 []byte = []byte(`</a> `)
var topics_12 []byte = []byte(`<a class="rowsmall" href="/forum/`)
var topics_13 []byte = []byte(`">`)
var topics_14 []byte = []byte(`</a>`)
var topics_15 []byte = []byte(`
var topics_10 []byte = []byte(`.`)
var topics_11 []byte = []byte(`">`)
var topics_12 []byte = []byte(`</a> `)
var topics_13 []byte = []byte(`<a class="rowsmall" href="`)
var topics_14 []byte = []byte(`">`)
var topics_15 []byte = []byte(`</a>`)
var topics_16 []byte = []byte(`
<br /><a class="rowsmall" href="/user/`)
var topics_16 []byte = []byte(`.`)
var topics_17 []byte = []byte(`">Starter: `)
var topics_18 []byte = []byte(`</a>
var topics_17 []byte = []byte(`.`)
var topics_18 []byte = []byte(`">Starter: `)
var topics_19 []byte = []byte(`</a>
`)
var topics_19 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E`)
var topics_20 []byte = []byte(`</span>
var topics_20 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E`)
var topics_21 []byte = []byte(`</span>
</span>
</div>
`)
var topics_21 []byte = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_22 []byte = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_23 []byte = []byte(`</div>`)
var topics_24 []byte = []byte(`
var topics_22 []byte = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_23 []byte = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_24 []byte = []byte(`</div>`)
var topics_25 []byte = []byte(`
</div>
`)
var forum_0 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" href="/forum/`)
@ -600,48 +597,58 @@ var forum_6 []byte = []byte(`?page=`)
var forum_7 []byte = []byte(`">&gt;</a></div>`)
var forum_8 []byte = []byte(`
<div class="rowblock">
<div class="rowitem rowhead"><a>`)
var forum_9 []byte = []byte(`</a>
`)
var forum_10 []byte = []byte(`<span class='username head_tag_upshift' title='No Permissions'>&#x1F512;&#xFE0E</span>`)
var forum_11 []byte = []byte(`<a href="/topics/create/`)
var forum_12 []byte = []byte(`" class='username head_tag_upshift'>New Topic</a>`)
var forum_13 []byte = []byte(`</div>
<div class="rowblock rowhead">
<div class="rowitem `)
var forum_9 []byte = []byte(`has_opt`)
var forum_10 []byte = []byte(`"><a>`)
var forum_11 []byte = []byte(`</a>
</div>
`)
var forum_12 []byte = []byte(`
<div class="opt create_topic_opt" title="Create Topic">&#x1F58A;&#xFE0E</div>
`)
var forum_13 []byte = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic">&#x1F512;&#xFE0E</div>`)
var forum_14 []byte = []byte(`
<div style="clear: both;"></div>
`)
var forum_15 []byte = []byte(`
<!--href="/topics/create/`)
var forum_16 []byte = []byte(`"-->
</div>
<div id="forum_topic_list" class="rowblock topic_list">
`)
var forum_14 []byte = []byte(`<div class="rowitem passive datarow" style="`)
var forum_15 []byte = []byte(`background-image: url(`)
var forum_16 []byte = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var forum_17 []byte = []byte(`background-color: #FFFFCC;`)
var forum_18 []byte = []byte(`background-color: #eaeaea;`)
var forum_19 []byte = []byte(`">
var forum_17 []byte = []byte(`<div class="rowitem passive datarow `)
var forum_18 []byte = []byte(`topic_sticky`)
var forum_19 []byte = []byte(`topic_closed`)
var forum_20 []byte = []byte(`" style="`)
var forum_21 []byte = []byte(`background-image: url(`)
var forum_22 []byte = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var forum_23 []byte = []byte(`">
<span class="rowsmall" style="float: right;">
<span class="replyCount">`)
var forum_20 []byte = []byte(` replies</span><br />
var forum_24 []byte = []byte(` replies</span><br />
<span class="lastReplyAt">`)
var forum_21 []byte = []byte(`</span>
var forum_25 []byte = []byte(`</span>
</span>
<span>
<a class="rowtopic" href="/topic/`)
var forum_22 []byte = []byte(`.`)
var forum_23 []byte = []byte(`">`)
var forum_24 []byte = []byte(`</a>
var forum_26 []byte = []byte(`.`)
var forum_27 []byte = []byte(`">`)
var forum_28 []byte = []byte(`</a>
<br /><a class="rowsmall" href="/user/`)
var forum_25 []byte = []byte(`.`)
var forum_26 []byte = []byte(`">Starter: `)
var forum_27 []byte = []byte(`</a>
var forum_29 []byte = []byte(`.`)
var forum_30 []byte = []byte(`">Starter: `)
var forum_31 []byte = []byte(`</a>
`)
var forum_28 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E`)
var forum_29 []byte = []byte(`</span>
var forum_32 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E`)
var forum_33 []byte = []byte(`</span>
</span>
</div>
`)
var forum_30 []byte = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_31 []byte = []byte(` <a href="/topics/create/`)
var forum_32 []byte = []byte(`">Start one?</a>`)
var forum_33 []byte = []byte(`</div>`)
var forum_34 []byte = []byte(`
var forum_34 []byte = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_35 []byte = []byte(` <a href="/topics/create/`)
var forum_36 []byte = []byte(`">Start one?</a>`)
var forum_37 []byte = []byte(`</div>`)
var forum_38 []byte = []byte(`
</div>
`)

View File

@ -68,56 +68,57 @@ w.Write(topics_0)
if len(tmpl_topics_vars.ItemList) != 0 {
for _, item := range tmpl_topics_vars.ItemList {
w.Write(topics_1)
if item.Avatar != "" {
if item.Sticky {
w.Write(topics_2)
w.Write([]byte(item.Avatar))
} else {
if item.Is_Closed {
w.Write(topics_3)
}
if item.Sticky {
}
w.Write(topics_4)
} else {
if item.Is_Closed {
if item.Avatar != "" {
w.Write(topics_5)
}
}
w.Write([]byte(item.Avatar))
w.Write(topics_6)
w.Write([]byte(strconv.Itoa(item.PostCount)))
}
w.Write(topics_7)
w.Write([]byte(item.LastReplyAt))
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(topics_8)
w.Write([]byte(item.Slug))
w.Write([]byte(item.LastReplyAt))
w.Write(topics_9)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write([]byte(item.Slug))
w.Write(topics_10)
w.Write([]byte(item.Title))
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topics_11)
if item.ForumName != "" {
w.Write([]byte(item.Title))
w.Write(topics_12)
w.Write([]byte(strconv.Itoa(item.ParentID)))
if item.ForumName != "" {
w.Write(topics_13)
w.Write([]byte(item.ForumName))
w.Write([]byte(item.ForumLink))
w.Write(topics_14)
}
w.Write([]byte(item.ForumName))
w.Write(topics_15)
w.Write([]byte(item.UserSlug))
w.Write(topics_16)
w.Write([]byte(strconv.Itoa(item.CreatedBy)))
w.Write(topics_17)
w.Write([]byte(item.CreatedByName))
w.Write(topics_18)
if item.Is_Closed {
w.Write(topics_19)
}
w.Write(topics_16)
w.Write([]byte(item.UserSlug))
w.Write(topics_17)
w.Write([]byte(strconv.Itoa(item.CreatedBy)))
w.Write(topics_18)
w.Write([]byte(item.CreatedByName))
w.Write(topics_19)
if item.Is_Closed {
w.Write(topics_20)
}
} else {
w.Write(topics_21)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_22)
}
} else {
w.Write(topics_22)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_23)
}
w.Write(topics_24)
}
w.Write(topics_25)
w.Write(footer_0)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)

View File

@ -168,7 +168,7 @@ w.Write([]byte(`," + ",-1)
//whitespace_writes := regexp.MustCompile(`(?s)w.Write\(\[\]byte\(`+spstr+`\)\)`)
//fout = whitespace_writes.ReplaceAllString(fout,"")
if debug {
if debug_mode {
for index, count := range c.stats {
fmt.Println(index + ": " + strconv.Itoa(count))
}

View File

@ -1,6 +1,6 @@
<div class="colstack_left">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>My Account</a></div>
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>My Account</a></div>
</div>
<div class="colstack_item">
<div class="rowitem passive"><a href="/user/edit/avatar/">Change Avatar</a></div>
@ -11,4 +11,4 @@
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
</div>
</div>
</div>

View File

@ -1,8 +1,8 @@
{{template "header.html" . }}
{{template "account-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Edit Avatar</a></div>
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Edit Avatar</a></div>
</div>
{{if .CurrentUser.Avatar}}
<div class="colstack_item">

View File

@ -1,8 +1,8 @@
{{template "header.html" . }}
{{template "account-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Emails</a></div>
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Emails</a></div>
</div>
<div class="colstack_item">
{{range .ItemList}}
@ -16,4 +16,4 @@
{{end}}
</div>
</div>
{{template "footer.html" . }}
{{template "footer.html" . }}

View File

@ -1,8 +1,8 @@
{{template "header.html" . }}
{{template "account-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Edit Username</a></div>
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Edit Username</a></div>
</div>
<div class="colstack_item">
<form action="/user/edit/username/submit/" method="post">

View File

@ -1,8 +1,8 @@
{{template "header.html" . }}
{{template "account-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Edit Password</a></div>
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Edit Password</a></div>
</div>
<div class="colstack_item">
<form action="/user/edit/critical/submit/" method="post">

View File

@ -1,10 +1,10 @@
{{template "header.html" . }}
<div class="rowblock">
<div class="rowitem rowhead"><a>Are you sure?</a></div>
<div class="rowblock rowhead">
<div class="rowitem"><a>Are you sure?</a></div>
</div>
<div class="rowblock">
<div class="rowitem passive">{{.Something.Message}}<br /><br />
<a class="username" href="{{.Something.URL}}?session={{.CurrentUser.Session}}">Continue</a>
</div>
</div>
{{template "footer.html" . }}
{{template "footer.html" . }}

View File

@ -1,16 +1,16 @@
{{template "header.html" . }}
<div class="rowblock">
<div class="rowitem rowhead"><a>Create Topic</a></div>
<div class="rowblock rowhead">
<div class="rowitem"><a>Create Topic</a></div>
</div>
<div class="rowblock">
<form action="/topic/create/submit/" method="post">
<div class="formrow">
<div class="formitem formlabel"><a>Board</a></div>
<div class="formitem"><select name="topic-board">
{{range .ItemList}}<option {{if eq .ID $.FID}}selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}}
</select></div>
{{range .ItemList}}<option {{if eq .ID $.FID}}selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}}
</select></div>
</div>
<div class="formrow">
<div class="formrow">
<div class="formitem formlabel"><a>Topic Name</a></div>
<div class="formitem"><input name="topic-name" type="text" placeholder="Topic Name" /></div>
</div>

View File

@ -1,8 +1,8 @@
{{template "header.html" . }}
<div class="rowblock">
<div class="rowitem rowhead"><a>An error has occured</a></div>
<div class="rowblock rowhead">
<div class="rowitem"><a>An error has occured</a></div>
</div>
<div class="rowblock">
<div class="rowitem passive">{{.Something}}</div>
</div>
{{template "footer.html" . }}
{{template "footer.html" . }}

View File

@ -4,12 +4,19 @@
{{if ne .LastPage .Page}}<link rel="prerender" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div class="rowblock">
<div class="rowitem rowhead"><a>{{.Title}}</a>
{{if ne .CurrentUser.ID 0}}{{if not .CurrentUser.Perms.CreateTopic}}<span class='username head_tag_upshift' title='No Permissions'>&#x1F512;&#xFE0E</span>{{else}}<a href="/topics/create/{{.Forum.ID}}" class='username head_tag_upshift'>New Topic</a>{{end}}{{end}}</div>
<div class="rowblock rowhead">
<div class="rowitem {{if ne .CurrentUser.ID 0}}has_opt{{end}}"><a>{{.Title}}</a>
</div>
{{if ne .CurrentUser.ID 0}}
{{if .CurrentUser.Perms.CreateTopic}}
<div class="opt create_topic_opt" title="Create Topic">&#x1F58A;&#xFE0E</div>
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic">&#x1F512;&#xFE0E</div>{{end}}
<div style="clear: both;"></div>
{{end}}
<!--href="/topics/create/{{.Forum.ID}}"-->
</div>
<div id="forum_topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="rowitem passive datarow" style="{{if .Avatar}}background-image: url({{.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}{{if .Sticky}}background-color: #FFFFCC;{{else if .Is_Closed}}background-color: #eaeaea;{{end}}">
{{range .ItemList}}<div class="rowitem passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Avatar}}background-image: url({{.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span class="rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span>

View File

@ -1,20 +1,20 @@
{{template "header.html" . }}
<div class="rowblock opthead">
<div class="rowitem rowhead"><a>Forums</a></div>
<div class="rowitem"><a>Forums</a></div>
</div>
<div class="rowblock">
{{range .ItemList}}<div class="rowitem {{if (.Desc) or (.LastTopicTime)}}datarow{{end}}">
{{if .Desc}}<span style="float: left;">
<a href="/forum/{{.Slug}}.{{.ID}}" style="">{{.Name}}</a>
<a href="{{.Link}}" style="">{{.Name}}</a>
<br /><span class="rowsmall">{{.Desc}}</span>
</span>{{else if .LastTopicTime}}<span style="float: left;padding-top: 8px;font-size: 18px;">
<a href="/forum/{{.Slug}}.{{.ID}}">{{.Name}}</a>
<a href="{{.Link}}">{{.Name}}</a>
</span>{{else}}<span style="float: left;">
<a href="/forum/{{.Slug}}.{{.ID}}">{{.Name}}</a>
<a href="{{.Link}}">{{.Name}}</a>
</span>{{end}}
<span style="float: right;">
<a href="/topic/{{.LastTopicSlug}}.{{.LastTopicID}}" style="float: right;font-size: 14px;">{{.LastTopic}}</a>
<a href="/topic/{{.LastTopicSlug}}" style="float: right;font-size: 14px;">{{.LastTopic}}</a>
{{if .LastTopicTime}}<br /><span class="rowsmall">{{.LastTopicTime}}</span>{{end}}
</span>
<div style="clear: both;"></div>

View File

@ -1,6 +1,6 @@
{{template "header.html" . }}
<div class="rowblock">
<div class="rowitem rowhead"><a>Login</a></div>
<div class="rowblock rowhead">
<div class="rowitem"><a>Login</a></div>
</div>
<div class="rowblock">
<form action="/accounts/login/submit/" method="post">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<div class="colstack_left">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a href="/panel/logs/mod/">Logs</a></div>
<div class="rowitem"><a href="/panel/logs/mod/">Logs</a></div>
</div>
<div class="colstack_item">
<div class="rowitem passive"><a href="/panel/logs/mod/">Moderation Logs</a></div>
@ -12,7 +12,7 @@
</div>
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Administration Logs</a></div>
<div class="rowitem"><a>Administration Logs</a></div>
</div>
<div id="panel_adminlogs" class="colstack_item">
{{range .Logs}}

View File

@ -7,7 +7,7 @@ var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_acces
</div>
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>{{.Name}} Forum</a></div>
<div class="rowitem"><a>{{.Name}} Forum</a></div>
</div>
<div id="panel_forum" class="colstack_item">
<form action="/panel/forums/edit/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post">
@ -46,7 +46,7 @@ var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_acces
</form>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Forum Permissions</a></div>
<div class="rowitem"><a>Forum Permissions</a></div>
</div>
<div id="forum_quick_perms" class="colstack_item">
{{range .Groups}}

View File

@ -7,7 +7,7 @@
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Forums</a></div>
<div class="rowitem"><a>Forums</a></div>
</div>
<div id="panel_forums" class="colstack_item">
{{range .ItemList}}
@ -33,7 +33,7 @@
</div>
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Add Forum</a></div>
<div class="rowitem"><a>Add Forum</a></div>
</div>
<div class="colstack_item">
<form action="/panel/forums/create/?session={{.CurrentUser.Session}}" method="post">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<div class="colstack_left">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a href="/panel/groups/edit/{{.ID}}">Group Editor</a></div>
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">Group Editor</a></div>
</div>
<div class="colstack_item">
<div class="rowitem passive"><a href="/panel/groups/edit/{{.ID}}">General</a></div>
@ -12,7 +12,7 @@
</div>
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>{{.Name}} Group</a></div>
<div class="rowitem"><a>{{.Name}} Group</a></div>
</div>
<form action="/panel/groups/edit/perms/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post">
<div id="panel_group" class="colstack_item">
@ -36,7 +36,7 @@
</div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Extended Permissions</a></div>
<div class="rowitem"><a>Extended Permissions</a></div>
</div>
<div class="colstack_item">
{{if .CurrentUser.Perms.EditGroupGlobalPerms}}

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<div class="colstack_left">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a href="/panel/groups/edit/{{.ID}}">Group Editor</a></div>
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">Group Editor</a></div>
</div>
<div class="colstack_item">
<div class="rowitem passive"><a href="/panel/groups/edit/{{.ID}}">General</a></div>
@ -12,7 +12,7 @@
</div>
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>{{.Name}} Group</a></div>
<div class="rowitem"><a>{{.Name}} Group</a></div>
</div>
<div id="panel_group" class="colstack_item">
<form action="/panel/groups/edit/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post">

View File

@ -3,7 +3,7 @@
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Groups</a></div>
<div class="rowitem"><a>Groups</a></div>
</div>
<div id="panel_groups" class="colstack_item">
{{range .ItemList}}
@ -19,7 +19,7 @@
{{end}}
</div>
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Create Group</a></div>
<div class="rowitem"><a>Create Group</a></div>
</div>
<div class="colstack_item">
<form action="/panel/groups/create/?session={{.CurrentUser.Session}}" method="post">

View File

@ -1,5 +1,5 @@
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a href="/panel/">Control Panel</a></div>
<div class="rowitem"><a href="/panel/">Control Panel</a></div>
</div>
<div class="colstack_item">
<div class="rowitem passive"><a href="/panel/users/">Users</a></div>
@ -10,4 +10,4 @@
{{if .CurrentUser.Perms.ManagePlugins}}<div class="rowitem passive"><a href="/panel/plugins/">Plugins</a></div>{{end}}
<div class="rowitem passive"><a href="/forum/1">Reports</a></div>
<div class="rowitem passive"><a href="/panel/logs/mod/">Logs</a></div>
</div>
</div>

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<div class="colstack_left">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a href="/panel/logs/mod/">Logs</a></div>
<div class="rowitem"><a href="/panel/logs/mod/">Logs</a></div>
</div>
<div class="colstack_item">
<div class="rowitem passive"><a href="/panel/logs/mod/">Moderation Logs</a></div>
@ -12,7 +12,7 @@
</div>
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Moderation Logs</a></div>
<div class="rowitem"><a>Moderation Logs</a></div>
</div>
<div id="panel_modlogs" class="colstack_item">
{{range .Logs}}

View File

@ -2,7 +2,7 @@
{{template "panel-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Plugins</a></div>
<div class="rowitem"><a>Plugins</a></div>
</div>
<div id="panel_plugins" class="colstack_item">
{{range .ItemList}}
@ -12,6 +12,9 @@
<span style="float: right;">
{{if .Settings}}<a href="/panel/settings/" class="panel_tag">Settings</a>{{end}}
{{if .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Deactivate</a>
{{else if .Installable}}
{{/** TO-DO: Write a custom template interpreter to fix this nonsense **/}}
{{if .Installed}}<a href="/panel/plugins/activate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Activate</a>{{else}}<a href="/panel/plugins/install/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Install</a>{{end}}
{{else}}<a href="/panel/plugins/activate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">Activate</a>{{end}}
</span>
</div>

View File

@ -2,7 +2,7 @@
{{template "panel-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Edit Setting</a></div>
<div class="rowitem"><a>Edit Setting</a></div>
</div>
<div id="panel_setting" class="colstack_item">
<form action="/panel/settings/edit/submit/{{.Something.Name}}?session={{.CurrentUser.Session}}" method="post">

View File

@ -2,7 +2,7 @@
{{template "panel-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Settings</a></div>
<div class="rowitem"><a>Settings</a></div>
</div>
<div id="panel_settings" class="colstack_item">
{{range $key, $value := .Something}}

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<div class="colstack_left">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a href="/panel/themes/">Theme Manager</a></div>
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item">
<div class="rowitem passive"><a href="#">Widgets</a></div>
@ -20,7 +20,7 @@
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Primary Themes</a></div>
<div class="rowitem"><a>Primary Themes</a></div>
</div>
<div id="panel_primary_themes" class="colstack_item panel_themes">
{{range .PrimaryThemes}}
@ -38,7 +38,7 @@
{{end}}
</div>
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Variant Themes</a></div>
<div class="rowitem"><a>Variant Themes</a></div>
</div>
<div id="panel_variant_themes" class="colstack_item panel_themes">
{{range .VariantThemes}}

View File

@ -2,7 +2,7 @@
{{template "panel-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>User Editor</a></div>
<div class="rowitem"><a>User Editor</a></div>
</div>
<div id="panel_user" class="colstack_item">
<form action="/panel/users/edit/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post">

View File

@ -3,7 +3,7 @@
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Users</a></div>
<div class="rowitem"><a>Users</a></div>
</div>
<div id="panel_users" class="colstack_item">
{{range .ItemList}}

View File

@ -19,8 +19,8 @@
</div>
</div>
<div class="colblock_right" style="width: calc(95% - 210px);">
<div class="rowitem rowhead"><a>Comments</a></div>
<div class="colblock_right rowhead" style="width: calc(95% - 210px);">
<div class="rowitem"><a>Comments</a></div>
</div>
<div id="profile_comments" class="colblock_right" style="overflow: hidden;border-top: none;width:calc(95% - 210px);">{{range .ItemList}}
<div class="rowitem passive deletable_block editable_parent simple" style="{{if .Avatar}}background-image: url({{.Avatar}}), url(/static/white-dot.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat: no-repeat, repeat-y;background-size: 128px;padding-left: 136px;{{.Css}}{{end}}">

View File

@ -1,6 +1,6 @@
{{template "header.html" . }}
<div class="rowblock">
<div class="rowitem rowhead"><a>Create Account</a></div>
<div class="rowblock rowhead">
<div class="rowitem"><a>Create Account</a></div>
</div>
<div class="rowblock">
<form action="/accounts/create/submit/" method="post">

View File

@ -0,0 +1,30 @@
{{template "header.html" . }}
<div class="rowblock rowhead">
<div class="rowitem"><a>Create Group</a></div>
</div>
<div class="rowblock">
<form action="/group/create/submit/" method="post">
<div class="formrow">
<div class="formitem formlabel"><a>Group Name</a></div>
<div class="formitem"><input name="group_name" type="text" placeholder="Group Name" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Description</a></div>
<div class="formitem"><input name="group_desc" type="text" placeholder="Description" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Visibility</a></div>
<div class="formitem">
<select name="group_privacy">
<option val="0">Public</option>
<option val="1">Protected</option>
<option val="2">Private</option>
</select>
</div>
</div>
<div class="formrow">
<div class="formitem"><button name="group_button" class="formbutton">Create Group</button></div>
</div>
</form>
</div>
{{template "footer.html" . }}

View File

@ -0,0 +1,48 @@
<style>
.miniMenu {
min-height: 41px;
border-top: 1px solid rgba(255,255,255,0.5);
background-color: rgba(255,255,255,0.5);
position: relative;
top: 0px;
z-index: 0;
padding-top: 3px;
}
.menuItem:first-child {
margin-left: 5px;
}
.menuItem {
float: left;
min-height: 38px;
padding: 10px;
border-right: 1px solid rgba(204,204,204,0.9);
background-color: rgba(255,255,255,0.9);
margin-right: 5px;
max-height: 30px;
box-shadow: 0 7px 15px rgba(0,0,0,0.1);
border-top: white;
border-left: white;
position: relative;
top: 2px;
}
.menuItem:hover {
top: 4px;
}
.menuItem a {
color: black;
text-decoration: none;
}
.rightMenu {
float: right;
border-right: none;
border-left: 1px solid #ccc;
}
.sgBackdrop {
background-image: url('/uploads/socialgroup_1.jpg');
min-height: 150px;
width: 100%;
border: 1px solid #ccc;
padding-top: calc(150px - 38px);
border-bottom: none;
}
</style>

View File

@ -0,0 +1,19 @@
{{template "header.html" . }}
<div class="rowblock opthead">
<div class="rowitem"><a>Group List</a></div>
</div>
<div class="rowblock">
{{range .GroupList}}<div class="rowitem datarow">
<span style="float: left;">
<a href="{{.Link}}" style="">{{.Name}}</a>
<br /><span class="rowsmall">{{.Desc}}</span>
</span>
<span style="float: right;">
<span style="float: right;font-size: 14px;">{{.MemberCount}} members</span>
<br /><span class="rowsmall">{{.LastUpdateTime}}</span>
</span>
<div style="clear: both;"></div>
</div>
{{else}}<div class="rowitem passive">There aren't any visible groups.</div>{{end}}
</div>
{{template "footer.html" . }}

View File

@ -0,0 +1,34 @@
{{template "header.html" . }}
{{/** TO-DO: Move this into a CSS file **/}}
{{template "socialgroups_css.html" . }}
{{/** TO-DO: Port the page template functions to the template interpreter **/}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/group/members/{{.SocialGroup.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/group/members/{{.SocialGroup.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/group/members/{{.SocialGroup.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div class="sgBackdrop">
<div class="miniMenu">
<div class="menuItem"><a href="#">Test Group</a></div>
<div class="menuItem"><a href="#">About</a></div>
<div class="menuItem"><a href="#">Members</a></div>
<div class="menuItem rightMenu"><a href="#">Edit</a></div>
<div class="menuItem rightMenu"><a href="/group/join/{{.SocialGroup.ID}}">Join</a></div>
</div>
<div style="clear: both;"></div>
</div>
<div id="socialgroups_member_list" class="rowblock member_list" style="position: relative;z-index: 50;">
{{range .ItemList}}<div class="rowitem passive datarow" style="{{if .User.Avatar}}background-image: url({{.User.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 78px;{{end}}{{if .Offline}}background-color: #eaeaea;{{else if gt .Rank 0}}background-color: #e6f3ff;{{end}}">
<span style="float: right;">
<span class="rank" style="font-size: 15px;">{{.RankString}}</span><br />
<span class="joinedAt rowsmall">{{.JoinedAt}}</span>
</span>
<span>
<a class="rowtopic" href="{{.Link}}">{{.User.Name}}</a>
{{/** Use this for badges instead of rank? Both? Group Titles? **/}}
<br /><span class="rowsmall postCount">{{.PostCount}} posts</span>
</span>
</div>
{{end}}
</div>
{{template "footer.html" . }}

View File

@ -0,0 +1,34 @@
{{template "header.html" . }}
{{/** TO-DO: Move this into a CSS file **/}}
{{template "socialgroups_css.html" . }}
{{/** TO-DO: Port the page template functions to the template interpreter **/}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/group/{{.SocialGroup.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/group/{{.SocialGroup.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/group/{{.SocialGroup.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div class="sgBackdrop">
<div class="miniMenu">
<div class="menuItem"><a href="#">Test Group</a></div>
<div class="menuItem"><a href="#">About</a></div>
<div class="menuItem"><a href="#">Members</a></div>
<div class="menuItem rightMenu"><a href="#">Edit</a></div>
<div class="menuItem rightMenu"><a href="/group/join/{{.SocialGroup.ID}}">Join</a></div>
</div>
<div style="clear: both;"></div>
</div>
<div id="forum_topic_list" class="rowblock topic_list" style="position: relative;z-index: 50;">
{{range .ItemList}}<div class="rowitem passive datarow" style="{{if .Avatar}}background-image: url({{.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}{{if .Sticky}}background-color: #FFFFCC;{{else if .Is_Closed}}background-color: #eaeaea;{{end}}">
<span class="rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span>
</span>
<span>
<a class="rowtopic" href="/topic/{{.Slug}}.{{.ID}}">{{.Title}}</a>
<br /><a class="rowsmall" href="/user/{{.UserSlug}}.{{.CreatedBy}}">Starter: {{.CreatedByName}}</a>
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E{{end}}</span>
</span>
</div>
{{else}}<div class="rowitem passive">There aren't any topics in here yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">Start one?</a>{{end}}</div>{{end}}
</div>
{{template "footer.html" . }}

View File

@ -7,9 +7,9 @@
<a class="next_link" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}">&gt;</a>
</div>{{end}}
<div class="rowblock topic_block">
<div class="rowblock rowhead topic_block">
<form action='/topic/edit/submit/{{.Topic.ID}}' method="post">
<div class="rowitem rowhead topic_item"{{if .Topic.Sticky}} style="background-color:#FFFFEA;"{{else if .Topic.Is_Closed}} style="background-color:#eaeaea;"{{end}}>
<div class="rowitem topic_item"{{if .Topic.Sticky}} style="background-color:#FFFFEA;"{{else if .Topic.Is_Closed}} style="background-color:#eaeaea;"{{end}}>
<a class='topic_name hide_on_edit'>{{.Topic.Title}}</a>
{{if .Topic.Is_Closed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
{{if .CurrentUser.Perms.EditTopic}}

View File

@ -3,9 +3,9 @@
{{if ne .LastPage .Page}}<link rel="prerender" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div class="rowblock topic_block">
<div class="rowblock rowhead topic_block">
<form action='/topic/edit/submit/{{.Topic.ID}}' method="post">
<div class="rowitem topic_item rowhead{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.Is_Closed}} topic_closed_head{{end}}">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.Is_Closed}} topic_closed_head{{end}}">
<a class='topic_name hide_on_edit'>{{.Topic.Title}}</a>
{{if .Topic.Is_Closed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
{{if .CurrentUser.Perms.EditTopic}}

View File

@ -1,15 +1,15 @@
{{template "header.html" . }}
<div class="rowblock">
<div class="rowitem rowhead"><a>Topic List</a></div>
<div class="rowblock rowhead">
<div class="rowitem"><a>Topic List</a></div>
</div>
<div id="topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="rowitem passive datarow" style="{{if .Avatar}}background-image: url({{.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}{{if .Sticky}}background-color: #FFFFCC;{{else if .Is_Closed}}background-color: #eaeaea;{{end}}">
{{range .ItemList}}<div class="rowitem passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Avatar}}background-image: url({{.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span class="rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span>
</span>
<span>
<a class="rowtopic" href="/topic/{{.Slug}}.{{.ID}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall" href="/forum/{{.ParentID}}">{{.ForumName}}</a>{{end}}
<a class="rowtopic" href="/topic/{{.Slug}}.{{.ID}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}}
<br /><a class="rowsmall" href="/user/{{.UserSlug}}.{{.CreatedBy}}">Starter: {{.CreatedByName}}</a>
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E{{end}}</span>
</span>

View File

@ -0,0 +1,6 @@
<div class="rowblock rowhead">
<div class="rowitem">{{.Name}}</div>
</div>
<div class="rowblock">{{range .MenuList}}
<div class="rowitem {{if .Compact}}datarow{{end}}"><a href="{{.Location}}">{{.Text}}</a></div>
{{end}}</div>

View File

@ -1,5 +1,5 @@
<div class="rowblock">
<div class="rowitem rowhead">{{.Name}}</div>
<div class="rowblock rowhead">
<div class="rowitem">{{.Name}}</div>
</div>
<div class="rowblock">
<div class="rowitem">{{.Text}}</div>

View File

@ -140,7 +140,7 @@ func init_themes() {
theme.Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file
if theme.FullImage != "" {
if debug {
if debug_mode {
log.Print("Adding theme image")
}
err = add_static_file("./themes/" + themeName + "/" + theme.FullImage, "./themes/" + themeName)
@ -155,7 +155,7 @@ func init_themes() {
func add_theme_static_files(theme Theme) {
err := filepath.Walk("./themes/" + theme.Name + "/public", func(path string, f os.FileInfo, err error) error {
if debug {
if debug_mode {
log.Print("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
}
if err != nil {
@ -190,7 +190,7 @@ func add_theme_static_files(theme Theme) {
gzip_data := compress_bytes_gzip(data)
static_files["/static" + path] = SFile{data,gzip_data,0,int64(len(data)),int64(len(gzip_data)),mime.TypeByExtension(ext),f,f.ModTime().UTC().Format(http.TimeFormat)}
if debug {
if debug_mode {
log.Print("Added the '" + path + "' static file for default theme " + theme.Name + ".")
}
return nil

View File

@ -151,12 +151,13 @@ li:hover {
hr { color: silver; border: 1px solid silver; }
.rowhead {
/* I HATE CSS for being so incompetently designed that I have to declare this for THREE different elements rather than just having a statement go back up the tree. What on earth is the W3C doing?! */
.rowhead .rowitem, .colstack_head .rowitem, .opthead .rowitem {
border-top: none;
font-weight: bold;
color: white;
}
.rowhead:hover {
.rowhead .rowitem:hover, .colstack_head .rowItem:hover, .opthead .rowitem:hover {
color: rgba(200,200,200,1);
transition: color 1s;
-moz-transition: color 1s;
@ -192,7 +193,7 @@ hr { color: silver; border: 1px solid silver; }
font-size:12px;
}
.rowhead, .colhead {
.rowhead .rowitem, .colhead, .opthead .rowitem, .colstack_head .rowitem {
background: #ce2424;
background: -webkit-gradient(linear, 0 0, 0 bottom, from(#f97779), to(#ce2424));
background: -moz-linear-gradient(#f97779, #ce2424);
@ -213,13 +214,45 @@ hr { color: silver; border: 1px solid silver; }
overflow: hidden;
text-overflow: ellipsis;
}
.rowhead a { color: white; display: block; padding-top: 5px; }
.rowhead span { display: block; padding-top: 5px; }
.colhead a { color: white; display: block; padding-top: 5px; }
.colhead span { display: block; padding-top: 5px; }
.show_on_edit { display: none; }
.rowhead .topic_status_e { display: none !important; }
.topic_button { float: right; position: relative; top: -22px; margin-right: 2px; border-style: solid !important; }
.rowhead a, .opthead a, .colstack_head a {
color: white;
display: block;
padding-top: 5px;
}
.rowhead span, .opthead span, .colstack_head span {
display: block;
padding-top: 5px;
}
.colhead a {
color: white;
display: block;
padding-top: 5px;
}
.colhead span {
display: block;
padding-top: 5px;
}
.show_on_edit {
display: none;
}
.rowhead .topic_status_e, .opthead .topic_status_e, .colstack_head .topic_status_e {
display: none !important;
}
.topic_sticky {
background-color: rgb(255,255,234);
}
.topic_closed {
background-color: rgb(248,248,248);
}
.topic_button {
float: right;
position: relative;
top: -22px;
margin-right: 2px;
border-style: solid !important;
}
.colblock_left {
padding: 0px;
@ -736,8 +769,15 @@ blockquote p {
background-image: -webkit-linear-gradient(bottom, #4c4c4c, #2e2e2e);
}
.rowblock { border-left: none; border-right: none; border-bottom: none; }
.rowitem, .rowhead, .tbody { border-left: none; border-right: none; }
.rowblock {
border-left: none;
border-right: none;
border-bottom: none;
}
.rowitem, .tbody {
border-left: none;
border-right: none;
}
}
@media(max-width: 620px) {

View File

@ -139,12 +139,13 @@ li:hover {
hr { color: silver; border: 1px solid silver; }
.rowhead {
/* I HATE CSS for being so incompetently designed that I have to declare this for THREE different elements rather than just having a statement go back up the tree. What on earth is the W3C doing?! */
.rowhead .rowitem, .opthead .rowitem, .colstack_head .rowitem {
border-top: none;
font-weight: bold;
color: white;
}
.rowhead:hover {
.rowhead .rowitem:hover, .opthead .rowitem:hover, .colstack_head .rowitem:hover {
color: rgba(200,200,200,1);
transition: color 1s;
-moz-transition: color 1s;
@ -179,7 +180,7 @@ hr { color: silver; border: 1px solid silver; }
font-size:12px;
}
.rowhead, .colhead {
.rowhead .rowitem, .opthead .rowitem, .colstack_head .rowitem, .colhead {
background: #ce2424;
background: -webkit-gradient(linear, 0 0, 0 bottom, from(#f97779), to(#ce2424));
background: -moz-linear-gradient(#f97779, #ce2424);
@ -200,13 +201,36 @@ hr { color: silver; border: 1px solid silver; }
overflow: hidden;
text-overflow: ellipsis;
}
.rowhead a { color: white; display: block; padding-top: 5px; }
.rowhead span { display: block; padding-top: 5px; }
.rowhead a, .opthead a, .colstack_head a {
color: white;
display: block;
padding-top: 5px;
}
.rowhead span, .opthead span, .colstack_head span {
display: block;
padding-top: 5px;
}
.colhead a { color: white; display: block; padding-top: 5px; }
.colhead span { display: block; padding-top: 5px; }
.show_on_edit { display: none; }
.rowhead .topic_status_e { display: none !important; }
.topic_button { float: right; position: relative; top: -22px; margin-right: 2px; border-style: solid !important; }
.rowhead .topic_status_e, .opthead .topic_status_e, .colstack_head .topic_status_e {
display: none !important;
}
.topic_sticky {
background-color: rgb(255,255,234);
}
.topic_closed {
background-color: rgb(248,248,248);
}
.topic_button {
float: right;
position: relative;
top: -22px;
margin-right: 2px;
border-style: solid !important;
}
.colblock_left {
padding: 0px;
@ -748,10 +772,19 @@ blockquote p {
background-image: -webkit-linear-gradient(bottom, #4c4c4c, #2e2e2e);
}
.rowblock { border-left: none; border-right: none; border-bottom: none; }
.rowitem { border-left: none; border-right: none; }
.rowhead { border-left: none; border-right: none; }
.tbody { border-left: none; border-right: none; }
.rowblock {
border-left: none;
border-right: none;
border-bottom: none;
}
.rowitem {
border-left: none;
border-right: none;
}
.tbody {
border-left: none;
border-right: none;
}
.forumLastposter { width: 35%; }
.forumLastposter .title { width: 90px; }
.cell_author img { display: none; }

View File

@ -301,6 +301,14 @@ button {
}
/* Topics */
.topic_sticky {
background-color: rgb(255,255,234);
}
.topic_closed {
background-color: rgb(248,248,248);
}
.topic_status {
text-transform: none;
margin-left: 8px;
@ -314,7 +322,9 @@ button {
}
.topic_status:empty { display: none; }
.rowhead { background: linear-gradient(to bottom, white, hsl(0, 0%, 93%)); }
.rowhead .rowitem, .opthead .rowitem, .colstack_head .rowitem {
background: linear-gradient(to bottom, white, hsl(0, 0%, 93%));
}
.topic_sticky_head {
background-color: #FFFFEA;
background: linear-gradient(to bottom, hsl(60, 70%, 96%), hsl(60, 70%, 89%)), url('/static/fabric-base-simple-alpha.png');

View File

@ -299,6 +299,14 @@ button {
}
/* Topics */
.topic_sticky {
background-color: rgb(255,255,234);
}
.topic_closed {
background-color: rgb(248,248,248);
}
.topic_status {
text-transform: none;
margin-left: 8px;

View File

@ -18,7 +18,7 @@ ul {
height: 36px;
list-style-type: none;
border: 1px solid #ccc;
background-color: white;
background-color: rgb(248,248,248);
margin-bottom: 12px;
}
li {
@ -26,6 +26,8 @@ li {
padding-left: 10px;
padding-top: 8px;
padding-bottom: 8px;
background: white;
border-bottom: 1px solid #ccc;
}
li:hover { background: rgb(250,250,250); }
li a {
@ -101,7 +103,7 @@ li a {
padding: 8px;
overflow: hidden;
text-overflow: ellipsis;
padding-top: 15px;
padding-top: 17px;
padding-bottom: 16px;
}
.alertItem.withAvatar {
@ -220,6 +222,13 @@ li a {
.stat_red { background-color: #ffb2b2; border-color: #ffb2b2; }
.stat_disabled { background-color: lightgray; border-color: lightgray; }
.rowhead {
border-bottom: none;
}
.rowhead .rowitem, .colstack_head .rowitem {
background-color: rgb(252,252,252);
}
.rowitem {
width: 100%;
/*padding-left: 8px;
@ -242,6 +251,27 @@ li a {
.top_post { margin-bottom: 12px; }
.opthead { display: none; }
.rowitem.has_opt {
float: left;
width: calc(100% - 50px);
border-right: 1px solid #ccc;
border-bottom: none;
}
.opt {
float: left;
font-size: 32px;
height: 100%;
background-color: white;
width: 50px;
text-align: center;
}
.create_topic_opt {
color: rgb(120,120,120);
}
.locked_opt {
color: rgb(80,80,80);
}
.datarow {
padding-top: 10px;
padding-bottom: 10px;
@ -302,6 +332,14 @@ button {
}
/* Topics */
.topic_sticky {
background-color: rgb(255,255,234);
}
.topic_closed {
background-color: rgb(248,248,248);
}
.topic_status {
text-transform: none;
margin-left: 8px;

View File

@ -71,7 +71,7 @@ type TopicsRow struct
LastReplyAt string
//LastReplyBy int
ParentID int
Status string // Deprecated. Marked for removal.
Status string // Deprecated. Marked for removal. -Is there anything we could use it for?
IpAddress string
PostCount int
LikeCount int
@ -89,6 +89,7 @@ type TopicsRow struct
Level int
ForumName string //TopicsRow
ForumLink string
}
func get_topicuser(tid int) (TopicUser,error) {
@ -170,3 +171,7 @@ func build_topic_url(slug string, tid int) string {
}
return "/topic/" + slug + "." + strconv.Itoa(tid)
}
func get_topic_url_prefix() string {
return "/topic/"
}

View File

@ -5,6 +5,7 @@ import "sync"
import "database/sql"
import "./query_gen/lib"
// TO-DO: Add the watchdog goroutine
var topics TopicStore
type TopicStore interface {

View File

@ -1,5 +1,7 @@
echo "Updating the MySQL Driver"
go get -u github.com/go-sql-driver/mysql
echo "Updating the PostgreSQL Driver"
go get -u github.com/lib/pq
echo "Updating bcrypt"
go get -u golang.org/x/crypto/bcrypt
echo "Updating gopsutil"

View File

@ -5,6 +5,12 @@ if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Updating the PostgreSQL Driver
go get -u github.com/lib/pq
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Updating bcrypt
go get -u golang.org/x/crypto/bcrypt

112
user.go
View File

@ -12,9 +12,13 @@ import (
)
var guest_user User = User{ID:0,Group:6,Perms:GuestPerms}
var SimpleSessionCheck func(http.ResponseWriter, *http.Request) (User,bool) = _simple_session_check
var PanelSessionCheck func(http.ResponseWriter, *http.Request) (User,HeaderVars,bool) = _panel_session_check
var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request) (User,bool) = _simple_panel_session_check
var PreRoute func(http.ResponseWriter, *http.Request) (User,bool) = _pre_route
var PanelSessionCheck func(http.ResponseWriter, *http.Request, *User) (HeaderVars,bool) = _panel_session_check
var SimplePanelSessionCheck func(http.ResponseWriter, *http.Request, *User) bool = _simple_panel_session_check
var SimpleForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (success bool) = _simple_forum_session_check
var ForumSessionCheck func(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars HeaderVars, success bool) = _forum_session_check
var SessionCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, success bool) = _session_check
var CheckPassword func(real_password string, password string, salt string) (err error) = BcryptCheckPassword
type User struct
{
@ -30,11 +34,12 @@ type User struct
Is_Super_Admin bool
Is_Banned bool
Perms Perms
PluginPerms map[string]bool
Session string
Loggedin bool
Avatar string
Message string
URLPrefix string
URLPrefix string // Move this to another table? Create a user lite?
URLName string
Tag string
Level int
@ -51,7 +56,7 @@ type Email struct
Token string
}
func CheckPassword(real_password string, password string, salt string) (err error) {
func BcryptCheckPassword(real_password string, password string, salt string) (err error) {
return bcrypt.CompareHashAndPassword([]byte(real_password), []byte(password + salt))
}
@ -86,7 +91,14 @@ func SendValidationEmail(username string, email string, token string) bool {
}
// TO-DO: Support for left sidebars and sidebars on both sides
func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars) {
// http.Request is for context.Context middleware. Mostly for plugin_socialgroups right now
func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars, r *http.Request) {
if vhooks["intercept_build_widgets"] != nil {
if run_vhook("intercept_build_widgets", zone, data, headerVars, r).(bool) {
return
}
}
//fmt.Println("themes[defaultTheme].Sidebars",themes[defaultTheme].Sidebars)
if themes[defaultTheme].Sidebars == "right" {
if len(docks.RightSidebar) != 0 {
@ -103,11 +115,17 @@ func BuildWidgets(zone string, data interface{}, headerVars *HeaderVars) {
}
}
func SimpleForumSessionCheck(w http.ResponseWriter, r *http.Request, fid int) (user User, success bool) {
user, success = SimpleSessionCheck(w,r)
func _simple_forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fid int) (success bool) {
if !fstore.Exists(fid) {
PreError("The target forum doesn't exist.",w,r)
return user, false
return false
}
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
if vhooks["simple_forum_check_pre_perms"] != nil {
if run_vhook("simple_forum_check_pre_perms", w, r, user, &fid, &success).(bool) {
return success
}
}
fperms := groups[user.Group].Forums[fid]
@ -125,18 +143,24 @@ func SimpleForumSessionCheck(w http.ResponseWriter, r *http.Request, fid int) (u
if len(fperms.ExtData) != 0 {
for name, perm := range fperms.ExtData {
user.Perms.ExtData[name] = perm
user.PluginPerms[name] = perm
}
}
}
return user, success
return true
}
func ForumSessionCheck(w http.ResponseWriter, r *http.Request, fid int) (user User, headerVars HeaderVars, success bool) {
user, headerVars, success = SessionCheck(w,r)
func _forum_session_check(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars HeaderVars, success bool) {
headerVars, success = SessionCheck(w,r,user)
if !fstore.Exists(fid) {
NotFound(w,r)
return user, headerVars, false
return headerVars, false
}
if vhooks["forum_check_pre_perms"] != nil {
if run_vhook("forum_check_pre_perms", w, r, user, &fid, &success, &headerVars).(bool) {
return headerVars, success
}
}
fperms := groups[user.Group].Forums[fid]
@ -156,19 +180,18 @@ func ForumSessionCheck(w http.ResponseWriter, r *http.Request, fid int) (user Us
if len(fperms.ExtData) != 0 {
for name, perm := range fperms.ExtData {
user.Perms.ExtData[name] = perm
user.PluginPerms[name] = perm
}
}
}
return user, headerVars, success
return headerVars, success
}
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
func _panel_session_check(w http.ResponseWriter, r *http.Request) (user User, headerVars HeaderVars, success bool) {
user, success = SimpleSessionCheck(w,r)
func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, success bool) {
if !user.Is_Super_Mod {
NoPermissions(w,r,user)
return user, headerVars, false
NoPermissions(w,r,*user)
return headerVars, false
}
headerVars.Stylesheets = append(headerVars.Stylesheets,"panel.css")
@ -189,19 +212,17 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request) (user User, he
}
}
return user, headerVars, success
return headerVars, true
}
func _simple_panel_session_check(w http.ResponseWriter, r *http.Request) (user User, success bool) {
user, success = SimpleSessionCheck(w,r)
func _simple_panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (success bool) {
if !user.Is_Super_Mod {
NoPermissions(w,r,user)
return user, false
NoPermissions(w,r,*user)
return false
}
return user, success
return true
}
func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, headerVars HeaderVars, success bool) {
user, success = SimpleSessionCheck(w,r)
func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerVars HeaderVars, success bool) {
if user.Is_Banned {
headerVars.NoticeList = append(headerVars.NoticeList,"Your account has been suspended. Some of your permissions may have been revoked.")
}
@ -223,41 +244,24 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, headerVars
}
}
return user, headerVars, success
return headerVars, true
}
func _simple_session_check(w http.ResponseWriter, r *http.Request) (User,bool) {
// Are there any session cookies..?
cookie, err := r.Cookie("uid")
if err != nil {
return guest_user, true
func _pre_route(w http.ResponseWriter, r *http.Request) (User,bool) {
user, halt := auth.SessionCheck(w,r)
if halt {
return *user, false
}
uid, err := strconv.Atoi(cookie.Value)
if err != nil {
return guest_user, true
}
cookie, err = r.Cookie("session")
if err != nil {
return guest_user, true
}
// Is this session valid..?
user, err := users.CascadeGet(uid)
if err == ErrNoRows {
return guest_user, true
} else if err != nil {
InternalError(err,w,r)
return guest_user, false
}
if user.Session == "" || cookie.Value != user.Session {
return guest_user, true
if user == &guest_user {
return *user, true
}
if user.Is_Super_Admin {
user.Perms = AllPerms
user.PluginPerms = AllPluginPerms
} else {
user.Perms = groups[user.Group].Perms
user.PluginPerms = groups[user.Group].PluginPerms
}
host, _, err := net.SplitHostPort(r.RemoteAddr)

Some files were not shown because too many files have changed in this diff Show More