diff --git a/data.sql b/data.sql index 1cd09579..75db8755 100644 --- a/data.sql +++ b/data.sql @@ -6,6 +6,7 @@ CREATE TABLE `users`( `password` varchar(100) not null, `salt` varchar(80) DEFAULT '' not null, `group` int not null, + `active` tinyint DEFAULT 0 not null, `is_super_admin` tinyint(1) not null, `createdAt` datetime not null, `lastActiveAt` datetime not null, @@ -99,7 +100,7 @@ INSERT INTO settings(`name`,`content`,`type`) VALUES ('url_tags','1','bool'); INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`,`message`) VALUES ('Admin',1,1,NOW(),NOW(),''); -INSERT INTO users_groups(`name`,`permissions`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{}',1,1,"Admin"); +INSERT INTO users_groups(`name`,`permissions`,`active`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{}',1,1,1,"Admin"); INSERT INTO users_groups(`name`,`permissions`,`is_mod`,`tag`) VALUES ('Moderator','{}',1,"Mod"); INSERT INTO users_groups(`name`,`permissions`) VALUES ('Member','{}'); INSERT INTO users_groups(`name`,`permissions`,`is_banned`) VALUES ('Banned','{}',1); diff --git a/extend.go b/extend.go index 0db39266..0d483fd5 100644 --- a/extend.go +++ b/extend.go @@ -3,6 +3,7 @@ package main 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{}) type Plugin struct { @@ -14,12 +15,22 @@ type Plugin struct Active bool Type string Init func() + Activate func() + Deactivate func() } func add_hook(name string, handler func(interface{})interface{}) { hooks[name] = handler } +func remove_hook(name string) { + delete(hooks, name) +} + func run_hook(name string, data interface{}) interface{} { return hooks[name](data) +} + +func run_hook_v(name string, data ...interface{}) { + vhooks[name](data...) } \ No newline at end of file diff --git a/gosora.exe b/gosora.exe index c6a56616..d200d06e 100644 Binary files a/gosora.exe and b/gosora.exe differ diff --git a/images/edit_setting.PNG b/images/edit_setting.PNG new file mode 100644 index 00000000..b9ffa77d Binary files /dev/null and b/images/edit_setting.PNG differ diff --git a/images/new-topic-list.PNG b/images/new-topic-list.PNG new file mode 100644 index 00000000..2249ae42 Binary files /dev/null and b/images/new-topic-list.PNG differ diff --git a/images/plugins.PNG b/images/plugins.PNG new file mode 100644 index 00000000..1ef02107 Binary files /dev/null and b/images/plugins.PNG differ diff --git a/images/settings.PNG b/images/settings.PNG new file mode 100644 index 00000000..3559947e Binary files /dev/null and b/images/settings.PNG differ diff --git a/images/users.PNG b/images/users.PNG new file mode 100644 index 00000000..b79f7745 Binary files /dev/null and b/images/users.PNG differ diff --git a/main.go b/main.go index 5dc4730b..5434c92d 100644 --- a/main.go +++ b/main.go @@ -135,7 +135,8 @@ func main(){ http.HandleFunc("/panel/settings/edit/submit/", route_panel_setting_edit) http.HandleFunc("/panel/plugins/", route_panel_plugins) http.HandleFunc("/panel/plugins/activate/", route_panel_plugins_activate) - //http.HandleFunc("/panel/plugins/deactivate/", route_panel_plugins_deactivate) + http.HandleFunc("/panel/plugins/deactivate/", route_panel_plugins_deactivate) + http.HandleFunc("/panel/users/", route_panel_users) http.HandleFunc("/", default_route) diff --git a/mod_routes.go b/mod_routes.go index 38f1d344..2c3e5fe3 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -2,6 +2,7 @@ package main import "log" import "fmt" +import "strings" import "strconv" import "net/http" import "html" @@ -723,7 +724,6 @@ func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request){ } uname := r.URL.Path[len("/panel/plugins/activate/"):] - plugin, ok := plugins[uname] if !ok { LocalError("The plugin isn't registered in the system",w,r,user) @@ -743,6 +743,11 @@ func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request){ LocalError("The plugin is already active",w,r,user) return } + _, err = update_plugin_stmt.Exec(1, uname) + if err != nil { + InternalError(err,w,r,user) + return + } } else { _, err := add_plugin_stmt.Exec(uname,1) if err != nil { @@ -756,4 +761,108 @@ func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request){ plugins[uname].Init() http.Redirect(w,r,"/panel/plugins/",http.StatusSeeOther) +} + +func route_panel_plugins_deactivate(w http.ResponseWriter, r *http.Request){ + user := SessionCheck(w,r) + if !user.Is_Admin { + NoPermissions(w,r,user) + return + } + + uname := r.URL.Path[len("/panel/plugins/deactivate/"):] + plugin, ok := plugins[uname] + if !ok { + LocalError("The plugin isn't registered in the system",w,r,user) + return + } + + var active bool + err := db.QueryRow("SELECT active from plugins where uname = ?", uname).Scan(&active) + if err == sql.ErrNoRows { + LocalError("The plugin you're trying to deactivate isn't active",w,r,user) + return + } else if err != nil { + InternalError(err,w,r,user) + return + } + + if !active { + LocalError("The plugin you're trying to deactivate isn't active",w,r,user) + return + } + _, err = update_plugin_stmt.Exec(0, uname) + if err != nil { + InternalError(err,w,r,user) + return + } + + plugin.Active = false + plugins[uname] = plugin + plugins[uname].Deactivate() + + http.Redirect(w,r,"/panel/plugins/",http.StatusSeeOther) +} + +func route_panel_users(w http.ResponseWriter, r *http.Request){ + user := SessionCheck(w,r) + if !user.Is_Admin { + NoPermissions(w,r,user) + return + } + + var userList map[int]interface{} = make(map[int]interface{}) + currentID := 0 + + rows, err := db.Query("SELECT `uid`, `name`, `group`, `active`, `is_super_admin`, `avatar` FROM users") + if err != nil { + InternalError(err,w,r,user) + return + } + defer rows.Close() + + for rows.Next() { + puser := User{0,"",0,false,false,false,false,false,false,"",false,"","","","",""} + err := rows.Scan(&puser.ID, &puser.Name, &puser.Group, &puser.Active, &puser.Is_Super_Admin, &puser.Avatar) + if err != nil { + InternalError(err,w,r,user) + return + } + + puser.Is_Admin = puser.Is_Super_Admin || groups[puser.Group].Is_Admin + puser.Is_Super_Mod = puser.Is_Admin || groups[puser.Group].Is_Mod + puser.Is_Mod = puser.Is_Super_Mod + puser.Is_Banned = groups[puser.Group].Is_Banned + if puser.Is_Banned && puser.Is_Super_Mod { + puser.Is_Banned = false + } + + if puser.Avatar != "" { + if puser.Avatar[0] == '.' { + puser.Avatar = "/uploads/avatar_" + strconv.Itoa(puser.ID) + puser.Avatar + } + } else { + puser.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(puser.ID),1) + } + + if groups[puser.Group].Tag != "" { + puser.Tag = groups[puser.Group].Tag + } else { + puser.Tag = "" + } + + userList[currentID] = puser + currentID++ + } + err = rows.Err() + if err != nil { + InternalError(err,w,r,user) + return + } + + pi := Page{"User Manager","panel-users",user,userList,0} + err = templates.ExecuteTemplate(w,"panel-users.html", pi) + if err != nil { + InternalError(err, w, r, user) + } } \ No newline at end of file diff --git a/mysql.go b/mysql.go index a99f9cb1..5b2b2741 100644 --- a/mysql.go +++ b/mysql.go @@ -37,6 +37,7 @@ var delete_forum_stmt *sql.Stmt var update_forum_stmt *sql.Stmt var update_setting_stmt *sql.Stmt var add_plugin_stmt *sql.Stmt +var update_plugin_stmt *sql.Stmt func init_database(err error) { if(dbpassword != ""){ @@ -230,6 +231,12 @@ func init_database(err error) { log.Fatal(err) } + log.Print("Preparing update_plugin statement.") + update_plugin_stmt, err = db.Prepare("UPDATE plugins SET active = ? WHERE uname = ?") + if err != nil { + log.Fatal(err) + } + log.Print("Loading the usergroups.") rows, err := db.Query("SELECT gid,name,permissions,is_mod,is_admin,is_banned,tag FROM users_groups") if err != nil { diff --git a/pages.go b/pages.go index 5c787dc4..571fa61a 100644 --- a/pages.go +++ b/pages.go @@ -63,10 +63,15 @@ func shortcode_to_unicode(msg string) string { msg = strings.Replace(msg,":sleeping:","😴",-1) msg = strings.Replace(msg,":relieved:","😌",-1) msg = strings.Replace(msg,":nerd:","🤓",-1) - return strings.Replace(msg,":stuck_out_tongue:","😛",-1) + msg = strings.Replace(msg,":stuck_out_tongue:","😛",-1) + return msg } func preparse_message(msg string) string { + if hooks["preparse_preassign"] != nil { + out := run_hook("preparse_preassign", msg) + msg = out.(string) + } return shortcode_to_unicode(msg) } @@ -74,5 +79,10 @@ func parse_message(msg string) string { msg = strings.Replace(msg,":)","😀",-1) msg = strings.Replace(msg,":D","😃",-1) msg = strings.Replace(msg,":P","😛",-1) - return strings.Replace(msg,"\n","
",-1) + msg = strings.Replace(msg,"\n","
",-1) + if hooks["parse_assign"] != nil { + out := run_hook("parse_assign", msg) + msg = out.(string) + } + return msg } \ No newline at end of file diff --git a/plugin_helloworld.go b/plugin_helloworld.go index e18d1ee2..b0e89b15 100644 --- a/plugin_helloworld.go +++ b/plugin_helloworld.go @@ -2,7 +2,7 @@ package main import "html/template" func init() { - plugins["helloworld"] = Plugin{"helloworld","Hello World","Azareal","http://github.com/Azareal","",false,"",init_helloworld} + plugins["helloworld"] = Plugin{"helloworld","Hello World","Azareal","http://github.com/Azareal","",false,"",init_helloworld,nil,deactivate_helloworld} } // init_helloworld is separate from init() as we don't want the plugin to run if the plugin is disabled @@ -10,10 +10,14 @@ func init_helloworld() { add_hook("rrow_assign", helloworld_reply) } +func deactivate_helloworld() { + remove_hook("rrow_assign") +} + func helloworld_reply(data interface{}) interface{} { reply := data.(Reply) reply.Content = "Hello World!" reply.ContentHtml = template.HTML("Hello World!") - reply.Tag = "Automated" + reply.Tag = "Auto" return reply } \ No newline at end of file diff --git a/plugin_markdown.go b/plugin_markdown.go new file mode 100644 index 00000000..02ed8cc8 --- /dev/null +++ b/plugin_markdown.go @@ -0,0 +1,30 @@ +package main + +import "regexp" + +var bold_italic *regexp.Regexp +var bold *regexp.Regexp +var italic *regexp.Regexp + +func init() { + plugins["markdown"] = Plugin{"markdown","Markdown","Azareal","http://github.com/Azareal","",false,"",init_markdown,nil,deactivate_markdown} +} + +func init_markdown() { + add_hook("parse_assign", markdown_parse) + bold_italic = regexp.MustCompile(`\*\*\*(.*)\*\*\*`) + bold = regexp.MustCompile(`\*\*(.*)\*\*`) + italic = regexp.MustCompile(`\*(.*)\*`) +} + +func deactivate_markdown() { + remove_hook("parse_assign") +} + +func markdown_parse(data interface{}) interface{} { + msg := data.(string) + msg = bold_italic.ReplaceAllString(msg,"$1") + msg = bold.ReplaceAllString(msg,"$1") + msg = italic.ReplaceAllString(msg,"$1") + return msg +} \ No newline at end of file diff --git a/plugin_skeleton.go b/plugin_skeleton.go index b2f78ced..692aa2aa 100644 --- a/plugin_skeleton.go +++ b/plugin_skeleton.go @@ -3,15 +3,28 @@ 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 Active field should always be set to false in the init() function of 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. + 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. */ - plugins["skeleton"] = Plugin{"skeleton","Skeleton","Azareal","","",false,"",init_test} + plugins["skeleton"] = Plugin{"skeleton","Skeleton","Azareal","","",false,"",init_skeleton, activate_skeleton, deactivate_skeleton} } -func init_test() {} +func init_skeleton() {} +func activate_skeleton() {} +func deactivate_skeleton() {} \ No newline at end of file diff --git a/routes.go b/routes.go index 2c839734..2d635aee 100644 --- a/routes.go +++ b/routes.go @@ -55,10 +55,13 @@ func route_custom_page(w http.ResponseWriter, r *http.Request){ user := SessionCheck(w,r) name := r.URL.Path[len("/pages/"):] + if custom_pages.Lookup(name) == nil { + NotFound(w,r,user) + } pi := Page{"Page","page",user,tList,0} err := custom_pages.ExecuteTemplate(w,name,pi) if err != nil { - NotFound(w,r,user) + InternalError(err, w, r, user) } } @@ -416,7 +419,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){ replyList = make(map[int]interface{}) currentID = 0 - puser := User{0,"",0,false,false,false,false,false,"",false,"","","",""} + puser := User{0,"",0,false,false,false,false,false,false,"",false,"","","","",""} puser.ID, err = strconv.Atoi(r.URL.Path[len("/user/"):]) if err != nil { LocalError("The provided TopicID is not a valid number.",w,r,user) @@ -446,6 +449,12 @@ func route_profile(w http.ResponseWriter, r *http.Request){ } } + if groups[puser.Group].Tag != "" { + puser.Tag = groups[puser.Group].Tag + } else { + puser.Tag = "" + } + if puser.Avatar != "" { if puser.Avatar[0] == '.' { puser.Avatar = "/uploads/avatar_" + strconv.Itoa(puser.ID) + puser.Avatar @@ -721,11 +730,11 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { LocalError("Bad Form", w, r, user) return } - if r.FormValue("session") != user.Session { SecurityError(w,r,user) return } + item_id, err := strconv.Atoi(r.URL.Path[len("/report/submit/"):]) if err != nil { LocalError("Bad ID", w, r, user) @@ -758,6 +767,25 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { return } content = content + "

Original Post: " + title + "" + } else if item_type == "user-reply" { + err = db.QueryRow("select uid, content from users_replies where rid = ?", item_id).Scan(&tid, &content) + if err == sql.ErrNoRows { + LocalError("We were unable to find the reported post", w, r, user) + return + } else if err != nil { + InternalError(err,w,r,user) + return + } + + err = db.QueryRow("select name from users where uid = ?", tid).Scan(&title) + if err == sql.ErrNoRows { + LocalError("We were unable to find the profile which the reported post is supposed to be on", w, r, user) + return + } else if err != nil { + InternalError(err,w,r,user) + return + } + content = content + "

Original Post: " + title + "" } else if item_type == "topic" { err = db.QueryRow("select title, content from topics where tid = ?", item_id).Scan(&title,&content) if err == sql.ErrNoRows { @@ -769,6 +797,11 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { } content = content + "

Original Post: " + title + "" } else { + if vhooks["report_preassign"] != nil { + run_hook_v("report_preassign", &item_id, &item_type) + return + } + // Don't try to guess the type LocalError("Unknown type", w, r, user) return @@ -780,7 +813,6 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { InternalError(err,w,r,user) return } - for rows.Next() { err = rows.Scan(&count) if err != nil { diff --git a/templates/panel-forums.html b/templates/panel-forums.html index a744e7a2..eb83d538 100644 --- a/templates/panel-forums.html +++ b/templates/panel-forums.html @@ -4,6 +4,8 @@
Forums
Settings
Plugins
+
Users
+
Groups
Coming Soon
Coming Soon
Coming Soon
diff --git a/templates/panel-plugins.html b/templates/panel-plugins.html index 3fb71e7e..b82fdb87 100644 --- a/templates/panel-plugins.html +++ b/templates/panel-plugins.html @@ -4,6 +4,8 @@
Forums
Settings
Plugins
+
Users
+
Groups
Coming Soon
Coming Soon
Coming Soon
diff --git a/templates/panel-setting.html b/templates/panel-setting.html index 8dae1e98..e83b39b5 100644 --- a/templates/panel-setting.html +++ b/templates/panel-setting.html @@ -4,6 +4,8 @@
Forums
Settings
Plugins
+
Users
+
Groups
Coming Soon
Coming Soon
Coming Soon
diff --git a/templates/panel-settings.html b/templates/panel-settings.html index 6f249bab..f57b153c 100644 --- a/templates/panel-settings.html +++ b/templates/panel-settings.html @@ -4,6 +4,8 @@
Forums
Settings
Plugins
+
Users
+
Groups
Coming Soon
Coming Soon
Coming Soon
diff --git a/templates/panel-users.html b/templates/panel-users.html new file mode 100644 index 00000000..7c317ef3 --- /dev/null +++ b/templates/panel-users.html @@ -0,0 +1,29 @@ +{{template "header.html" . }} +
+
Control Panel
+
Forums
+
Settings
+
Plugins
+
Users
+
Groups
+
Coming Soon
+
Coming Soon
+
Coming Soon
+
Reports
+
+
+
Users
+
+
+ {{range .ItemList}} +
+ {{.Name}} + {{if .Tag}}{{.Tag}}{{end}} + + {{if .Is_Banned}}Unban{{else if not .Is_Super_Mod}}Ban{{end}} + {{if not .Active}}Activate{{end}} + +
+ {{end}} +
+{{template "footer.html" . }} \ No newline at end of file diff --git a/templates/profile.html b/templates/profile.html index c80c6d42..b13387bd 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -1,13 +1,15 @@ {{template "header.html" . }}
-
{{.Something.Name}}
+
+ {{.Something.Name}}{{if .Something.Tag}}{{.Something.Tag}}{{end}} +
Add Friend {{if (.CurrentUser.Is_Super_Mod) and not (.Something.Is_Super_Mod) }} {{if .Something.Is_Banned }}Unban{{else}}Ban{{end}} {{end}} - Report + Report
@@ -21,7 +23,7 @@ {{$element.CreatedByName}} {{if $.CurrentUser.Is_Mod}} {{end}} - + {{ if $element.Tag }}{{$element.Tag}}{{end}}
{{end}} diff --git a/templates/topics.html b/templates/topics.html index dd845af6..4fd2adef 100644 --- a/templates/topics.html +++ b/templates/topics.html @@ -4,7 +4,9 @@
{{range .ItemList}}
- {{.Title}} {{if .Is_Closed}}closed{{else}}open{{end}} + {{.Title}} {{if .Is_Closed}}closed + {{else}}open{{end}} + Status
{{end}}
{{if .Something}} diff --git a/user.go b/user.go index 61846050..ec3d6566 100644 --- a/user.go +++ b/user.go @@ -12,6 +12,7 @@ type User struct ID int Name string Group int + Active bool Is_Mod bool Is_Super_Mod bool Is_Admin bool @@ -23,6 +24,7 @@ type User struct Message string URLPrefix string URLName string + Tag string } func SetPassword(uid int, password string) (error) { @@ -45,7 +47,7 @@ func SetPassword(uid int, password string) (error) { } func SessionCheck(w http.ResponseWriter, r *http.Request) (User) { - user := User{0,"",0,false,false,false,false,false,"",false,"","","",""} + user := User{0,"",0,false,false,false,false,false,false,"",false,"","","","",""} var err error var cookie *http.Cookie