diff --git a/common/page_store.go b/common/page_store.go index 74017d76..4c0288bd 100644 --- a/common/page_store.go +++ b/common/page_store.go @@ -38,12 +38,12 @@ func BlankCustomPage() *CustomPage { return new(CustomPage) } -func (page *CustomPage) AddAllowedGroup(gid int) { - page.AllowedGroups = append(page.AllowedGroups, gid) +func (p *CustomPage) AddAllowedGroup(gid int) { + p.AllowedGroups = append(p.AllowedGroups, gid) } -func (page *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) { - for _, group := range page.AllowedGroups { +func (p *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) { + for _, group := range p.AllowedGroups { rawAllowedGroups += strconv.Itoa(group) + "," } if len(rawAllowedGroups) > 0 { @@ -52,18 +52,17 @@ func (page *CustomPage) getRawAllowedGroups() (rawAllowedGroups string) { return rawAllowedGroups } -func (page *CustomPage) Commit() error { - _, err := customPageStmts.update.Exec(page.Name, page.Title, page.Body, page.getRawAllowedGroups(), page.MenuID, page.ID) - Pages.Reload(page.ID) +func (p *CustomPage) Commit() error { + _, err := customPageStmts.update.Exec(p.Name, p.Title, p.Body, p.getRawAllowedGroups(), p.MenuID, p.ID) + Pages.Reload(p.ID) return err } -func (page *CustomPage) Create() (int, error) { - res, err := customPageStmts.create.Exec(page.Name, page.Title, page.Body, page.getRawAllowedGroups(), page.MenuID) +func (p *CustomPage) Create() (int, error) { + res, err := customPageStmts.create.Exec(p.Name, p.Title, p.Body, p.getRawAllowedGroups(), p.MenuID) if err != nil { return 0, err } - pid64, err := res.LastInsertId() return int(pid64), err } diff --git a/common/word_filters.go b/common/word_filters.go index 55b1deb7..798b74c3 100644 --- a/common/word_filters.go +++ b/common/word_filters.go @@ -4,7 +4,7 @@ import ( "database/sql" "sync/atomic" - "github.com/Azareal/Gosora/query_gen" + qgen "github.com/Azareal/Gosora/query_gen" ) // TODO: Move some features into methods on this? @@ -19,7 +19,8 @@ var WordFilters WordFilterStore type WordFilterStore interface { ReloadAll() error GetAll() (filters map[int]*WordFilter, err error) - Create(find string, replacement string) error + Get(id int) (*WordFilter, error) + Create(find string, replacement string) (int, error) Delete(id int) error Update(id int, find string, replacement string) error Length() int @@ -31,6 +32,7 @@ type DefaultWordFilterStore struct { box atomic.Value // An atomic value holding a WordFilterMap getAll *sql.Stmt + get *sql.Stmt create *sql.Stmt delete *sql.Stmt update *sql.Stmt @@ -41,6 +43,7 @@ func NewDefaultWordFilterStore(acc *qgen.Accumulator) (*DefaultWordFilterStore, wf := "word_filters" store := &DefaultWordFilterStore{ getAll: acc.Select(wf).Columns("wfid,find,replacement").Prepare(), + get: acc.Select(wf).Columns("wfid,find,replacement").Where("wfid = ?").Prepare(), create: acc.Insert(wf).Columns("find,replacement").Fields("?,?").Prepare(), delete: acc.Delete(wf).Where("wfid = ?").Prepare(), update: acc.Update(wf).Set("find = ?, replacement = ?").Where("wfid = ?").Prepare(), @@ -93,13 +96,23 @@ func (s *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err erro return s.box.Load().(map[int]*WordFilter), nil } +func (s *DefaultWordFilterStore) Get(id int) (*WordFilter, error) { + wf := &WordFilter{ID: id} + err := s.get.QueryRow(id).Scan(&wf.Find, &wf.Replacement) + return wf, err +} + // Create adds a new word filter to the database and refreshes the memory cache -func (s *DefaultWordFilterStore) Create(find string, replace string) error { - _, err := s.create.Exec(find, replace) +func (s *DefaultWordFilterStore) Create(find string, replace string) (int, error) { + res, err := s.create.Exec(find, replace) if err != nil { - return err + return 0, err } - return s.ReloadAll() + id64, err := res.LastInsertId() + if err != nil { + return 0, err + } + return int(id64), s.ReloadAll() } // Delete removes a word filter from the database and refreshes the memory cache diff --git a/langs/english.json b/langs/english.json index 6ae0b5a4..cd4b2ed6 100644 --- a/langs/english.json +++ b/langs/english.json @@ -1021,6 +1021,8 @@ "topic_unknown":"Unknown", "group_unknown":"Unknown", "forum_unknown":"Unknown", + "page_unknown":"Unknown", + "setting_unknown":"unknown", "panel_logs_administration_head":"Admin Action Logs", "panel_logs_administration_action_user_edit":"User %s was modified by %s", @@ -1032,6 +1034,14 @@ "panel_logs_administration_action_forum_create":"Forum %s was created by %s", "panel_logs_administration_action_forum_delete":"Forum %s was deleted by %s", "panel_logs_administration_action_forum_edit":"Forum %s was modified by %s", + "panel_logs_administration_action_page_create":"Page %s was created by %s", + "panel_logs_administration_action_page_delete":"Page %s was deleted by %s", + "panel_logs_administration_action_page_edit":"Page %s was modified by %s", + "panel_logs_administration_action_setting_edit":"Setting %s was modified by %s", + "panel_logs_administration_action_word_filter_create":"Word Filter %d was created by %s", + "panel_logs_administration_action_word_filter_delete":"Word Filter %d was deleted by %s", + "panel_logs_administration_action_word_filter_edit":"Word Filter %d was modified by %s", + "panel_logs_administration_action_backup_download":"A backup was downloaded by %s", "panel_logs_administration_action_unknown":"Unknown action '%s' on elementType '%s' by %s", "panel_logs_administration_no_logs":"There aren't any events logged.", diff --git a/routes/panel/backups.go b/routes/panel/backups.go index 6affcdbc..f3946ce9 100644 --- a/routes/panel/backups.go +++ b/routes/panel/backups.go @@ -28,19 +28,24 @@ func Backups(w http.ResponseWriter, r *http.Request, user c.User, backupURL stri if err != nil { return c.NotFound(w, r, basePage.Header) } - w.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10)) + h := w.Header() + h.Set("Content-Length", strconv.FormatInt(info.Size(), 10)) if ext == ".sql" { - // TODO: Change the served filename to gosora_backup_%timestamp%.sql, the time the file was generated, not when it was modified aka what the name of it should be - w.Header().Set("Content-Disposition", "attachment; filename=gosora_backup.sql") - w.Header().Set("Content-Type", "application/sql") + // TODO: Change the served filename to gosora_backup_%timestamp%.sql, the time the file was generated, not when it was modified aka what the name of it should be + h.Set("Content-Disposition", "attachment; filename=gosora_backup.sql") + h.Set("Content-Type", "application/sql") } else { - // TODO: Change the served filename to gosora_backup_%timestamp%.zip, the time the file was generated, not when it was modified aka what the name of it should be - w.Header().Set("Content-Disposition", "attachment; filename=gosora_backup.zip") - w.Header().Set("Content-Type", "application/zip") + // TODO: Change the served filename to gosora_backup_%timestamp%.zip, the time the file was generated, not when it was modified aka what the name of it should be + h.Set("Content-Disposition", "attachment; filename=gosora_backup.zip") + h.Set("Content-Type", "application/zip") } // TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side http.ServeFile(w, r, "./backups/"+backupURL) + err = c.AdminLogs.Create("download", 0, "backup", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } return nil } diff --git a/routes/panel/logs.go b/routes/panel/logs.go index bf1c7333..3479a45e 100644 --- a/routes/panel/logs.go +++ b/routes/panel/logs.go @@ -124,6 +124,26 @@ func adminlogsElementType(action string, elementType string, elementID int, acto } else { out = p.GetTmplPhrasef("panel_logs_administration_action_forum_"+action, "/panel/forums/edit/"+strconv.Itoa(f.ID), f.Name, actor.Link, actor.Name) } + case "page": + pp, err := c.Pages.Get(elementID) + if err != nil { + pp = &c.CustomPage{Name: p.GetTmplPhrase("page_unknown")} + } + out = p.GetTmplPhrasef("panel_logs_administration_action_page_"+action, "/panel/pages/edit/"+strconv.Itoa(pp.ID), pp.Name, actor.Link, actor.Name) + case "setting": + s, err := c.SettingBox.Load().(c.SettingMap).BypassGet(action) + if err != nil { + s = &c.Setting{Name: p.GetTmplPhrase("setting_unknown")} + } + out = p.GetTmplPhrasef("panel_logs_administration_action_setting_edit", "/panel/settings/edit/"+s.Name, s.Name, actor.Link, actor.Name) + case "word_filter": + wf, err := c.WordFilters.Get(elementID) + if err != nil { + wf = &c.WordFilter{} + } + out = p.GetTmplPhrasef("panel_logs_administration_action_word_filter_"+action, "/panel/settings/word-filters/", wf.ID, actor.Link, actor.Name) + case "backup": + out = p.GetTmplPhrasef("panel_logs_administration_action_backup_"+action, actor.Link, actor.Name) } if out == "" { out = p.GetTmplPhrasef("panel_logs_administration_action_unknown", action, elementType, actor.Link, actor.Name) diff --git a/routes/panel/pages.go b/routes/panel/pages.go index a5577fb6..454999b4 100644 --- a/routes/panel/pages.go +++ b/routes/panel/pages.go @@ -42,24 +42,28 @@ func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.Ro return ferr } - pname := r.PostFormValue("name") - if pname == "" { + name := c.SanitiseSingleLine(r.PostFormValue("name")) + if name == "" { return c.LocalError("No name was provided for this page", w, r, user) } - ptitle := r.PostFormValue("title") - if ptitle == "" { + title := c.SanitiseSingleLine(r.PostFormValue("title")) + if title == "" { return c.LocalError("No title was provided for this page", w, r, user) } - pbody := r.PostFormValue("body") - if pbody == "" { + body := r.PostFormValue("body") + if body == "" { return c.LocalError("No body was provided for this page", w, r, user) } page := c.BlankCustomPage() - page.Name = pname - page.Title = ptitle - page.Body = pbody - _, err := page.Create() + page.Name = name + page.Title = title + page.Body = body + pid, err := page.Create() + if err != nil { + return c.InternalError(err, w, r) + } + err = c.AdminLogs.Create("create", pid, "page", user.LastIP, user.ID) if err != nil { return c.InternalError(err, w, r) } @@ -102,16 +106,16 @@ func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid s if err != nil { return c.LocalError("Page ID needs to be an integer", w, r, user) } - pname := r.PostFormValue("name") - if pname == "" { + name := c.SanitiseSingleLine(r.PostFormValue("name")) + if name == "" { return c.LocalError("No name was provided for this page", w, r, user) } - ptitle := r.PostFormValue("title") - if ptitle == "" { + title := c.SanitiseSingleLine(r.PostFormValue("title")) + if title == "" { return c.LocalError("No title was provided for this page", w, r, user) } - pbody := r.PostFormValue("body") - if pbody == "" { + body := r.PostFormValue("body") + if body == "" { return c.LocalError("No body was provided for this page", w, r, user) } @@ -119,13 +123,17 @@ func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid s if err != nil { return c.NotFound(w, r, nil) } - page.Name = pname - page.Title = ptitle - page.Body = pbody + page.Name = name + page.Title = title + page.Body = body err = page.Commit() if err != nil { return c.InternalError(err, w, r) } + err = c.AdminLogs.Create("edit", pid, "page", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/pages/?updated=1", http.StatusSeeOther) return nil @@ -145,6 +153,10 @@ func PagesDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, spid if err != nil { return c.InternalError(err, w, r) } + err = c.AdminLogs.Create("delete", pid, "page", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/pages/?deleted=1", http.StatusSeeOther) return nil diff --git a/routes/panel/settings.go b/routes/panel/settings.go index 280f87c6..ac1fbd3f 100644 --- a/routes/panel/settings.go +++ b/routes/panel/settings.go @@ -77,7 +77,6 @@ func SettingEdit(w http.ResponseWriter, r *http.Request, user c.User, sname stri if err != nil { return c.LocalError("The value of this setting couldn't be converted to an integer", w, r, user) } - for index, label := range strings.Split(llist, ",") { itemList = append(itemList, c.OptionLabel{ Label: label, @@ -94,7 +93,7 @@ func SettingEdit(w http.ResponseWriter, r *http.Request, user c.User, sname stri return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_setting", &pi}) } -func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, sname string) c.RouteError { +func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, name string) c.RouteError { headerLite, ferr := c.SimplePanelUserCheck(w, r, &user) if ferr != nil { return ferr @@ -103,11 +102,17 @@ func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, snam return c.NoPermissions(w, r, user) } - scontent := c.SanitiseBody(r.PostFormValue("value")) - rerr := headerLite.Settings.Update(sname, scontent) + name = c.SanitiseSingleLine(name) + content := c.SanitiseBody(r.PostFormValue("value")) + rerr := headerLite.Settings.Update(name, content) if rerr != nil { return rerr } + // TODO: Avoid this hack + err := c.AdminLogs.Create(name, 0, "setting", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/settings/", http.StatusSeeOther) return nil diff --git a/routes/panel/word_filters.go b/routes/panel/word_filters.go index 6be12bc7..c022ee06 100644 --- a/routes/panel/word_filters.go +++ b/routes/panel/word_filters.go @@ -47,10 +47,14 @@ func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement replace := strings.TrimSpace(r.PostFormValue("replace")) - err := c.WordFilters.Create(find, replace) + wfid, err := c.WordFilters.Create(find, replace) if err != nil { return c.InternalErrorJSQ(err, w, r, js) } + err = c.AdminLogs.Create("create", wfid, "word_filter", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } return successRedirect("/panel/settings/word-filters/", w, r, js) } @@ -70,7 +74,7 @@ func WordFiltersEdit(w http.ResponseWriter, r *http.Request, user c.User, wfid s return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_word_filters_edit", &pi}) } -func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, wfid string) c.RouteError { +func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, swfid string) c.RouteError { _, ferr := c.SimplePanelUserCheck(w, r, &user) if ferr != nil { return ferr @@ -80,11 +84,10 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, return c.NoPermissionsJSQ(w, r, user, js) } - id, err := strconv.Atoi(wfid) + wfid, err := strconv.Atoi(swfid) if err != nil { return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js) } - find := strings.TrimSpace(r.PostFormValue("find")) if find == "" { return c.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, js) @@ -92,35 +95,41 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User, // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement replace := strings.TrimSpace(r.PostFormValue("replace")) - err = c.WordFilters.Update(id, find, replace) + err = c.WordFilters.Update(wfid, find, replace) if err != nil { return c.InternalErrorJSQ(err, w, r, js) } + err = c.AdminLogs.Create("edit", wfid, "word_filter", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) return nil } -func WordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, wfid string) c.RouteError { +func WordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, swfid string) c.RouteError { _, ferr := c.SimplePanelUserCheck(w, r, &user) if ferr != nil { return ferr } - js := (r.PostFormValue("js") == "1") if !user.Perms.EditSettings { return c.NoPermissionsJSQ(w, r, user, js) } - id, err := strconv.Atoi(wfid) + wfid, err := strconv.Atoi(swfid) if err != nil { return c.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, js) } - - err = c.WordFilters.Delete(id) + err = c.WordFilters.Delete(wfid) if err == sql.ErrNoRows { return c.LocalErrorJSQ("This word filter doesn't exist", w, r, user, js) } + err = c.AdminLogs.Create("delete", wfid, "word_filter", user.LastIP, user.ID) + if err != nil { + return c.InternalError(err, w, r) + } http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) return nil