Finish up the adminlogs.

Throw an error instead of updating a word filter that doesn't exist.
Track a bit of extra data for word filter updates.

Shorten the prefixes of the adminlog phrases from panel_logs_administration_ to panel_logs_admin_
Add a bunch of new adminlog phrases.
Tweak the word filter phrases.

Add the extra column to the moderation_logs and administration_logs tables.

You need to run the updater / patcher for this commit.
This commit is contained in:
Azareal 2019-11-10 12:37:53 +10:00
parent d03c0faf42
commit 1044518e74
23 changed files with 271 additions and 172 deletions

View File

@ -167,6 +167,7 @@ func createTables(adapter qgen.Adapter) (err error) {
) )
// TODO: Should we add a users prefix to this table to fit the "unofficial convention"? // TODO: Should we add a users prefix to this table to fit the "unofficial convention"?
// TODO: Add an autoincrement key?
createTable("emails", "", "", createTable("emails", "", "",
[]tC{ []tC{
tC{"email", "varchar", 200, false, false, ""}, tC{"email", "varchar", 200, false, false, ""},
@ -620,6 +621,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"ipaddress", "varchar", 200, false, false, ""}, tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"doneAt", "datetime", 0, false, false, ""}, tC{"doneAt", "datetime", 0, false, false, ""},
tC{"extra", "text", 0, false, false, ""},
}, nil, }, nil,
) )
@ -631,6 +633,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"ipaddress", "varchar", 200, false, false, ""}, tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"doneAt", "datetime", 0, false, false, ""}, tC{"doneAt", "datetime", 0, false, false, ""},
tC{"extra", "text", 0, false, false, ""},
}, nil, }, nil,
) )

View File

@ -4,7 +4,7 @@ import (
"database/sql" "database/sql"
"time" "time"
"github.com/Azareal/Gosora/query_gen" qgen "github.com/Azareal/Gosora/query_gen"
) )
var ModLogs LogStore var ModLogs LogStore
@ -14,15 +14,17 @@ type LogItem struct {
Action string Action string
ElementID int ElementID int
ElementType string ElementType string
IP string IP string
ActorID int ActorID int
DoneAt string DoneAt string
Extra string
} }
type LogStore interface { type LogStore interface {
Create(action string, elementID int, elementType string, ip string, actorID int) (err error) Create(action string, elementID int, elementType string, ip string, actorID int) (err error)
CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error)
Count() int Count() int
GetOffset(offset int, perPage int) (logs []LogItem, err error) GetOffset(offset, perPage int) (logs []LogItem, err error)
} }
type SQLModLogStore struct { type SQLModLogStore struct {
@ -34,15 +36,19 @@ type SQLModLogStore struct {
func NewModLogStore(acc *qgen.Accumulator) (*SQLModLogStore, error) { func NewModLogStore(acc *qgen.Accumulator) (*SQLModLogStore, error) {
ml := "moderation_logs" ml := "moderation_logs"
return &SQLModLogStore{ return &SQLModLogStore{
create: acc.Insert(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(), create: acc.Insert(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
count: acc.Count(ml).Prepare(), count: acc.Count(ml).Prepare(),
getOffset: acc.Select(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Orderby("doneAt DESC").Limit("?,?").Prepare(), getOffset: acc.Select(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Orderby("doneAt DESC").Limit("?,?").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
// TODO: Make a store for this? // TODO: Make a store for this?
func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) { func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID) return s.CreateExtra(action, elementID, elementType, ip, actorID, "")
}
func (s *SQLModLogStore) CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID, extra)
return err return err
} }
@ -58,7 +64,7 @@ func buildLogList(rows *sql.Rows) (logs []LogItem, err error) {
for rows.Next() { for rows.Next() {
var l LogItem var l LogItem
var doneAt time.Time var doneAt time.Time
err := rows.Scan(&l.Action, &l.ElementID, &l.ElementType, &l.IP, &l.ActorID, &doneAt) err := rows.Scan(&l.Action, &l.ElementID, &l.ElementType, &l.IP, &l.ActorID, &doneAt, &l.Extra)
if err != nil { if err != nil {
return logs, err return logs, err
} }
@ -68,7 +74,7 @@ func buildLogList(rows *sql.Rows) (logs []LogItem, err error) {
return logs, rows.Err() return logs, rows.Err()
} }
func (s *SQLModLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) { func (s *SQLModLogStore) GetOffset(offset, perPage int) (logs []LogItem, err error) {
rows, err := s.getOffset.Query(offset, perPage) rows, err := s.getOffset.Query(offset, perPage)
if err != nil { if err != nil {
return logs, err return logs, err
@ -86,15 +92,19 @@ type SQLAdminLogStore struct {
func NewAdminLogStore(acc *qgen.Accumulator) (*SQLAdminLogStore, error) { func NewAdminLogStore(acc *qgen.Accumulator) (*SQLAdminLogStore, error) {
al := "administration_logs" al := "administration_logs"
return &SQLAdminLogStore{ return &SQLAdminLogStore{
create: acc.Insert(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(), create: acc.Insert(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
count: acc.Count(al).Prepare(), count: acc.Count(al).Prepare(),
getOffset: acc.Select(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Orderby("doneAt DESC").Limit("?,?").Prepare(), getOffset: acc.Select(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Orderby("doneAt DESC").Limit("?,?").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
// TODO: Make a store for this? // TODO: Make a store for this?
func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) { func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID) return s.CreateExtra(action, elementID, elementType, ip, actorID, "")
}
func (s *SQLAdminLogStore) CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID, extra)
return err return err
} }
@ -106,7 +116,7 @@ func (s *SQLAdminLogStore) Count() (count int) {
return count return count
} }
func (s *SQLAdminLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) { func (s *SQLAdminLogStore) GetOffset(offset, perPage int) (logs []LogItem, err error) {
rows, err := s.getOffset.Query(offset, perPage) rows, err := s.getOffset.Query(offset, perPage)
if err != nil { if err != nil {
return logs, err return logs, err

View File

@ -321,11 +321,11 @@ type Plugin struct {
Installable bool Installable bool
Installed bool Installed bool
Init func(plugin *Plugin) error Init func(pl *Plugin) error
Activate func(plugin *Plugin) error Activate func(pl *Plugin) error
Deactivate func(plugin *Plugin) // TODO: We might want to let this return an error? Deactivate func(pl *Plugin) // TODO: We might want to let this return an error?
Install func(plugin *Plugin) error Install func(pl *Plugin) error
Uninstall func(plugin *Plugin) error // TODO: I'm not sure uninstall is implemented Uninstall func(pl *Plugin) error // TODO: I'm not sure uninstall is implemented
Hooks map[string]int Hooks map[string]int
Data interface{} // Usually used for hosting the VMs / reusable elements of non-native plugins Data interface{} // Usually used for hosting the VMs / reusable elements of non-native plugins

View File

@ -470,7 +470,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
return "" return ""
} }
for _, filter := range wordFilters { for _, filter := range wordFilters {
msg = strings.Replace(msg, filter.Find, filter.Replacement, -1) msg = strings.Replace(msg, filter.Find, filter.Replace, -1)
} }
// Search for URLs, mentions and hashlinks in the messages... // Search for URLs, mentions and hashlinks in the messages...

View File

@ -50,40 +50,40 @@ type Widget struct {
Literal bool Literal bool
TickMask atomic.Value TickMask atomic.Value
InitFunc func(widget *Widget, schedule *WidgetScheduler) error InitFunc func(w *Widget, schedule *WidgetScheduler) error
ShutdownFunc func(widget *Widget) error ShutdownFunc func(w *Widget) error
BuildFunc func(widget *Widget, hvars interface{}) (string, error) BuildFunc func(w *Widget, hvars interface{}) (string, error)
TickFunc func(widget *Widget) error TickFunc func(w *Widget) error
} }
func (widget *Widget) Delete() error { func (w *Widget) Delete() error {
_, err := widgetStmts.delete.Exec(widget.ID) _, err := widgetStmts.delete.Exec(w.ID)
if err != nil { if err != nil {
return err return err
} }
// Reload the dock // Reload the dock
// TODO: Better synchronisation // TODO: Better synchronisation
Widgets.delete(widget.ID) Widgets.delete(w.ID)
widgets, err := getDockWidgets(widget.Side) widgets, err := getDockWidgets(w.Side)
if err != nil { if err != nil {
return err return err
} }
setDock(widget.Side, widgets) setDock(w.Side, widgets)
return nil return nil
} }
func (widget *Widget) Copy() (owidget *Widget) { func (w *Widget) Copy() (owidget *Widget) {
owidget = &Widget{} owidget = &Widget{}
*owidget = *widget *owidget = *w
return owidget return owidget
} }
// TODO: Test this // TODO: Test this
// TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should // TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should
// TODO: Add a selector which also matches topics inside a specific forum? // TODO: Add a selector which also matches topics inside a specific forum?
func (widget *Widget) Allowed(zone string, zoneid int) bool { func (w *Widget) Allowed(zone string, zoneid int) bool {
for _, loc := range strings.Split(widget.Location, "|") { for _, loc := range strings.Split(w.Location, "|") {
if len(loc) == 0 { if len(loc) == 0 {
continue continue
} }
@ -107,16 +107,15 @@ func (widget *Widget) Allowed(zone string, zoneid int) bool {
} }
// TODO: Refactor // TODO: Refactor
func (widget *Widget) Build(hvars interface{}) (string, error) { func (w *Widget) Build(hvars interface{}) (string, error) {
if widget.Literal { if w.Literal {
return widget.Body, nil return w.Body, nil
} }
if widget.BuildFunc != nil { if w.BuildFunc != nil {
return widget.BuildFunc(widget, hvars) return w.BuildFunc(w, hvars)
} }
header := hvars.(*Header) header := hvars.(*Header)
err := header.Theme.RunTmpl(widget.Body, hvars, header.Writer) err := header.Theme.RunTmpl(w.Body, hvars, header.Writer)
return "", err return "", err
} }
@ -125,40 +124,42 @@ type WidgetEdit struct {
Data map[string]string Data map[string]string
} }
func (widget *WidgetEdit) Create() error { func (w *WidgetEdit) Create() (int, error) {
data, err := json.Marshal(widget.Data) data, err := json.Marshal(w.Data)
if err != nil {
return 0, err
}
res, err := widgetStmts.create.Exec(w.Position, w.Side, w.Type, w.Enabled, w.Location, data)
if err != nil {
return 0, err
}
// Reload the dock
widgets, err := getDockWidgets(w.Side)
if err != nil {
return 0, err
}
setDock(w.Side, widgets)
wid64, err := res.LastInsertId()
return int(wid64), err
}
func (w *WidgetEdit) Commit() error {
data, err := json.Marshal(w.Data)
if err != nil { if err != nil {
return err return err
} }
_, err = widgetStmts.create.Exec(widget.Position, widget.Side, widget.Type, widget.Enabled, widget.Location, data) _, err = widgetStmts.update.Exec(w.Position, w.Side, w.Type, w.Enabled, w.Location, data, w.ID)
if err != nil { if err != nil {
return err return err
} }
// Reload the dock // Reload the dock
widgets, err := getDockWidgets(widget.Side) widgets, err := getDockWidgets(w.Side)
if err != nil { if err != nil {
return err return err
} }
setDock(widget.Side, widgets) setDock(w.Side, widgets)
return nil
}
func (widget *WidgetEdit) Commit() error {
data, err := json.Marshal(widget.Data)
if err != nil {
return err
}
_, err = widgetStmts.update.Exec(widget.Position, widget.Side, widget.Type, widget.Enabled, widget.Location, data, widget.ID)
if err != nil {
return err
}
// Reload the dock
widgets, err := getDockWidgets(widget.Side)
if err != nil {
return err
}
setDock(widget.Side, widgets)
return nil return nil
} }

View File

@ -9,9 +9,15 @@ import (
// TODO: Move some features into methods on this? // TODO: Move some features into methods on this?
type WordFilter struct { type WordFilter struct {
ID int ID int
Find string Find string
Replacement string Replace string
}
type WordFilterDiff struct {
BeforeFind string
BeforeReplace string
AfterFind string
AfterReplace string
} }
var WordFilters WordFilterStore var WordFilters WordFilterStore
@ -82,7 +88,7 @@ func (s *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err erro
for rows.Next() { for rows.Next() {
f := &WordFilter{ID: 0} f := &WordFilter{ID: 0}
err := rows.Scan(&f.ID, &f.Find, &f.Replacement) err := rows.Scan(&f.ID, &f.Find, &f.Replace)
if err != nil { if err != nil {
return filters, err return filters, err
} }
@ -98,7 +104,7 @@ func (s *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err erro
func (s *DefaultWordFilterStore) Get(id int) (*WordFilter, error) { func (s *DefaultWordFilterStore) Get(id int) (*WordFilter, error) {
wf := &WordFilter{ID: id} wf := &WordFilter{ID: id}
err := s.get.QueryRow(id).Scan(&wf.Find, &wf.Replacement) err := s.get.QueryRow(id).Scan(&wf.Find, &wf.Replace)
return wf, err return wf, err
} }

View File

@ -16,14 +16,14 @@ func init() {
}) })
} }
func initAdventure(plugin *c.Plugin) error { func initAdventure(pl *c.Plugin) error {
return nil return nil
} }
// TODO: Change the signature to return an error? // TODO: Change the signature to return an error?
func deactivateAdventure(plugin *c.Plugin) { func deactivateAdventure(pl *c.Plugin) {
} }
func installAdventure(plugin *c.Plugin) error { func installAdventure(pl *c.Plugin) error {
return nil return nil
} }

View File

@ -1024,26 +1024,36 @@
"page_unknown":"Unknown", "page_unknown":"Unknown",
"setting_unknown":"unknown", "setting_unknown":"unknown",
"panel_logs_administration_head":"Admin Action Logs", "panel_logs_admin_head":"Admin Action Logs",
"panel_logs_administration_action_user_edit":"User <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>", "panel_logs_admin_action_user_edit":"User <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_group_create":"Group <a href='%s'>%s</a> was created by <a href='%s'>%s</a>", "panel_logs_admin_action_group_create":"Group <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_group_edit":"Group <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>", "panel_logs_admin_action_group_edit":"Group <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_group_promotion_create":"A group promotion was created by <a href='%s'>%s</a>", "panel_logs_admin_action_group_promotion_create":"A group promotion was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_group_promotion_delete":"A group promotion was deleted by <a href='%s'>%s</a>", "panel_logs_admin_action_group_promotion_delete":"A group promotion was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_reorder":"Forums were reordered by <a href='%s'>%s</a>", "panel_logs_admin_action_forum_reorder":"Forums were reordered by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_create":"Forum <a href='%s'>%s</a> was created by <a href='%s'>%s</a>", "panel_logs_admin_action_forum_create":"Forum <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_delete":"Forum <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>", "panel_logs_admin_action_forum_delete":"Forum <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_edit":"Forum <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>", "panel_logs_admin_action_forum_edit":"Forum <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_create":"Page <a href='%s'>%s</a> was created by <a href='%s'>%s</a>", "panel_logs_admin_action_page_create":"Page <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_delete":"Page <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>", "panel_logs_admin_action_page_delete":"Page <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_edit":"Page <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>", "panel_logs_admin_action_page_edit":"Page <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_setting_edit":"Setting <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>", "panel_logs_admin_action_setting_edit":"Setting <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_create":"Word Filter <a href='%s'>%d</a> was created by <a href='%s'>%s</a>", "panel_logs_admin_action_word_filter_create":"A word filter was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_delete":"Word Filter <a href='%s'>%d</a> was deleted by <a href='%s'>%s</a>", "panel_logs_admin_action_word_filter_delete":"A word filter was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_edit":"Word Filter <a href='%s'>%d</a> was modified by <a href='%s'>%s</a>", "panel_logs_admin_action_word_filter_edit":"A word filter was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_backup_download":"A backup was downloaded by <a href='%s'>%s</a>", "panel_logs_admin_action_menu_suborder":"Menu #%d was reordered by <a href='%s'>%s</a>",
"panel_logs_administration_action_unknown":"Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>", "panel_logs_admin_action_menu_item_edit":"Menu item <a href='%s'>#%d</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_no_logs":"There aren't any events logged.", "panel_logs_admin_action_menu_item_create":"Menu item <a href='%s'>#%d</a> was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_menu_item_delete":"Menu item <a href='%s'>#%d</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_admin_action_widget_edit":"Widget <a href='%s'>#%d</a> was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_widget_create":"Widget <a href='%s'>#%d</a> was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_widget_delete":"Widget <a href='%s'>#%d</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_admin_action_plugin_activate":"The plugin '%s' was activated by <a href='%s'>%s</a>",
"panel_logs_admin_action_plugin_deactivate":"The plugin '%s' was deactivated by <a href='%s'>%s</a>",
"panel_logs_admin_action_plugin_install":"The plugin '%s' was installed by <a href='%s'>%s</a>",
"panel_logs_admin_action_backup_download":"A backup was downloaded by <a href='%s'>%s</a>",
"panel_logs_admin_action_unknown":"Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>",
"panel_logs_admin_no_logs":"There aren't any events logged.",
"panel_plugins_head":"Plugins", "panel_plugins_head":"Plugins",
"panel_plugins_author_prefix":"Author: ", "panel_plugins_author_prefix":"Author: ",

View File

@ -1355,13 +1355,13 @@ func TestWordFilters(t *testing.T) {
filter := filters[1] filter := filters[1]
expect(t, filter.ID == 1, "Word filter ID should be 1") expect(t, filter.ID == 1, "Word filter ID should be 1")
expect(t, filter.Find == "imbecile", "Word filter needle should be imbecile") expect(t, filter.Find == "imbecile", "Word filter needle should be imbecile")
expect(t, filter.Replacement == "lovely", "Word filter replacement should be lovely") expect(t, filter.Replace == "lovely", "Word filter replacement should be lovely")
filter, err = c.WordFilters.Get(1) filter, err = c.WordFilters.Get(1)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, filter.ID == 1, "Word filter ID should be 1") expect(t, filter.ID == 1, "Word filter ID should be 1")
expect(t, filter.Find == "imbecile", "Word filter needle should be imbecile") expect(t, filter.Find == "imbecile", "Word filter needle should be imbecile")
expect(t, filter.Replacement == "lovely", "Word filter replacement should be lovely") expect(t, filter.Replace == "lovely", "Word filter replacement should be lovely")
// Update // Update
expectNilErr(t, c.WordFilters.Update(1, "b", "a")) expectNilErr(t, c.WordFilters.Update(1, "b", "a"))
@ -1376,13 +1376,13 @@ func TestWordFilters(t *testing.T) {
filter = filters[1] filter = filters[1]
expect(t, filter.ID == 1, "Word filter ID should be 1") expect(t, filter.ID == 1, "Word filter ID should be 1")
expect(t, filter.Find == "b", "Word filter needle should be b") expect(t, filter.Find == "b", "Word filter needle should be b")
expect(t, filter.Replacement == "a", "Word filter replacement should be a") expect(t, filter.Replace == "a", "Word filter replacement should be a")
filter, err = c.WordFilters.Get(1) filter, err = c.WordFilters.Get(1)
expectNilErr(t, err) expectNilErr(t, err)
expect(t, filter.ID == 1, "Word filter ID should be 1") expect(t, filter.ID == 1, "Word filter ID should be 1")
expect(t, filter.Find == "b", "Word filter needle should be imbecile") expect(t, filter.Find == "b", "Word filter needle should be imbecile")
expect(t, filter.Replacement == "a", "Word filter replacement should be a") expect(t, filter.Replace == "a", "Word filter replacement should be a")
// TODO: Add a test for ParseMessage relating to word filters // TODO: Add a test for ParseMessage relating to word filters
@ -1445,8 +1445,9 @@ func TestWidgets(t *testing.T) {
widget := &c.Widget{Position: 0, Side: "rightSidebar", Type: "simple", Enabled: true, Location: "global"} widget := &c.Widget{Position: 0, Side: "rightSidebar", Type: "simple", Enabled: true, Location: "global"}
ewidget := &c.WidgetEdit{widget, map[string]string{"Name": "Test", "Text": "Testing"}} ewidget := &c.WidgetEdit{widget, map[string]string{"Name": "Test", "Text": "Testing"}}
err = ewidget.Create() wid, err := ewidget.Create()
expectNilErr(t, err) expectNilErr(t, err)
expect(t,wid==1,"wid should be 1")
// TODO: Do a test for the widget body // TODO: Do a test for the widget body
widget2, err := c.Widgets.Get(1) widget2, err := c.Widgets.Get(1)

View File

@ -130,8 +130,10 @@ func patcher(scanner *bufio.Scanner) error {
} }
err := patch(scanner) err := patch(scanner)
if err != nil { if err != nil {
fmt.Println("Failed to apply patch "+strconv.Itoa(index+1))
return err return err
} }
fmt.Println("Applied patch "+strconv.Itoa(index+1))
patched++ patched++
} }
@ -140,6 +142,8 @@ func patcher(scanner *bufio.Scanner) error {
if err != nil { if err != nil {
return err return err
} }
} else {
fmt.Println("No new patches found.")
} }
return nil return nil

View File

@ -11,6 +11,7 @@ import (
type tblColumn = qgen.DBTableColumn type tblColumn = qgen.DBTableColumn
type tC = tblColumn type tC = tblColumn
type tblKey = qgen.DBTableKey type tblKey = qgen.DBTableKey
type tK = tblKey
func init() { func init() {
addPatch(0, patch0) addPatch(0, patch0)
@ -40,6 +41,7 @@ func init() {
addPatch(24, patch24) addPatch(24, patch24)
addPatch(25, patch25) addPatch(25, patch25)
addPatch(26, patch26) addPatch(26, patch26)
addPatch(27, patch27)
} }
func patch0(scanner *bufio.Scanner) (err error) { func patch0(scanner *bufio.Scanner) (err error) {
@ -56,8 +58,8 @@ func patch0(scanner *bufio.Scanner) (err error) {
[]tC{ []tC{
tC{"mid", "int", 0, false, true, ""}, tC{"mid", "int", 0, false, true, ""},
}, },
[]tblKey{ []tK{
tblKey{"mid", "primary", "", false}, tK{"mid", "primary", "", false},
}, },
)) ))
if err != nil { if err != nil {
@ -83,8 +85,8 @@ func patch0(scanner *bufio.Scanner) (err error) {
tC{"staffOnly", "boolean", 0, false, false, "0"}, tC{"staffOnly", "boolean", 0, false, false, "0"},
tC{"adminOnly", "boolean", 0, false, false, "0"}, tC{"adminOnly", "boolean", 0, false, false, "0"},
}, },
[]tblKey{ []tK{
tblKey{"miid", "primary", "", false}, tK{"miid", "primary", "", false},
}, },
)) ))
if err != nil { if err != nil {
@ -190,8 +192,8 @@ func patch3(scanner *bufio.Scanner) error {
tC{"ipaddress", "varchar", 200, false, false, ""}, tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"doneAt", "createdAt", 0, false, false, ""}, tC{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tK{
tblKey{"rlid", "primary", "", false}, tK{"rlid", "primary", "", false},
}, },
)) ))
} }
@ -253,8 +255,8 @@ func patch4(scanner *bufio.Scanner) error {
tC{"allowedGroups", "text", 0, false, false, ""}, tC{"allowedGroups", "text", 0, false, false, ""},
tC{"menuID", "int", 0, false, false, "-1"}, tC{"menuID", "int", 0, false, false, "-1"},
}, },
[]tblKey{ []tK{
tblKey{"pid", "primary", "", false}, tK{"pid", "primary", "", false},
}, },
)) ))
if err != nil { if err != nil {
@ -277,7 +279,7 @@ func patch5(scanner *bufio.Scanner) error {
return err return err
} }
err = execStmt(qgen.Builder.SimpleUpdate("menu_items", "path = '/user/edit/'", "path = '/user/edit/critical/'")) err = execStmt(qgen.Builder.SimpleUpdate("menu_items", "path='/user/edit/'", "path='/user/edit/critical/'"))
if err != nil { if err != nil {
return err return err
} }
@ -296,8 +298,8 @@ func patch5(scanner *bufio.Scanner) error {
tC{"scratch8", "varchar", 50, false, false, ""}, tC{"scratch8", "varchar", 50, false, false, ""},
tC{"createdAt", "createdAt", 0, false, false, ""}, tC{"createdAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tK{
tblKey{"uid", "primary", "", false}, tK{"uid", "primary", "", false},
}, },
)) ))
if err != nil { if err != nil {
@ -316,8 +318,8 @@ func patch7(scanner *bufio.Scanner) error {
[]tC{ []tC{
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
}, },
[]tblKey{ []tK{
tblKey{"uid", "primary", "", false}, tK{"uid", "primary", "", false},
}, },
)) ))
} }
@ -378,8 +380,7 @@ func patch8(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.CreateTable("updates", "", "", return execStmt(qgen.Builder.CreateTable("updates", "", "",
[]tC{ []tC{
tC{"dbVersion", "int", 0, false, false, "0"}, tC{"dbVersion", "int", 0, false, false, "0"},
}, }, nil,
[]tblKey{},
)) ))
} }
@ -398,8 +399,8 @@ func patch9(scanner *bufio.Scanner) error {
tC{"ipaddress", "varchar", 200, false, false, ""}, tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"doneAt", "createdAt", 0, false, false, ""}, tC{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tK{
tblKey{"lid", "primary", "", false}, tK{"lid", "primary", "", false},
}, },
)) ))
} }
@ -461,23 +462,21 @@ func patch11(scanner *bufio.Scanner) error {
// We could probably do something more efficient, but as there shouldn't be too many sites right now, we can probably cheat a little, otherwise it'll take forever to get things done // We could probably do something more efficient, but as there shouldn't be too many sites right now, we can probably cheat a little, otherwise it'll take forever to get things done
return acc().Select("topics").Cols("tid").EachInt(func(tid int) error { return acc().Select("topics").Cols("tid").EachInt(func(tid int) error {
stid := itoa(tid) stid := itoa(tid)
count, err := acc().Count("attachments").Where("originTable = 'topics' and originID = " + stid).Total() count, err := acc().Count("attachments").Where("originTable='topics' and originID=" + stid).Total()
if err != nil { if err != nil {
return err return err
} }
_, err = acc().Update("topics").Set("attachCount = ?").Where("tid = " + stid).Exec(count) _, err = acc().Update("topics").Set("attachCount = ?").Where("tid = " + stid).Exec(count)
return err return err
}) })
/*return acc().Select("replies").Cols("rid").EachInt(func(rid int) error { /*return acc().Select("replies").Cols("rid").EachInt(func(rid int) error {
srid := itoa(rid) srid := itoa(rid)
count, err := acc().Count("attachments").Where("originTable = 'replies' and originID = " + srid).Total() count, err := acc().Count("attachments").Where("originTable='replies' and originID=" + srid).Total()
if err != nil { if err != nil {
return err return err
} }
_, err = acc().Update("replies").Set("attachCount = ?").Where("rid=" + srid).Exec(count)
_, err = acc().Update("replies").Set("attachCount = ?").Where("rid = " + srid).Exec(count)
return err return err
})*/ })*/
} }
@ -519,24 +518,19 @@ func patch12(scanner *bufio.Scanner) error {
} }
func patch13(scanner *bufio.Scanner) error { func patch13(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("widgets", tC{"wid", "int", 0, false, true, ""}, &tblKey{"wid", "primary", "", false})) return execStmt(qgen.Builder.AddColumn("widgets", tC{"wid", "int", 0, false, true, ""}, &tK{"wid", "primary", "", false}))
if err != nil {
return err
}
return nil
} }
func patch14(scanner *bufio.Scanner) error { func patch14(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddKey("topics", "title", tblKey{"title", "fulltext", "", false})) err := execStmt(qgen.Builder.AddKey("topics", "title", tK{"title", "fulltext", "", false}))
if err != nil { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.AddKey("topics", "content", tblKey{"content", "fulltext", "", false})) err = execStmt(qgen.Builder.AddKey("topics", "content", tK{"content", "fulltext", "", false}))
if err != nil { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.AddKey("replies", "content", tblKey{"content", "fulltext", "", false})) err = execStmt(qgen.Builder.AddKey("replies", "content", tK{"content", "fulltext", "", false}))
if err != nil { if err != nil {
return err return err
} }
@ -585,12 +579,10 @@ func patch17(scanner *bufio.Scanner) error {
if err != nil { if err != nil {
return err return err
} }
err = acc().Select("topics").Cols("parentID").Where("tid = ?").QueryRow(tid).Scan(&sectionID) err = acc().Select("topics").Cols("parentID").Where("tid = ?").QueryRow(tid).Scan(&sectionID)
if err != nil { if err != nil {
return err return err
} }
_, err = acc().Update("attachments").Set("sectionID = ?, extra = ?").Where("originTable = 'replies' AND originID = ?").Exec(sectionID, tid, rid) _, err = acc().Update("attachments").Set("sectionID = ?, extra = ?").Where("originTable = 'replies' AND originID = ?").Exec(sectionID, tid, rid)
return err return err
}) })
@ -616,12 +608,10 @@ func patch20(scanner *bufio.Scanner) error {
if err != nil { if err != nil {
return err return err
} }
err = acc().Select("activity_stream").Cols("asid").Where("asid = ?").QueryRow(asid).Scan(&asid) err = acc().Select("activity_stream").Cols("asid").Where("asid = ?").QueryRow(asid).Scan(&asid)
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
return err return err
} }
_, err = acc().Delete("activity_stream_matches").Where("asid = ?").Run(asid) _, err = acc().Delete("activity_stream_matches").Where("asid = ?").Run(asid)
return err return err
}) })
@ -673,8 +663,8 @@ func patch23(scanner *bufio.Scanner) error {
tC{"lastReplyAt", "datetime", 0, false, false, ""}, tC{"lastReplyAt", "datetime", 0, false, false, ""},
tC{"lastReplyBy", "int", 0, false, false, ""}, tC{"lastReplyBy", "int", 0, false, false, ""},
}, },
[]tblKey{ []tK{
tblKey{"cid", "primary", "", false}, tK{"cid", "primary", "", false},
}, },
)) ))
if err != nil { if err != nil {
@ -693,8 +683,8 @@ func patch23(scanner *bufio.Scanner) error {
tC{"body", "varchar", 50, false, false, ""}, tC{"body", "varchar", 50, false, false, ""},
tC{"post", "varchar", 50, false, false, "''"}, tC{"post", "varchar", 50, false, false, "''"},
}, },
[]tblKey{ []tK{
tblKey{"pid", "primary", "", false}, tK{"pid", "primary", "", false},
}, },
)) ))
if err != nil { if err != nil {
@ -729,8 +719,8 @@ func patch24(scanner *bufio.Scanner) error {
tC{"level", "int", 0, false, false, ""}, tC{"level", "int", 0, false, false, ""},
tC{"minTime", "int", 0, false, false, ""}, // How long someone needs to have been in their current group before being promoted tC{"minTime", "int", 0, false, false, ""}, // How long someone needs to have been in their current group before being promoted
}, },
[]tblKey{ []tK{
tblKey{"pid", "primary", "", false}, tK{"pid", "primary", "", false},
}, },
)) ))
} }
@ -747,3 +737,11 @@ func patch26(scanner *bufio.Scanner) error {
}, nil, }, nil,
)) ))
} }
func patch27(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("moderation_logs", tC{"extra", "text", 0, false, false, ""}, nil))
if err != nil {
return err
}
return execStmt(qgen.Builder.AddColumn("administration_logs", tC{"extra", "text", 0, false, false, ""}, nil))
}

View File

@ -100,53 +100,59 @@ func modlogsElementType(action string, elementType string, elementID int, actor
return out return out
} }
func adminlogsElementType(action string, elementType string, elementID int, actor *c.User) (out string) { func adminlogsElementType(action string, elementType string, elementID int, actor *c.User, extra string) (out string) {
switch elementType { switch elementType {
// TODO: Record more detail for this, e.g. which field/s was changed // TODO: Record more detail for this, e.g. which field/s was changed
case "user": case "user":
targetUser := handleUnknownUser(c.Users.Get(elementID)) targetUser := handleUnknownUser(c.Users.Get(elementID))
out = p.GetTmplPhrasef("panel_logs_administration_action_user_"+action, targetUser.Link, targetUser.Name, actor.Link, actor.Name) out = p.GetTmplPhrasef("panel_logs_admin_action_user_"+action, targetUser.Link, targetUser.Name, actor.Link, actor.Name)
case "group": case "group":
g, err := c.Groups.Get(elementID) g, err := c.Groups.Get(elementID)
if err != nil { if err != nil {
g = &c.Group{Name: p.GetTmplPhrase("group_unknown")} g = &c.Group{Name: p.GetTmplPhrase("group_unknown")}
} }
out = p.GetTmplPhrasef("panel_logs_administration_action_group_"+action, "/panel/groups/edit/"+strconv.Itoa(g.ID), g.Name, actor.Link, actor.Name) out = p.GetTmplPhrasef("panel_logs_admin_action_group_"+action, "/panel/groups/edit/"+strconv.Itoa(g.ID), g.Name, actor.Link, actor.Name)
case "group_promotion": case "group_promotion":
out = p.GetTmplPhrasef("panel_logs_administration_action_group_promotion_"+action, actor.Link, actor.Name) out = p.GetTmplPhrasef("panel_logs_admin_action_group_promotion_"+action, actor.Link, actor.Name)
case "forum": case "forum":
f, err := c.Forums.Get(elementID) f, err := c.Forums.Get(elementID)
if err != nil { if err != nil {
f = &c.Forum{Name: p.GetTmplPhrase("forum_unknown")} f = &c.Forum{Name: p.GetTmplPhrase("forum_unknown")}
} }
if action == "reorder" { if action == "reorder" {
out = p.GetTmplPhrasef("panel_logs_administration_action_forum_reorder", actor.Link, actor.Name) out = p.GetTmplPhrasef("panel_logs_admin_action_forum_reorder", actor.Link, actor.Name)
} else { } else {
out = p.GetTmplPhrasef("panel_logs_administration_action_forum_"+action, "/panel/forums/edit/"+strconv.Itoa(f.ID), f.Name, actor.Link, actor.Name) out = p.GetTmplPhrasef("panel_logs_admin_action_forum_"+action, "/panel/forums/edit/"+strconv.Itoa(f.ID), f.Name, actor.Link, actor.Name)
} }
case "page": case "page":
pp, err := c.Pages.Get(elementID) pp, err := c.Pages.Get(elementID)
if err != nil { if err != nil {
pp = &c.CustomPage{Name: p.GetTmplPhrase("page_unknown")} 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) out = p.GetTmplPhrasef("panel_logs_admin_action_page_"+action, "/panel/pages/edit/"+strconv.Itoa(pp.ID), pp.Name, actor.Link, actor.Name)
case "setting": case "setting":
s, err := c.SettingBox.Load().(c.SettingMap).BypassGet(action) s, err := c.SettingBox.Load().(c.SettingMap).BypassGet(action)
if err != nil { if err != nil {
s = &c.Setting{Name: p.GetTmplPhrase("setting_unknown")} 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) out = p.GetTmplPhrasef("panel_logs_admin_action_setting_edit", "/panel/settings/edit/"+s.Name, s.Name, actor.Link, actor.Name)
case "word_filter": case "word_filter":
wf, err := c.WordFilters.Get(elementID) out = p.GetTmplPhrasef("panel_logs_admin_action_word_filter_"+action, actor.Link, actor.Name)
if err != nil { case "menu":
wf = &c.WordFilter{} if action == "suborder" {
out = p.GetTmplPhrasef("panel_logs_admin_action_menu_suborder", elementID, actor.Link, actor.Name)
} }
out = p.GetTmplPhrasef("panel_logs_administration_action_word_filter_"+action, "/panel/settings/word-filters/", wf.ID, actor.Link, actor.Name) case "menu_item":
out = p.GetTmplPhrasef("panel_logs_admin_action_menu_item_"+action, "/panel/themes/menus/item/edit/"+strconv.Itoa(elementID), elementID, actor.Link, actor.Name)
case "widget":
out = p.GetTmplPhrasef("panel_logs_admin_action_widget_"+action, "/panel/themes/widgets/", elementID, actor.Link, actor.Name)
case "plugin":
out = p.GetTmplPhrasef("panel_logs_admin_action_plugin_"+action, extra, actor.Link, actor.Name)
case "backup": case "backup":
out = p.GetTmplPhrasef("panel_logs_administration_action_backup_"+action, actor.Link, actor.Name) out = p.GetTmplPhrasef("panel_logs_admin_action_backup_"+action, actor.Link, actor.Name)
} }
if out == "" { if out == "" {
out = p.GetTmplPhrasef("panel_logs_administration_action_unknown", action, elementType, actor.Link, actor.Name) out = p.GetTmplPhrasef("panel_logs_admin_action_unknown", action, elementType, actor.Link, actor.Name)
} }
return out return out
} }
@ -182,8 +188,6 @@ func LogsAdmin(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
basePage.AddNotice("Currently under development")
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 12 perPage := 12
offset, page, lastPage := c.PageOffset(c.AdminLogs.Count(), page, perPage) offset, page, lastPage := c.PageOffset(c.AdminLogs.Count(), page, perPage)
@ -195,7 +199,7 @@ func LogsAdmin(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
llist := make([]c.PageLogItem, len(logs)) llist := make([]c.PageLogItem, len(logs))
for index, log := range logs { for index, log := range logs {
actor := handleUnknownUser(c.Users.Get(log.ActorID)) actor := handleUnknownUser(c.Users.Get(log.ActorID))
action := adminlogsElementType(log.Action, log.ElementType, log.ElementID, actor) action := adminlogsElementType(log.Action, log.ElementType, log.ElementID, actor, log.Extra)
llist[index] = c.PageLogItem{Action: template.HTML(action), IP: log.IP, DoneAt: log.DoneAt} llist[index] = c.PageLogItem{Action: template.HTML(action), IP: log.IP, DoneAt: log.DoneAt}
} }

View File

@ -22,7 +22,7 @@ func Plugins(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
pluginList = append(pluginList, plugin) pluginList = append(pluginList, plugin)
} }
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage,"","","panel_plugins", c.PanelPage{basePage, pluginList, nil}}) return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_plugins", c.PanelPage{basePage, pluginList, nil}})
} }
// TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily // TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily
@ -73,6 +73,10 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user c.User, uname
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, user)
} }
err = c.AdminLogs.CreateExtra("activate", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther) http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil return nil
@ -107,6 +111,10 @@ func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user c.User, unam
if plugin.Deactivate != nil { if plugin.Deactivate != nil {
plugin.Deactivate(plugin) plugin.Deactivate(plugin)
} }
err = c.AdminLogs.CreateExtra("deactivate", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther) http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil return nil
@ -173,6 +181,10 @@ func PluginsInstall(w http.ResponseWriter, r *http.Request, user c.User, uname s
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, user)
} }
err = c.AdminLogs.CreateExtra("install", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther) http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil return nil

View File

@ -58,6 +58,10 @@ func ThemesSetDefault(w http.ResponseWriter, r *http.Request, user c.User, uname
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.CreateExtra("set_default", 0, "theme", user.LastIP, user.ID, c.SanitiseSingleLine(theme.Name))
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther) http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther)
return nil return nil
@ -173,10 +177,10 @@ func themesMenuItemSetters(r *http.Request, i c.MenuItem) c.MenuItem {
i.Aria = getItem("aria") i.Aria = getItem("aria")
i.Tooltip = getItem("tooltip") i.Tooltip = getItem("tooltip")
i.TmplName = getItem("tmplname") i.TmplName = getItem("tmplname")
i.GuestOnly = false
switch getItem("permissions") { switch getItem("permissions") {
case "everyone": case "everyone":
i.GuestOnly = false
i.MemberOnly = false i.MemberOnly = false
i.SuperModOnly = false i.SuperModOnly = false
i.AdminOnly = false i.AdminOnly = false
@ -186,17 +190,14 @@ func themesMenuItemSetters(r *http.Request, i c.MenuItem) c.MenuItem {
i.SuperModOnly = false i.SuperModOnly = false
i.AdminOnly = false i.AdminOnly = false
case "member-only": case "member-only":
i.GuestOnly = false
i.MemberOnly = true i.MemberOnly = true
i.SuperModOnly = false i.SuperModOnly = false
i.AdminOnly = false i.AdminOnly = false
case "supermod-only": case "supermod-only":
i.GuestOnly = false
i.MemberOnly = true i.MemberOnly = true
i.SuperModOnly = true i.SuperModOnly = true
i.AdminOnly = false i.AdminOnly = false
case "admin-only": case "admin-only":
i.GuestOnly = false
i.MemberOnly = true i.MemberOnly = true
i.SuperModOnly = true i.SuperModOnly = true
i.AdminOnly = true i.AdminOnly = true
@ -232,6 +233,11 @@ func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user c.Use
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("edit", menuItem.ID, "menu_item", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, js) return successRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, js)
} }
@ -240,11 +246,11 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user c.U
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes { if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, user, js)
} }
smenuID := r.PostFormValue("mid") smenuID := r.PostFormValue("mid")
if smenuID == "" { if smenuID == "" {
return c.LocalErrorJSQ("No menuID provided", w, r, user, js) return c.LocalErrorJSQ("No menuID provided", w, r, user, js)
@ -260,6 +266,11 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user c.U
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("create", itemID, "menu_item", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, js) return successRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, js)
} }
@ -289,6 +300,11 @@ func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.U
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("delete", menuItem.ID, "menu_item", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/menus/", w, r, js) return successRedirect("/panel/themes/menus/", w, r, js)
} }
@ -326,6 +342,11 @@ func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user c.Us
} }
menuHold.UpdateOrder(updateMap) menuHold.UpdateOrder(updateMap)
err = c.AdminLogs.Create("suborder", menuHold.MenuID, "menu", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/menus/edit/"+strconv.Itoa(mid), w, r, js) return successRedirect("/panel/themes/menus/edit/"+strconv.Itoa(mid), w, r, js)
} }
@ -409,7 +430,6 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User
if err != nil { if err != nil {
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js) return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js)
} }
widget, err := c.Widgets.Get(wid) widget, err := c.Widgets.Get(wid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.NotFoundJSQ(w, r, nil, js) return c.NotFoundJSQ(w, r, nil, js)
@ -426,6 +446,10 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("edit", widget.ID, "widget", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/widgets/", w, r, js) return successRedirect("/panel/themes/widgets/", w, r, js)
} }
@ -446,10 +470,14 @@ func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.Us
return c.LocalErrorJSQ(err.Error(), w, r, user, js) return c.LocalErrorJSQ(err.Error(), w, r, user, js)
} }
err = ewidget.Create() wid, err := ewidget.Create()
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("create", wid, "widget", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/widgets/", w, r, js) return successRedirect("/panel/themes/widgets/", w, r, js)
} }
@ -479,6 +507,10 @@ func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.Us
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("delete", widget.ID, "widget", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/widgets/", w, r, js) return successRedirect("/panel/themes/widgets/", w, r, js)
} }

View File

@ -2,6 +2,7 @@ package panel
import ( import (
"database/sql" "database/sql"
"encoding/json"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -95,13 +96,24 @@ 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 // 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")) replace := strings.TrimSpace(r.PostFormValue("replace"))
wf, err := c.WordFilters.Get(wfid)
if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This word filter doesn't exist.", w, r, user, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.WordFilters.Update(wfid, find, replace) err = c.WordFilters.Update(wfid, find, replace)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("edit", wfid, "word_filter", user.LastIP, user.ID)
lBytes, err := json.Marshal(c.WordFilterDiff{wf.Find, wf.Replace, find, replace})
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.CreateExtra("edit", wfid, "word_filter", user.LastIP, user.ID, string(lBytes))
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
} }
http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther)

View File

@ -4,5 +4,6 @@ CREATE TABLE [administration_logs] (
[elementType] nvarchar (100) not null, [elementType] nvarchar (100) not null,
[ipaddress] nvarchar (200) not null, [ipaddress] nvarchar (200) not null,
[actorID] int not null, [actorID] int not null,
[doneAt] datetime not null [doneAt] datetime not null,
[extra] nvarchar (MAX) not null
); );

View File

@ -4,5 +4,6 @@ CREATE TABLE [moderation_logs] (
[elementType] nvarchar (100) not null, [elementType] nvarchar (100) not null,
[ipaddress] nvarchar (200) not null, [ipaddress] nvarchar (200) not null,
[actorID] int not null, [actorID] int not null,
[doneAt] datetime not null [doneAt] datetime not null,
[extra] nvarchar (MAX) not null
); );

View File

@ -4,5 +4,6 @@ CREATE TABLE `administration_logs` (
`elementType` varchar(100) not null, `elementType` varchar(100) not null,
`ipaddress` varchar(200) not null, `ipaddress` varchar(200) not null,
`actorID` int not null, `actorID` int not null,
`doneAt` datetime not null `doneAt` datetime not null,
`extra` text not null
); );

View File

@ -4,5 +4,6 @@ CREATE TABLE `moderation_logs` (
`elementType` varchar(100) not null, `elementType` varchar(100) not null,
`ipaddress` varchar(200) not null, `ipaddress` varchar(200) not null,
`actorID` int not null, `actorID` int not null,
`doneAt` datetime not null `doneAt` datetime not null,
`extra` text not null
); );

View File

@ -4,5 +4,6 @@ CREATE TABLE "administration_logs" (
`elementType` varchar (100) not null, `elementType` varchar (100) not null,
`ipaddress` varchar (200) not null, `ipaddress` varchar (200) not null,
`actorID` int not null, `actorID` int not null,
`doneAt` timestamp not null `doneAt` timestamp not null,
`extra` text not null
); );

View File

@ -4,5 +4,6 @@ CREATE TABLE "moderation_logs" (
`elementType` varchar (100) not null, `elementType` varchar (100) not null,
`ipaddress` varchar (200) not null, `ipaddress` varchar (200) not null,
`actorID` int not null, `actorID` int not null,
`doneAt` timestamp not null `doneAt` timestamp not null,
`extra` text not null
); );

View File

@ -1,5 +1,5 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_administration_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_logs_admin_head"}}</h1></div>
</div> </div>
<div id="panel_modlogs" class="colstack_item rowlist loglist"> <div id="panel_modlogs" class="colstack_item rowlist loglist">
{{range .Logs}} {{range .Logs}}
@ -15,7 +15,7 @@
</div> </div>
{{else}} {{else}}
<div class="rowitem rowmsg"> <div class="rowitem rowmsg">
<a>{{lang "panel_logs_administration_no_logs"}}</a> <a>{{lang "panel_logs_admin_no_logs"}}</a>
</div> </div>
{{end}} {{end}}
</div> </div>

View File

@ -6,7 +6,7 @@
<div class="rowitem panel_compactrow editable_parent"> <div class="rowitem panel_compactrow editable_parent">
<a data-field="find" data-type="text" href="/panel/settings/word-filters/edit/{{.ID}}" class="editable_block panel_upshift edit_fields filter_find">{{.Find}}</a> <a data-field="find" data-type="text" href="/panel/settings/word-filters/edit/{{.ID}}" class="editable_block panel_upshift edit_fields filter_find">{{.Find}}</a>
<span class="itemSeparator"></span> <span class="itemSeparator"></span>
<a data-field="replace" data-type="text" class="editable_block panel_compacttext filter_replace">{{.Replacement}}</a> <a data-field="replace" data-type="text" class="editable_block panel_compacttext filter_replace">{{.Replace}}</a>
<span class="panel_buttons"> <span class="panel_buttons">
<a class="panel_tag edit_fields hide_on_edit panel_right_button edit_button" aria-label="{{lang "panel_word_filters_edit_button_aria"}}"></a> <a class="panel_tag edit_fields hide_on_edit panel_right_button edit_button" aria-label="{{lang "panel_word_filters_edit_button_aria"}}"></a>
<a class="panel_right_button show_on_edit" href="/panel/settings/word-filters/edit/submit/{{.ID}}"><button class='panel_tag submit_edit' type='submit'>{{lang "panel_word_filters_update_button"}}</button></a> <a class="panel_right_button show_on_edit" href="/panel/settings/word-filters/edit/submit/{{.ID}}"><button class='panel_tag submit_edit' type='submit'>{{lang "panel_word_filters_update_button"}}</button></a>