diff --git a/auth.go b/auth.go index a9845577..1f720cf3 100644 --- a/auth.go +++ b/auth.go @@ -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 } diff --git a/cache.go b/cache.go index 80ff68aa..56a38ed7 100644 --- a/cache.go +++ b/cache.go @@ -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 { diff --git a/config.go b/config.go index 1cbe7843..19764dab 100644 --- a/config.go +++ b/config.go @@ -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 diff --git a/database.go b/database.go index e7f0739e..b6a92f5c 100644 --- a/database.go +++ b/database.go @@ -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() diff --git a/errors.go b/errors.go index 0a7e58c7..a478ba21 100644 --- a/errors.go +++ b/errors.go @@ -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()) diff --git a/extend.go b/extend.go index 563e561c..f3e9ae38 100644 --- a/extend.go +++ b/extend.go @@ -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 +} diff --git a/files.go b/files.go index 15f9ad3a..70bbf5d0 100644 --- a/files.go +++ b/files.go @@ -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 diff --git a/forum.go b/forum.go index ae32c09b..8f5d1160 100644 --- a/forum.go +++ b/forum.go @@ -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/" +} diff --git a/forum_store.go b/forum_store.go index fc119281..d55d0169 100644 --- a/forum_store.go +++ b/forum_store.go @@ -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 diff --git a/gen_mysql.go b/gen_mysql.go index 59b8b59f..e2d062d7 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -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 { diff --git a/gen_router.go b/gen_router.go index 1db8c590..97226773 100644 --- a/gen_router.go +++ b/gen_router.go @@ -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) diff --git a/general_test.go b/general_test.go index 6dd711c8..1a38b5cf 100644 --- a/general_test.go +++ b/general_test.go @@ -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)) } diff --git a/group.go b/group.go index 2fdb1f55..62745124 100644 --- a/group.go +++ b/group.go @@ -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 != "" } diff --git a/images/forum-list.PNG b/images/forum-list.PNG index 4b6fe612..a76a7767 100644 Binary files a/images/forum-list.PNG and b/images/forum-list.PNG differ diff --git a/images/socialgroups_group_members.png b/images/socialgroups_group_members.png new file mode 100644 index 00000000..4a05413d Binary files /dev/null and b/images/socialgroups_group_members.png differ diff --git a/images/socialgroups_list.png b/images/socialgroups_list.png new file mode 100644 index 00000000..af95097c Binary files /dev/null and b/images/socialgroups_list.png differ diff --git a/images/socialgroups_view_group.png b/images/socialgroups_view_group.png new file mode 100644 index 00000000..66fd6df5 Binary files /dev/null and b/images/socialgroups_view_group.png differ diff --git a/images/topic-list.png b/images/topic-list.png index 3b0fb8f1..f44282c5 100644 Binary files a/images/topic-list.png and b/images/topic-list.png differ diff --git a/install-gosora-linux b/install-gosora-linux index 82438ade..bd1482db 100644 --- a/install-gosora-linux +++ b/install-gosora-linux @@ -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" diff --git a/install.bat b/install.bat index 83c69b14..d1218411 100644 --- a/install.bat +++ b/install.bat @@ -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 diff --git a/install/install.go b/install/install.go index 8c573bf0..51538454 100644 --- a/install/install.go +++ b/install/install.go @@ -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 `) diff --git a/main.go b/main.go index 45c4a706..a0e95a3e 100644 --- a/main.go +++ b/main.go @@ -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 { diff --git a/misc_test.go b/misc_test.go new file mode 100644 index 00000000..a0e19034 --- /dev/null +++ b/misc_test.go @@ -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) + } + } +} diff --git a/mod_routes.go b/mod_routes.go index f0449c73..79754ade 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -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 diff --git a/mysql.go b/mysql.go index 1893d3f7..59349591 100644 --- a/mysql.go +++ b/mysql.go @@ -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 } diff --git a/mysql.sql b/mysql.sql index a6453283..c2860b02 100644 --- a/mysql.sql +++ b/mysql.sql @@ -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()); diff --git a/no_websockets.go b/no_websockets.go index 0271ca92..5d76f851 100644 --- a/no_websockets.go +++ b/no_websockets.go @@ -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) { } diff --git a/pages.go b/pages.go index a58f4e1e..8592f9bb 100644 --- a/pages.go +++ b/pages.go @@ -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","
",-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,"$2$3//$4") msg = strings.Replace(msg,"\n","
",-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 } diff --git a/panel_routes.go b/panel_routes.go index 01969607..360deecd 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -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) diff --git a/permissions.go b/permissions.go index bbbe7aa1..3b951fa9 100644 --- a/permissions.go +++ b/permissions.go @@ -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) +} diff --git a/pgsql.go b/pgsql.go new file mode 100644 index 00000000..9f139699 --- /dev/null +++ b/pgsql.go @@ -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) +} diff --git a/phrases.go b/phrases.go index 1ecae3ed..e4106732 100644 --- a/phrases.go +++ b/phrases.go @@ -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 } diff --git a/plugin_bbcode.go b/plugin_bbcode.go index 5d5fe58b..c3d472e1 100644 --- a/plugin_bbcode.go +++ b/plugin_bbcode.go @@ -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("[Invalid Number]") + bbcode_no_negative = []byte("[No Negative Numbers]") bbcode_missing_tag = []byte("[Missing Tag]") - + 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,"$1") msg = bbcode_italic.ReplaceAllString(msg,"$1") msg = bbcode_underline.ReplaceAllString(msg,"$1") msg = bbcode_strikethrough.ReplaceAllString(msg,"$1") - msg = bbcode_url.ReplaceAllString(msg,"$1$2//$3") - msg = bbcode_url_label.ReplaceAllString(msg,"$4") - msg = bbcode_quotes.ReplaceAllString(msg,"$1") + msg = bbcode_url.ReplaceAllString(msg,"$1$2//$3") + msg = bbcode_url_label.ReplaceAllString(msg,"$4") + msg = bbcode_quotes.ReplaceAllString(msg,"$1") + //msg = bbcode_code.ReplaceAllString(msg,"$1") 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("") - msgbytes = append(msgbytes, closer...) + close_under := []byte("") + close_italic := []byte("") + close_bold := []byte("") + close_strike := []byte("") + 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("") - msgbytes = append(msgbytes, closer...) + close_under := []byte("") + close_italic := []byte("") + close_bold := []byte("") + close_strike := []byte("") + 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,"$1$2//$3") - msg = bbcode_url_label.ReplaceAllString(msg,"$4") - msg = bbcode_quotes.ReplaceAllString(msg,"$1") + msg = bbcode_url.ReplaceAllString(msg,"$1$2//$3") + msg = bbcode_url_label.ReplaceAllString(msg,"$4") + msg = bbcode_quotes.ReplaceAllString(msg,"$1") + msg = bbcode_code.ReplaceAllString(msg,"$1") } - + 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("") - msgbytes = append(msgbytes, closer...) + close_under := []byte("") + close_italic := []byte("") + close_bold := []byte("") + close_strike := []byte("") + 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,"$1$2//$3") - msg = bbcode_url_label.ReplaceAllString(msg,"$4") - msg = bbcode_quotes.ReplaceAllString(msg,"$1") - // Convert [code] into class="codequotes" - //fmt.Println("guuuaaaa") + msg = bbcode_url_label.ReplaceAllString(msg,"$4") + msg = bbcode_quotes.ReplaceAllString(msg,"$1") + msg = bbcode_code.ReplaceAllString(msg,"$1") } else { msg = string(msgbytes[0:len(msgbytes) - 10]) } - + return msg } diff --git a/plugin_helloworld.go b/plugin_helloworld.go index 3a8c67a4..3580bb03 100644 --- a/plugin_helloworld.go +++ b/plugin_helloworld.go @@ -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 } diff --git a/plugin_markdown.go b/plugin_markdown.go index fabf864a..6f21785e 100644 --- a/plugin_markdown.go +++ b/plugin_markdown.go @@ -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,"$1") msg = markdown_bold.ReplaceAllString(msg,"$1") msg = markdown_italic.ReplaceAllString(msg,"$1") @@ -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-- diff --git a/plugin_skeleton.go b/plugin_skeleton.go index cd577330..784ed438 100644 --- a/plugin_skeleton.go +++ b/plugin_skeleton.go @@ -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() {} \ No newline at end of file +func deactivate_skeleton() {} diff --git a/plugin_socialgroups.go b/plugin_socialgroups.go new file mode 100644 index 00000000..8df9600e --- /dev/null +++ b/plugin_socialgroups.go @@ -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 +} diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 00000000..bbcb2d3b --- /dev/null +++ b/plugin_test.go @@ -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]","😀") + msgList = addMEPair(msgList,"[b]😀😀😀[/b]","😀😀😀") + msgList = addMEPair(msgList,"[b]hi[/b]","hi") + msgList = addMEPair(msgList,"[u]hi[/u]","hi") + msgList = addMEPair(msgList,"[i]hi[/i]","hi") + msgList = addMEPair(msgList,"[s]hi[/s]","hi") + 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]","hi") + msgList = addMEPair(msgList,"[code][b]hi[/b][/code]","[b]hi[/b]") + msgList = addMEPair(msgList,"[quote]hi[/quote]","hi") + msgList = addMEPair(msgList,"[quote][b]hi[/b][/quote]","hi") + msgList = addMEPair(msgList,"[quote][b]h[/b][/quote]","h") + msgList = addMEPair(msgList,"[quote][b][/b][/quote]","") + + 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 = "[Invalid Number][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 = "[No Negative Numbers][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**","hi") + msgList = addMEPair(msgList,"_hi_","hi") + msgList = addMEPair(msgList,"*hi*","hi") + msgList = addMEPair(msgList,"~hi~","hi") + msgList = addMEPair(msgList,"*hi**","hi*") + msgList = addMEPair(msgList,"**hi***","hi*") + msgList = addMEPair(msgList,"**hi*","*hi") + msgList = addMEPair(msgList,"***hi***","*hi") + msgList = addMEPair(msgList,"\\*hi\\*","*hi*") + msgList = addMEPair(msgList,"*~hi~*","hi") + 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,"_ _"," ") + msgList = addMEPair(msgList,"* *"," ") + msgList = addMEPair(msgList,"** **"," ") + msgList = addMEPair(msgList,"*** ***"," ") + + 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) + } + } +} diff --git a/query_gen/lib/builder.go b/query_gen/lib/builder.go index 13d24bf0..c07e7091 100644 --- a/query_gen/lib/builder.go +++ b/query_gen/lib/builder.go @@ -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 { diff --git a/query_gen/lib/mysql.go b/query_gen/lib/mysql.go index c87a5e1a..8dbe142e 100644 --- a/query_gen/lib/mysql.go +++ b/query_gen/lib/mysql.go @@ -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" +} diff --git a/query_gen/lib/querygen.go b/query_gen/lib/querygen.go index 55076651..572dbe27 100644 --- a/query_gen/lib/querygen.go +++ b/query_gen/lib/querygen.go @@ -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) diff --git a/query_gen/main.go b/query_gen/main.go index 72500a43..a09a9186 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -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 = ?") diff --git a/reply.go b/reply.go index b833650c..a270fb01 100644 --- a/reply.go +++ b/reply.go @@ -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 diff --git a/router.go b/router.go index 86230bf9..bac28b33 100644 --- a/router.go +++ b/router.go @@ -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) diff --git a/router_gen/main.go b/router_gen/main.go index 22228610..577a917e 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -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) diff --git a/router_gen/routes.go b/router_gen/routes.go index 01df8295..4c047fc6 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -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"}}, diff --git a/setting.go b/setting.go index e6a4f6a7..506b644f 100644 --- a/setting.go +++ b/setting.go @@ -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 diff --git a/template_forum.go b/template_forum.go index 449f915d..382fd12d 100644 --- a/template_forum.go +++ b/template_forum.go @@ -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) diff --git a/template_forums.go b/template_forums.go index 189ba2e8..2f5eae73 100644 --- a/template_forums.go +++ b/template_forums.go @@ -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) diff --git a/template_list.go b/template_list.go index 41bc01b1..68831fb6 100644 --- a/template_list.go +++ b/template_list.go @@ -84,10 +84,10 @@ var topic_7 []byte = []byte(`">> `) var topic_8 []byte = []byte(` -
+
-
@@ -249,10 +249,10 @@ var topic_alt_6 []byte = []byte(`?page=`) var topic_alt_7 []byte = []byte(`">>
`) var topic_alt_8 []byte = []byte(` -
+
-
@@ -441,8 +441,8 @@ var profile_16 []byte = []byte(`&type=user" class="profile_menu_item report_item
-
- +
`) var profile_17 []byte = []byte(` @@ -499,7 +499,7 @@ var profile_39 []byte = []byte(` `) var forums_0 []byte = []byte(`
`) @@ -508,85 +508,82 @@ var forums_2 []byte = []byte(`datarow`) var forums_3 []byte = []byte(`"> `) var forums_4 []byte = []byte(` - `) -var forums_7 []byte = []byte(` + `) +var forums_6 []byte = []byte(`
`) -var forums_8 []byte = []byte(` +var forums_7 []byte = []byte(`
`) -var forums_9 []byte = []byte(` - `) -var forums_12 []byte = []byte(` +var forums_8 []byte = []byte(` + `) +var forums_10 []byte = []byte(` `) -var forums_13 []byte = []byte(` - `) -var forums_16 []byte = []byte(` +var forums_11 []byte = []byte(` + `) +var forums_13 []byte = []byte(` `) -var forums_17 []byte = []byte(` +var forums_14 []byte = []byte(` `) -var forums_20 []byte = []byte(` +var forums_15 []byte = []byte(`" style="float: right;font-size: 14px;">`) +var forums_16 []byte = []byte(` `) -var forums_21 []byte = []byte(`
`) -var forums_22 []byte = []byte(``) -var forums_23 []byte = []byte(` +var forums_17 []byte = []byte(`
`) +var forums_18 []byte = []byte(``) +var forums_19 []byte = []byte(`
`) -var forums_24 []byte = []byte(`
You don't have access to any forums.
`) -var forums_25 []byte = []byte(` +var forums_20 []byte = []byte(`
You don't have access to any forums.
`) +var forums_21 []byte = []byte(`
`) var topics_0 []byte = []byte(` -
- +
`) -var topics_1 []byte = []byte(`
+var topics_1 []byte = []byte(`
`) -var topics_7 []byte = []byte(` replies
+var topics_8 []byte = []byte(` replies

`) -var topics_8 []byte = []byte(` +var topics_9 []byte = []byte(` `) -var topics_11 []byte = []byte(` `) -var topics_12 []byte = []byte(``) -var topics_14 []byte = []byte(``) -var topics_15 []byte = []byte(` +var topics_10 []byte = []byte(`.`) +var topics_11 []byte = []byte(`">`) +var topics_12 []byte = []byte(` `) +var topics_13 []byte = []byte(``) +var topics_15 []byte = []byte(``) +var topics_16 []byte = []byte(`
Starter: `) -var topics_18 []byte = []byte(` +var topics_17 []byte = []byte(`.`) +var topics_18 []byte = []byte(`">Starter: `) +var topics_19 []byte = []byte(` `) -var topics_19 []byte = []byte(` | 🔒︎`) -var topics_20 []byte = []byte(` +var topics_20 []byte = []byte(` | 🔒︎`) +var topics_21 []byte = []byte(`
`) -var topics_21 []byte = []byte(`
There aren't any topics yet.`) -var topics_22 []byte = []byte(` Start one?`) -var topics_23 []byte = []byte(`
`) -var topics_24 []byte = []byte(` +var topics_22 []byte = []byte(`
There aren't any topics yet.`) +var topics_23 []byte = []byte(` Start one?`) +var topics_24 []byte = []byte(`
`) +var topics_25 []byte = []byte(`
`) var forum_0 []byte = []byte(``) var forum_8 []byte = []byte(` -
-
`) -var forum_9 []byte = []byte(` - `) -var forum_10 []byte = []byte(`🔒︎`) -var forum_11 []byte = []byte(`New Topic`) -var forum_13 []byte = []byte(`
+
+ + `) +var forum_12 []byte = []byte(` +
🖊︎
+ `) +var forum_13 []byte = []byte(`
🔒︎
`) +var forum_14 []byte = []byte(` +
+ `) +var forum_15 []byte = []byte(` +
`) -var forum_14 []byte = []byte(`
+var forum_17 []byte = []byte(`
`) -var forum_20 []byte = []byte(` replies
+var forum_24 []byte = []byte(` replies

`) -var forum_21 []byte = []byte(` +var forum_25 []byte = []byte(` `) -var forum_24 []byte = []byte(` +var forum_26 []byte = []byte(`.`) +var forum_27 []byte = []byte(`">`) +var forum_28 []byte = []byte(`
Starter: `) -var forum_27 []byte = []byte(` +var forum_29 []byte = []byte(`.`) +var forum_30 []byte = []byte(`">Starter: `) +var forum_31 []byte = []byte(` `) -var forum_28 []byte = []byte(` | 🔒︎`) -var forum_29 []byte = []byte(` +var forum_32 []byte = []byte(` | 🔒︎`) +var forum_33 []byte = []byte(`
`) -var forum_30 []byte = []byte(`
There aren't any topics in this forum yet.`) -var forum_31 []byte = []byte(` Start one?`) -var forum_33 []byte = []byte(`
`) -var forum_34 []byte = []byte(` +var forum_34 []byte = []byte(`
There aren't any topics in this forum yet.`) +var forum_35 []byte = []byte(` Start one?`) +var forum_37 []byte = []byte(`
`) +var forum_38 []byte = []byte(`
`) diff --git a/template_topics.go b/template_topics.go index 411ed055..4d256b27 100644 --- a/template_topics.go +++ b/template_topics.go @@ -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) diff --git a/templates.go b/templates.go index 69f24d52..aef10289 100644 --- a/templates.go +++ b/templates.go @@ -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)) } diff --git a/templates/account-menu.html b/templates/account-menu.html index 0595ca9f..63c762da 100644 --- a/templates/account-menu.html +++ b/templates/account-menu.html @@ -1,6 +1,6 @@
- \ No newline at end of file +
diff --git a/templates/account-own-edit-avatar.html b/templates/account-own-edit-avatar.html index f98b28cf..d825fe18 100644 --- a/templates/account-own-edit-avatar.html +++ b/templates/account-own-edit-avatar.html @@ -1,8 +1,8 @@ {{template "header.html" . }} {{template "account-menu.html" . }}
-
- + {{if .CurrentUser.Avatar}}
diff --git a/templates/account-own-edit-email.html b/templates/account-own-edit-email.html index 19050656..539c2bba 100644 --- a/templates/account-own-edit-email.html +++ b/templates/account-own-edit-email.html @@ -1,8 +1,8 @@ {{template "header.html" . }} {{template "account-menu.html" . }}
-
- +
+
{{range .ItemList}} @@ -16,4 +16,4 @@ {{end}}
-{{template "footer.html" . }} \ No newline at end of file +{{template "footer.html" . }} diff --git a/templates/account-own-edit-username.html b/templates/account-own-edit-username.html index 9e2045db..b81ff94e 100644 --- a/templates/account-own-edit-username.html +++ b/templates/account-own-edit-username.html @@ -1,8 +1,8 @@ {{template "header.html" . }} {{template "account-menu.html" . }}
-
- +
diff --git a/templates/account-own-edit.html b/templates/account-own-edit.html index 49fa9c03..2e2182bf 100644 --- a/templates/account-own-edit.html +++ b/templates/account-own-edit.html @@ -1,8 +1,8 @@ {{template "header.html" . }} {{template "account-menu.html" . }}
-
- +
diff --git a/templates/areyousure.html b/templates/areyousure.html index d56be243..74da6096 100644 --- a/templates/areyousure.html +++ b/templates/areyousure.html @@ -1,10 +1,10 @@ {{template "header.html" . }} -
- +
{{.Something.Message}}

Continue
-{{template "footer.html" . }} \ No newline at end of file +{{template "footer.html" . }} diff --git a/templates/create-topic.html b/templates/create-topic.html index 854b2379..afc68e14 100644 --- a/templates/create-topic.html +++ b/templates/create-topic.html @@ -1,16 +1,16 @@ {{template "header.html" . }} -
- +
+ {{range .ItemList}}{{end}} +
-
+ diff --git a/templates/error.html b/templates/error.html index 294d12da..f55c57a0 100644 --- a/templates/error.html +++ b/templates/error.html @@ -1,8 +1,8 @@ {{template "header.html" . }} -
- +
{{.Something}}
-{{template "footer.html" . }} \ No newline at end of file +{{template "footer.html" . }} diff --git a/templates/forum.html b/templates/forum.html index 625ea84c..9348ff01 100644 --- a/templates/forum.html +++ b/templates/forum.html @@ -4,12 +4,19 @@ {{if ne .LastPage .Page}} {{end}} -
-
{{.Title}} - {{if ne .CurrentUser.ID 0}}{{if not .CurrentUser.Perms.CreateTopic}}🔒︎{{else}}New Topic{{end}}{{end}}
+
+ + {{if ne .CurrentUser.ID 0}} + {{if .CurrentUser.Perms.CreateTopic}} +
🖊︎
+ {{else}}
🔒︎
{{end}} +
+ {{end}} +
- {{range .ItemList}}
+ {{range .ItemList}}
{{.PostCount}} replies
{{.LastReplyAt}} diff --git a/templates/forums.html b/templates/forums.html index 41f527eb..3d8d000f 100644 --- a/templates/forums.html +++ b/templates/forums.html @@ -1,20 +1,20 @@ {{template "header.html" . }}
{{range .ItemList}}
{{if .Desc}} - {{.Name}} + {{.Name}}
{{.Desc}}
{{else if .LastTopicTime}} - {{.Name}} + {{.Name}} {{else}} - {{.Name}} + {{.Name}} {{end}} - {{.LastTopic}} + {{.LastTopic}} {{if .LastTopicTime}}
{{.LastTopicTime}}{{end}}
diff --git a/templates/login.html b/templates/login.html index 9fc6f867..4dd03b5a 100644 --- a/templates/login.html +++ b/templates/login.html @@ -1,6 +1,6 @@ {{template "header.html" . }} -
- +
+
diff --git a/templates/panel-adminlogs.html b/templates/panel-adminlogs.html index 37181535..d616fc54 100644 --- a/templates/panel-adminlogs.html +++ b/templates/panel-adminlogs.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
- +
@@ -12,7 +12,7 @@
{{range .Logs}} diff --git a/templates/panel-forum-edit.html b/templates/panel-forum-edit.html index 8a9aeec3..b2e5ad53 100644 --- a/templates/panel-forum-edit.html +++ b/templates/panel-forum-edit.html @@ -7,7 +7,7 @@ var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_acces
@@ -46,7 +46,7 @@ var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_acces
{{range .Groups}} diff --git a/templates/panel-forums.html b/templates/panel-forums.html index 1293d12c..ccfb64f7 100644 --- a/templates/panel-forums.html +++ b/templates/panel-forums.html @@ -7,7 +7,7 @@
{{range .ItemList}} @@ -33,7 +33,7 @@
diff --git a/templates/panel-group-edit-perms.html b/templates/panel-group-edit-perms.html index cb7064a0..6dfb073e 100644 --- a/templates/panel-group-edit-perms.html +++ b/templates/panel-group-edit-perms.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
@@ -12,7 +12,7 @@
@@ -36,7 +36,7 @@
{{if .CurrentUser.Perms.EditGroupGlobalPerms}} diff --git a/templates/panel-group-edit.html b/templates/panel-group-edit.html index 3c55e2b3..4dd50f49 100644 --- a/templates/panel-group-edit.html +++ b/templates/panel-group-edit.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
@@ -12,7 +12,7 @@
diff --git a/templates/panel-groups.html b/templates/panel-groups.html index 32e7669b..ca1de0ac 100644 --- a/templates/panel-groups.html +++ b/templates/panel-groups.html @@ -3,7 +3,7 @@
{{range .ItemList}} @@ -19,7 +19,7 @@ {{end}}
diff --git a/templates/panel-inner-menu.html b/templates/panel-inner-menu.html index 6c27e5bf..f78ae5cd 100644 --- a/templates/panel-inner-menu.html +++ b/templates/panel-inner-menu.html @@ -1,5 +1,5 @@
@@ -10,4 +10,4 @@ {{if .CurrentUser.Perms.ManagePlugins}}{{end}} -
\ No newline at end of file +
diff --git a/templates/panel-modlogs.html b/templates/panel-modlogs.html index 5c5455f7..2d5dbeec 100644 --- a/templates/panel-modlogs.html +++ b/templates/panel-modlogs.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
- +
@@ -12,7 +12,7 @@
{{range .Logs}} diff --git a/templates/panel-plugins.html b/templates/panel-plugins.html index 146d345f..109fd374 100644 --- a/templates/panel-plugins.html +++ b/templates/panel-plugins.html @@ -2,7 +2,7 @@ {{template "panel-menu.html" . }}
{{range .ItemList}} @@ -12,6 +12,9 @@ {{if .Settings}}Settings{{end}} {{if .Active}}Deactivate + {{else if .Installable}} + {{/** TO-DO: Write a custom template interpreter to fix this nonsense **/}} + {{if .Installed}}Activate{{else}}Install{{end}} {{else}}Activate{{end}}
diff --git a/templates/panel-setting.html b/templates/panel-setting.html index 4b8e9c07..61cda60e 100644 --- a/templates/panel-setting.html +++ b/templates/panel-setting.html @@ -2,7 +2,7 @@ {{template "panel-menu.html" . }}
diff --git a/templates/panel-settings.html b/templates/panel-settings.html index db2d8a71..ceb8598f 100644 --- a/templates/panel-settings.html +++ b/templates/panel-settings.html @@ -2,7 +2,7 @@ {{template "panel-menu.html" . }}
{{range $key, $value := .Something}} diff --git a/templates/panel-themes.html b/templates/panel-themes.html index 0091c0c3..c6ee9ccc 100644 --- a/templates/panel-themes.html +++ b/templates/panel-themes.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
@@ -20,7 +20,7 @@
{{range .PrimaryThemes}} @@ -38,7 +38,7 @@ {{end}}
{{range .VariantThemes}} diff --git a/templates/panel-user-edit.html b/templates/panel-user-edit.html index a652c006..7838d2fb 100644 --- a/templates/panel-user-edit.html +++ b/templates/panel-user-edit.html @@ -2,7 +2,7 @@ {{template "panel-menu.html" . }}
diff --git a/templates/panel-users.html b/templates/panel-users.html index b41710db..e30e22fa 100644 --- a/templates/panel-users.html +++ b/templates/panel-users.html @@ -3,7 +3,7 @@
- +
{{range .ItemList}} diff --git a/templates/profile.html b/templates/profile.html index f4e750a6..948d92c6 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -19,8 +19,8 @@
-
- +
{{range .ItemList}}
diff --git a/templates/register.html b/templates/register.html index c03f992f..e0696b39 100644 --- a/templates/register.html +++ b/templates/register.html @@ -1,6 +1,6 @@ {{template "header.html" . }} -
- +
diff --git a/templates/socialgroups_create_group.html b/templates/socialgroups_create_group.html new file mode 100644 index 00000000..3e01f9d8 --- /dev/null +++ b/templates/socialgroups_create_group.html @@ -0,0 +1,30 @@ +{{template "header.html" . }} + +
+ +
+ +
+
+
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+{{template "footer.html" . }} diff --git a/templates/socialgroups_css.html b/templates/socialgroups_css.html new file mode 100644 index 00000000..d80ac1e1 --- /dev/null +++ b/templates/socialgroups_css.html @@ -0,0 +1,48 @@ + diff --git a/templates/socialgroups_group_list.html b/templates/socialgroups_group_list.html new file mode 100644 index 00000000..fc117fec --- /dev/null +++ b/templates/socialgroups_group_list.html @@ -0,0 +1,19 @@ +{{template "header.html" . }} + +
+ {{range .GroupList}}
+ + {{.Name}} +
{{.Desc}} +
+ + {{.MemberCount}} members +
{{.LastUpdateTime}} +
+
+
+ {{else}}
There aren't any visible groups.
{{end}} +
+{{template "footer.html" . }} diff --git a/templates/socialgroups_member_list.html b/templates/socialgroups_member_list.html new file mode 100644 index 00000000..b238fd62 --- /dev/null +++ b/templates/socialgroups_member_list.html @@ -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}}{{end}} +{{if ne .LastPage .Page}} +{{end}} + +
+ +
+
+
+ {{range .ItemList}}
+ + {{.RankString}}
+ {{.JoinedAt}} +
+ + {{.User.Name}} + {{/** Use this for badges instead of rank? Both? Group Titles? **/}} +
{{.PostCount}} posts +
+
+ {{end}} +
+{{template "footer.html" . }} diff --git a/templates/socialgroups_view_group.html b/templates/socialgroups_view_group.html new file mode 100644 index 00000000..acb865d5 --- /dev/null +++ b/templates/socialgroups_view_group.html @@ -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}}{{end}} +{{if ne .LastPage .Page}} +{{end}} + +
+ +
+
+
+ {{range .ItemList}}
+ + {{.PostCount}} replies
+ {{.LastReplyAt}} +
+ + {{.Title}} +
Starter: {{.CreatedByName}} + {{if .Is_Closed}} | 🔒︎{{end}} +
+
+ {{else}}
There aren't any topics in here yet.{{if .CurrentUser.Perms.CreateTopic}} Start one?{{end}}
{{end}} +
+{{template "footer.html" . }} diff --git a/templates/topic.html b/templates/topic.html index d949b624..8e12a9ee 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -7,9 +7,9 @@ >
{{end}} -
+
-
+
{{.Topic.Title}} {{if .Topic.Is_Closed}}🔒︎{{end}} {{if .CurrentUser.Perms.EditTopic}} diff --git a/templates/topic_alt.html b/templates/topic_alt.html index b7ebd891..2390b18a 100644 --- a/templates/topic_alt.html +++ b/templates/topic_alt.html @@ -3,9 +3,9 @@ {{if ne .LastPage .Page}} {{end}} -
+
-
+
{{.Topic.Title}} {{if .Topic.Is_Closed}}🔒︎{{end}} {{if .CurrentUser.Perms.EditTopic}} diff --git a/templates/topics.html b/templates/topics.html index 7ef1185b..66a11cc5 100644 --- a/templates/topics.html +++ b/templates/topics.html @@ -1,15 +1,15 @@ {{template "header.html" . }} -
- +
- {{range .ItemList}}
+ {{range .ItemList}}
{{.PostCount}} replies
{{.LastReplyAt}}
- {{.Title}} {{if .ForumName}}{{.ForumName}}{{end}} + {{.Title}} {{if .ForumName}}{{.ForumName}}{{end}}
Starter: {{.CreatedByName}} {{if .Is_Closed}} | 🔒︎{{end}}
diff --git a/templates/widget_menu.html b/templates/widget_menu.html new file mode 100644 index 00000000..94f035e8 --- /dev/null +++ b/templates/widget_menu.html @@ -0,0 +1,6 @@ +
+
{{.Name}}
+
+
{{range .MenuList}} + +{{end}}
diff --git a/templates/widget_simple.html b/templates/widget_simple.html index 813caf4f..91d58609 100644 --- a/templates/widget_simple.html +++ b/templates/widget_simple.html @@ -1,5 +1,5 @@ -
-
{{.Name}}
+
+
{{.Name}}
{{.Text}}
diff --git a/themes.go b/themes.go index 0bc9fa75..dd5ffc02 100644 --- a/themes.go +++ b/themes.go @@ -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 diff --git a/themes/cosmo-conflux/public/main.css b/themes/cosmo-conflux/public/main.css index aa3a9c85..cb97ec9e 100644 --- a/themes/cosmo-conflux/public/main.css +++ b/themes/cosmo-conflux/public/main.css @@ -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) { diff --git a/themes/cosmo/public/main.css b/themes/cosmo/public/main.css index 1b54aa5d..dcababf3 100644 --- a/themes/cosmo/public/main.css +++ b/themes/cosmo/public/main.css @@ -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; } diff --git a/themes/tempra-conflux/public/main.css b/themes/tempra-conflux/public/main.css index c4718d63..4b171b1f 100644 --- a/themes/tempra-conflux/public/main.css +++ b/themes/tempra-conflux/public/main.css @@ -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'); diff --git a/themes/tempra-cursive/public/main.css b/themes/tempra-cursive/public/main.css index b5cb9665..c02f046b 100644 --- a/themes/tempra-cursive/public/main.css +++ b/themes/tempra-cursive/public/main.css @@ -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; diff --git a/themes/tempra-simple/public/main.css b/themes/tempra-simple/public/main.css index a00d588c..0122077d 100644 --- a/themes/tempra-simple/public/main.css +++ b/themes/tempra-simple/public/main.css @@ -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; diff --git a/topic.go b/topic.go index c73de397..b6ff58cd 100644 --- a/topic.go +++ b/topic.go @@ -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/" +} diff --git a/topic_store.go b/topic_store.go index 8a222ea4..8e766486 100644 --- a/topic_store.go +++ b/topic_store.go @@ -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 { diff --git a/update-deps-linux b/update-deps-linux index e2006620..71d23621 100644 --- a/update-deps-linux +++ b/update-deps-linux @@ -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" diff --git a/update-deps.bat b/update-deps.bat index 22997898..da23d320 100644 --- a/update-deps.bat +++ b/update-deps.bat @@ -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 diff --git a/user.go b/user.go index 3200b26e..256bae8b 100644 --- a/user.go +++ b/user.go @@ -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) diff --git a/user_store.go b/user_store.go index 3c105695..23abf626 100644 --- a/user_store.go +++ b/user_store.go @@ -10,6 +10,7 @@ import "database/sql" import "./query_gen/lib" import "golang.org/x/crypto/bcrypt" +// TO-DO: Add the watchdog goroutine var users UserStore var err_account_exists = errors.New("This username is already in use.") diff --git a/utils.go b/utils.go index fbcc78e1..76762f00 100644 --- a/utils.go +++ b/utils.go @@ -343,6 +343,13 @@ func fill_group_id_gap(biggerID int, smallerID int) { } } +func build_slug(slug string, id int) string { + if slug == "" { + return strconv.Itoa(id) + } + return slug + "." + strconv.Itoa(id) +} + func addModLog(action string, elementID int, elementType string, ipaddress string, actorID int) (err error) { _, err = add_modlog_entry_stmt.Exec(action,elementID,elementType,ipaddress,actorID) if err != nil { diff --git a/websockets.go b/websockets.go index f4194c74..939872a8 100644 --- a/websockets.go +++ b/websockets.go @@ -157,11 +157,7 @@ func(hub *WS_Hub) push_alerts(users []int, event string, elementType string, act return nil } -func route_websockets(w http.ResponseWriter, r *http.Request) { - user, ok := SimpleSessionCheck(w,r) - if !ok { - return - } +func route_websockets(w http.ResponseWriter, r *http.Request, user User) { conn, err := ws_upgrader.Upgrade(w,r,nil) if err != nil { return diff --git a/widgets.go b/widgets.go index 9935b32e..f9331cff 100644 --- a/widgets.go +++ b/widgets.go @@ -25,6 +25,19 @@ type Widget struct Body string } +type WidgetMenu struct +{ + Name string + MenuList []WidgetMenuItem +} + +type WidgetMenuItem struct +{ + Text string + Location string + Compact bool +} + type NameTextPair struct { Name string