Refactored the plugin system to use a hook table. This should help to reduce the probability of data races and strange bugs, as-well as helping to abstract certain details, so future changes are less likely to break things.
Fixed the missing background for the create topic non-JS page in Nox. The non-JS create topic page now has a localised title.
This commit is contained in:
parent
218070fceb
commit
876ae4e245
@ -29,9 +29,9 @@ func SendValidationEmail(username string, email string, token string) bool {
|
|||||||
// TODO: Add support for TLS
|
// TODO: Add support for TLS
|
||||||
func SendEmail(email string, subject string, msg string) bool {
|
func SendEmail(email string, subject string, msg string) bool {
|
||||||
// This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server?
|
// This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server?
|
||||||
// TODO: Abstract this
|
ret, hasHook := GetHookTable().VhookNeedHook("email_send_intercept", email, subject, msg)
|
||||||
if Vhooks["email_send_intercept"] != nil {
|
if hasHook {
|
||||||
return Vhooks["email_send_intercept"](email, subject, msg).(bool)
|
return ret.(bool)
|
||||||
}
|
}
|
||||||
body := "Subject: " + subject + "\n\n" + msg + "\n"
|
body := "Subject: " + subject + "\n\n" + msg + "\n"
|
||||||
|
|
||||||
|
261
common/extend.go
261
common/extend.go
@ -12,6 +12,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"../query_gen/lib"
|
"../query_gen/lib"
|
||||||
@ -50,7 +51,7 @@ var hookTableBox atomic.Value
|
|||||||
type HookTable struct {
|
type HookTable struct {
|
||||||
Hooks map[string][]func(interface{}) interface{}
|
Hooks map[string][]func(interface{}) interface{}
|
||||||
Vhooks map[string]func(...interface{}) interface{}
|
Vhooks map[string]func(...interface{}) interface{}
|
||||||
VhookSkippable map[string]func(...interface{}) (bool, RouteError)
|
VhookSkippable_ map[string]func(...interface{}) (bool, RouteError)
|
||||||
Sshooks map[string][]func(string) string
|
Sshooks map[string][]func(string) string
|
||||||
PreRenderHooks map[string][]func(http.ResponseWriter, *http.Request, *User, interface{}) bool
|
PreRenderHooks map[string][]func(http.ResponseWriter, *http.Request, *User, interface{}) bool
|
||||||
|
|
||||||
@ -59,29 +60,122 @@ type HookTable struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
hookTableBox.Store(new(HookTable))
|
RebuildHookTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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?
|
// For extend.go use only, access this via GetHookTable() elsewhere
|
||||||
var Hooks = map[string][]func(interface{}) interface{}{
|
var hookTable = &HookTable{
|
||||||
|
map[string][]func(interface{}) interface{}{
|
||||||
"forums_frow_assign": nil,
|
"forums_frow_assign": nil,
|
||||||
"topic_create_frow_assign": nil,
|
"topic_create_frow_assign": nil,
|
||||||
}
|
},
|
||||||
|
map[string]func(...interface{}) interface{}{
|
||||||
// Hooks with a variable number of arguments
|
|
||||||
var Vhooks = map[string]func(...interface{}) interface{}{
|
|
||||||
"forum_trow_assign": nil,
|
"forum_trow_assign": nil,
|
||||||
"topics_topic_row_assign": nil,
|
"topics_topic_row_assign": nil,
|
||||||
//"topics_user_row_assign": nil,
|
//"topics_user_row_assign": nil,
|
||||||
"topic_reply_row_assign": nil,
|
"topic_reply_row_assign": nil,
|
||||||
"create_group_preappend": nil, // What is this? Investigate!
|
"create_group_preappend": nil, // What is this? Investigate!
|
||||||
"topic_create_pre_loop": nil,
|
"topic_create_pre_loop": nil,
|
||||||
|
},
|
||||||
|
map[string]func(...interface{}) (bool, RouteError){
|
||||||
|
"simple_forum_check_pre_perms": nil,
|
||||||
|
"forum_check_pre_perms": nil,
|
||||||
|
},
|
||||||
|
map[string][]func(string) string{
|
||||||
|
"preparse_preassign": nil,
|
||||||
|
"parse_assign": nil,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
}
|
||||||
|
var hookTableUpdateMutex sync.Mutex
|
||||||
|
|
||||||
|
func RebuildHookTable() {
|
||||||
|
hookTableUpdateMutex.Lock()
|
||||||
|
defer hookTableUpdateMutex.Unlock()
|
||||||
|
unsafeRebuildHookTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsafeRebuildHookTable() {
|
||||||
|
ihookTable := new(HookTable)
|
||||||
|
*ihookTable = *hookTable
|
||||||
|
hookTableBox.Store(ihookTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHookTable() *HookTable {
|
||||||
|
return hookTableBox.Load().(*HookTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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?
|
||||||
|
func (table *HookTable) Hook(name string, data interface{}) interface{} {
|
||||||
|
hooks, ok := table.Hooks[name]
|
||||||
|
if ok {
|
||||||
|
for _, hook := range hooks {
|
||||||
|
data = hook(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// To cover the case in routes/topic.go's CreateTopic route, we could probably obsolete this use and replace it
|
||||||
|
func (table *HookTable) HookSkippable(name string, data interface{}) (skip bool) {
|
||||||
|
hooks, ok := table.Hooks[name]
|
||||||
|
if ok {
|
||||||
|
for _, hook := range hooks {
|
||||||
|
skip = hook(data).(bool)
|
||||||
|
if skip {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks with a variable number of arguments
|
||||||
|
// TODO: Use RunHook semantics to allow multiple lined up plugins / modules their turn?
|
||||||
|
func (table *HookTable) Vhook(name string, data ...interface{}) interface{} {
|
||||||
|
hook := table.Vhooks[name]
|
||||||
|
if hook != nil {
|
||||||
|
return hook(data...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *HookTable) VhookNoRet(name string, data ...interface{}) {
|
||||||
|
hook := table.Vhooks[name]
|
||||||
|
if hook != nil {
|
||||||
|
_ = hook(data...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Find a better way of doing this
|
||||||
|
func (table *HookTable) VhookNeedHook(name string, data ...interface{}) (ret interface{}, hasHook bool) {
|
||||||
|
hook := table.Vhooks[name]
|
||||||
|
if hook != nil {
|
||||||
|
return hook(data...), true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hooks with a variable number of arguments and return values for skipping the parent function and propagating an error upwards
|
// Hooks with a variable number of arguments and return values for skipping the parent function and propagating an error upwards
|
||||||
var VhookSkippable = map[string]func(...interface{}) (bool, RouteError){
|
func (table *HookTable) VhookSkippable(name string, data ...interface{}) (bool, RouteError) {
|
||||||
"simple_forum_check_pre_perms": nil,
|
hook := table.VhookSkippable_[name]
|
||||||
"forum_check_pre_perms": nil,
|
if hook != nil {
|
||||||
|
return hook(data...)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks which take in and spit out a string. This is usually used for parser components
|
||||||
|
// Trying to get a teeny bit of type-safety where-ever possible, especially for such a critical set of hooks
|
||||||
|
func (table *HookTable) Sshook(name string, data string) string {
|
||||||
|
ssHooks, ok := table.Sshooks[name]
|
||||||
|
if ok {
|
||||||
|
for _, hook := range ssHooks {
|
||||||
|
data = hook(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
//var vhookErrorable = map[string]func(...interface{}) (interface{}, RouteError){}
|
//var vhookErrorable = map[string]func(...interface{}) (interface{}, RouteError){}
|
||||||
@ -117,12 +211,6 @@ var messageHooks = map[string][]func(Message, PageInt, ...interface{}) interface
|
|||||||
"topic_reply_row_assign": nil,
|
"topic_reply_row_assign": nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hooks which take in and spit out a string. This is usually used for parser components
|
|
||||||
var Sshooks = map[string][]func(string) string{
|
|
||||||
"preparse_preassign": nil,
|
|
||||||
"parse_assign": nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The hooks which run before the template is rendered for a route
|
// The hooks which run before the template is rendered for a route
|
||||||
var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User, interface{}) bool{
|
var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User, interface{}) bool{
|
||||||
"pre_render": nil,
|
"pre_render": nil,
|
||||||
@ -317,87 +405,76 @@ func (plugins PluginList) Load() error {
|
|||||||
// ? - Is this racey?
|
// ? - Is this racey?
|
||||||
// TODO: Generate the cases in this switch
|
// TODO: Generate the cases in this switch
|
||||||
func (plugin *Plugin) AddHook(name string, handler interface{}) {
|
func (plugin *Plugin) AddHook(name string, handler interface{}) {
|
||||||
|
hookTableUpdateMutex.Lock()
|
||||||
|
defer hookTableUpdateMutex.Unlock()
|
||||||
|
|
||||||
switch h := handler.(type) {
|
switch h := handler.(type) {
|
||||||
case func(interface{}) interface{}:
|
case func(interface{}) interface{}:
|
||||||
if len(Hooks[name]) == 0 {
|
if len(hookTable.Hooks[name]) == 0 {
|
||||||
var hookSlice []func(interface{}) interface{}
|
hookTable.Hooks[name] = []func(interface{}) interface{}{}
|
||||||
hookSlice = append(hookSlice, h)
|
|
||||||
Hooks[name] = hookSlice
|
|
||||||
} else {
|
|
||||||
Hooks[name] = append(Hooks[name], h)
|
|
||||||
}
|
}
|
||||||
plugin.Hooks[name] = len(Hooks[name]) - 1
|
hookTable.Hooks[name] = append(hookTable.Hooks[name], h)
|
||||||
|
plugin.Hooks[name] = len(hookTable.Hooks[name]) - 1
|
||||||
case func(string) string:
|
case func(string) string:
|
||||||
if len(Sshooks[name]) == 0 {
|
if len(hookTable.Sshooks[name]) == 0 {
|
||||||
var hookSlice []func(string) string
|
hookTable.Sshooks[name] = []func(string) string{}
|
||||||
hookSlice = append(hookSlice, h)
|
|
||||||
Sshooks[name] = hookSlice
|
|
||||||
} else {
|
|
||||||
Sshooks[name] = append(Sshooks[name], h)
|
|
||||||
}
|
}
|
||||||
plugin.Hooks[name] = len(Sshooks[name]) - 1
|
hookTable.Sshooks[name] = append(hookTable.Sshooks[name], h)
|
||||||
|
plugin.Hooks[name] = len(hookTable.Sshooks[name]) - 1
|
||||||
case func(http.ResponseWriter, *http.Request, *User, interface{}) bool:
|
case func(http.ResponseWriter, *http.Request, *User, interface{}) bool:
|
||||||
if len(PreRenderHooks[name]) == 0 {
|
if len(PreRenderHooks[name]) == 0 {
|
||||||
var hookSlice []func(http.ResponseWriter, *http.Request, *User, interface{}) bool
|
PreRenderHooks[name] = []func(http.ResponseWriter, *http.Request, *User, interface{}) bool{}
|
||||||
hookSlice = append(hookSlice, h)
|
|
||||||
PreRenderHooks[name] = hookSlice
|
|
||||||
} else {
|
|
||||||
PreRenderHooks[name] = append(PreRenderHooks[name], h)
|
|
||||||
}
|
}
|
||||||
|
PreRenderHooks[name] = append(PreRenderHooks[name], h)
|
||||||
plugin.Hooks[name] = len(PreRenderHooks[name]) - 1
|
plugin.Hooks[name] = len(PreRenderHooks[name]) - 1
|
||||||
case func() error: // ! We might want a more generic name, as we might use this signature for things other than tasks hooks
|
case func() error: // ! We might want a more generic name, as we might use this signature for things other than tasks hooks
|
||||||
if len(taskHooks[name]) == 0 {
|
if len(taskHooks[name]) == 0 {
|
||||||
var hookSlice []func() error
|
taskHooks[name] = []func() error{}
|
||||||
hookSlice = append(hookSlice, h)
|
|
||||||
taskHooks[name] = hookSlice
|
|
||||||
} else {
|
|
||||||
taskHooks[name] = append(taskHooks[name], h)
|
|
||||||
}
|
}
|
||||||
|
taskHooks[name] = append(taskHooks[name], h)
|
||||||
plugin.Hooks[name] = len(taskHooks[name]) - 1
|
plugin.Hooks[name] = len(taskHooks[name]) - 1
|
||||||
case func(...interface{}) interface{}:
|
case func(...interface{}) interface{}:
|
||||||
Vhooks[name] = h
|
hookTable.Vhooks[name] = h
|
||||||
plugin.Hooks[name] = 0
|
plugin.Hooks[name] = 0
|
||||||
case func(...interface{}) (bool, RouteError):
|
case func(...interface{}) (bool, RouteError):
|
||||||
VhookSkippable[name] = h
|
hookTable.VhookSkippable_[name] = h
|
||||||
plugin.Hooks[name] = 0
|
plugin.Hooks[name] = 0
|
||||||
default:
|
default:
|
||||||
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
|
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
|
||||||
}
|
}
|
||||||
|
// TODO: Do this once during plugin activation / deactivation rather than doing it for each hook
|
||||||
|
unsafeRebuildHookTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ? - Is this racey?
|
// ? - Is this racey?
|
||||||
// TODO: Generate the cases in this switch
|
// TODO: Generate the cases in this switch
|
||||||
func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
|
func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
|
||||||
switch handler.(type) {
|
hookTableUpdateMutex.Lock()
|
||||||
case func(interface{}) interface{}:
|
defer hookTableUpdateMutex.Unlock()
|
||||||
|
|
||||||
key, ok := plugin.Hooks[name]
|
key, ok := plugin.Hooks[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("handler not registered as hook")
|
panic("handler not registered as hook")
|
||||||
}
|
}
|
||||||
hook := Hooks[name]
|
|
||||||
|
switch handler.(type) {
|
||||||
|
case func(interface{}) interface{}:
|
||||||
|
hook := hookTable.Hooks[name]
|
||||||
if len(hook) == 1 {
|
if len(hook) == 1 {
|
||||||
hook = []func(interface{}) interface{}{}
|
hook = []func(interface{}) interface{}{}
|
||||||
} else {
|
} else {
|
||||||
hook = append(hook[:key], hook[key+1:]...)
|
hook = append(hook[:key], hook[key+1:]...)
|
||||||
}
|
}
|
||||||
Hooks[name] = hook
|
hookTable.Hooks[name] = hook
|
||||||
case func(string) string:
|
case func(string) string:
|
||||||
key, ok := plugin.Hooks[name]
|
hook := hookTable.Sshooks[name]
|
||||||
if !ok {
|
|
||||||
panic("handler not registered as hook")
|
|
||||||
}
|
|
||||||
hook := Sshooks[name]
|
|
||||||
if len(hook) == 1 {
|
if len(hook) == 1 {
|
||||||
hook = []func(string) string{}
|
hook = []func(string) string{}
|
||||||
} else {
|
} else {
|
||||||
hook = append(hook[:key], hook[key+1:]...)
|
hook = append(hook[:key], hook[key+1:]...)
|
||||||
}
|
}
|
||||||
Sshooks[name] = hook
|
hookTable.Sshooks[name] = hook
|
||||||
case func(http.ResponseWriter, *http.Request, *User, interface{}) bool:
|
case func(http.ResponseWriter, *http.Request, *User, interface{}) bool:
|
||||||
key, ok := plugin.Hooks[name]
|
|
||||||
if !ok {
|
|
||||||
panic("handler not registered as hook")
|
|
||||||
}
|
|
||||||
hook := PreRenderHooks[name]
|
hook := PreRenderHooks[name]
|
||||||
if len(hook) == 1 {
|
if len(hook) == 1 {
|
||||||
hook = []func(http.ResponseWriter, *http.Request, *User, interface{}) bool{}
|
hook = []func(http.ResponseWriter, *http.Request, *User, interface{}) bool{}
|
||||||
@ -406,10 +483,6 @@ func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
|
|||||||
}
|
}
|
||||||
PreRenderHooks[name] = hook
|
PreRenderHooks[name] = hook
|
||||||
case func() error:
|
case func() error:
|
||||||
key, ok := plugin.Hooks[name]
|
|
||||||
if !ok {
|
|
||||||
panic("handler not registered as hook")
|
|
||||||
}
|
|
||||||
hook := taskHooks[name]
|
hook := taskHooks[name]
|
||||||
if len(hook) == 1 {
|
if len(hook) == 1 {
|
||||||
hook = []func() error{}
|
hook = []func() error{}
|
||||||
@ -418,13 +491,15 @@ func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
|
|||||||
}
|
}
|
||||||
taskHooks[name] = hook
|
taskHooks[name] = hook
|
||||||
case func(...interface{}) interface{}:
|
case func(...interface{}) interface{}:
|
||||||
delete(Vhooks, name)
|
delete(hookTable.Vhooks, name)
|
||||||
case func(...interface{}) (bool, RouteError):
|
case func(...interface{}) (bool, RouteError):
|
||||||
delete(VhookSkippable, name)
|
delete(hookTable.VhookSkippable_, name)
|
||||||
default:
|
default:
|
||||||
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
|
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
|
||||||
}
|
}
|
||||||
delete(plugin.Hooks, name)
|
delete(plugin.Hooks, name)
|
||||||
|
// TODO: Do this once during plugin activation / deactivation rather than doing it for each hook
|
||||||
|
unsafeRebuildHookTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add a HasHook method to complete the AddHook, RemoveHook, etc. set?
|
// TODO: Add a HasHook method to complete the AddHook, RemoveHook, etc. set?
|
||||||
@ -450,55 +525,6 @@ func InitPlugins() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ? - Are the following functions racey?
|
// ? - Are the following functions racey?
|
||||||
func RunHook(name string, data interface{}) interface{} {
|
|
||||||
hooks, ok := Hooks[name]
|
|
||||||
if ok {
|
|
||||||
for _, hook := range hooks {
|
|
||||||
data = hook(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunHookNoreturn(name string, data interface{}) {
|
|
||||||
hooks, ok := Hooks[name]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, hook := range hooks {
|
|
||||||
_ = hook(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use RunHook semantics to allow multiple lined up plugins / modules their turn?
|
|
||||||
func RunVhook(name string, data ...interface{}) interface{} {
|
|
||||||
hook := Vhooks[name]
|
|
||||||
if hook != nil {
|
|
||||||
return hook(data...)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunVhookSkippable(name string, data ...interface{}) (bool, RouteError) {
|
|
||||||
return VhookSkippable[name](data...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunVhookNoreturn(name string, data ...interface{}) {
|
|
||||||
hook := Vhooks[name]
|
|
||||||
if hook != nil {
|
|
||||||
_ = hook(data...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Find a better way of doing this
|
|
||||||
func RunVhookNeedHook(name string, data ...interface{}) (ret interface{}, hasHook bool) {
|
|
||||||
hook := Vhooks[name]
|
|
||||||
if hook != nil {
|
|
||||||
return hook(data...), true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunTaskHook(name string) error {
|
func RunTaskHook(name string) error {
|
||||||
for _, hook := range taskHooks[name] {
|
for _, hook := range taskHooks[name] {
|
||||||
err := hook()
|
err := hook()
|
||||||
@ -509,17 +535,6 @@ func RunTaskHook(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trying to get a teeny bit of type-safety where-ever possible, especially for such a critical set of hooks
|
|
||||||
func RunSshook(name string, data string) string {
|
|
||||||
ssHooks, ok := Sshooks[name]
|
|
||||||
if ok {
|
|
||||||
for _, hook := range ssHooks {
|
|
||||||
data = hook(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunPreRenderHook(name string, w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
|
func RunPreRenderHook(name string, w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
|
||||||
// This hook runs on ALL PreRender hooks
|
// This hook runs on ALL PreRender hooks
|
||||||
preRenderHooks, ok := PreRenderHooks["pre_render"]
|
preRenderHooks, ok := PreRenderHooks["pre_render"]
|
||||||
|
@ -229,7 +229,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
|
|||||||
var blankIntList []int
|
var blankIntList []int
|
||||||
var pluginPerms = make(map[string]bool)
|
var pluginPerms = make(map[string]bool)
|
||||||
var pluginPermsBytes = []byte("{}")
|
var pluginPermsBytes = []byte("{}")
|
||||||
RunVhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
|
GetHookTable().Vhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
|
||||||
|
|
||||||
// Generate the forum permissions based on the presets...
|
// Generate the forum permissions based on the presets...
|
||||||
fdata, err := Forums.GetAll()
|
fdata, err := Forums.GetAll()
|
||||||
|
@ -23,6 +23,7 @@ type Header struct {
|
|||||||
Theme *Theme
|
Theme *Theme
|
||||||
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?
|
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?
|
||||||
CurrentUser User // TODO: Deprecate CurrentUser on the page structs and use a pointer here
|
CurrentUser User // TODO: Deprecate CurrentUser on the page structs and use a pointer here
|
||||||
|
Hooks *HookTable
|
||||||
Zone string
|
Zone string
|
||||||
Path string
|
Path string
|
||||||
MetaDesc string
|
MetaDesc string
|
||||||
@ -50,6 +51,7 @@ func (header *Header) AddNotice(name string) {
|
|||||||
type HeaderLite struct {
|
type HeaderLite struct {
|
||||||
Site *site
|
Site *site
|
||||||
Settings SettingMap
|
Settings SettingMap
|
||||||
|
Hooks *HookTable
|
||||||
ExtData ExtData
|
ExtData ExtData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ func PreparseMessage(msg string) string {
|
|||||||
msg = strings.Replace(msg, " ", "", -1)
|
msg = strings.Replace(msg, " ", "", -1)
|
||||||
msg = strings.Replace(msg, "\r", "", -1) // Windows artifact
|
msg = strings.Replace(msg, "\r", "", -1) // Windows artifact
|
||||||
//msg = strings.Replace(msg, "\n\n\n\n", "\n\n\n", -1)
|
//msg = strings.Replace(msg, "\n\n\n\n", "\n\n\n", -1)
|
||||||
msg = RunSshook("preparse_preassign", msg)
|
msg = GetHookTable().Sshook("preparse_preassign", msg)
|
||||||
// There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there
|
// There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there
|
||||||
msg = SanitiseBody(msg)
|
msg = SanitiseBody(msg)
|
||||||
|
|
||||||
@ -624,7 +624,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
|
|||||||
}
|
}
|
||||||
|
|
||||||
msg = strings.Replace(msg, "\n", "<br>", -1)
|
msg = strings.Replace(msg, "\n", "<br>", -1)
|
||||||
msg = RunSshook("parse_assign", msg)
|
msg = GetHookTable().Sshook("parse_assign", msg)
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,28 +20,29 @@ var ForumUserCheck func(w http.ResponseWriter, r *http.Request, user *User, fid
|
|||||||
var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, err RouteError) = simpleUserCheck
|
var SimpleUserCheck func(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, err RouteError) = simpleUserCheck
|
||||||
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (header *Header, err RouteError) = userCheck
|
var UserCheck func(w http.ResponseWriter, r *http.Request, user *User) (header *Header, err RouteError) = userCheck
|
||||||
|
|
||||||
func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerLite *HeaderLite, rerr RouteError) {
|
func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (header *HeaderLite, rerr RouteError) {
|
||||||
|
header, rerr = SimpleUserCheck(w, r, user)
|
||||||
|
if rerr != nil {
|
||||||
|
return header, rerr
|
||||||
|
}
|
||||||
if !Forums.Exists(fid) {
|
if !Forums.Exists(fid) {
|
||||||
return nil, PreError("The target forum doesn't exist.", w, r)
|
return nil, PreError("The target forum doesn't exist.", w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
|
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
|
||||||
if VhookSkippable["simple_forum_check_pre_perms"] != nil {
|
skip, rerr := header.Hooks.VhookSkippable("simple_forum_check_pre_perms", w, r, user, &fid, &header)
|
||||||
var skip bool
|
|
||||||
skip, rerr = RunVhookSkippable("simple_forum_check_pre_perms", w, r, user, &fid, &headerLite)
|
|
||||||
if skip || rerr != nil {
|
if skip || rerr != nil {
|
||||||
return headerLite, rerr
|
return header, rerr
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fperms, err := FPStore.Get(fid, user.Group)
|
fperms, err := FPStore.Get(fid, user.Group)
|
||||||
if err == ErrNoRows {
|
if err == ErrNoRows {
|
||||||
fperms = BlankForumPerms()
|
fperms = BlankForumPerms()
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return headerLite, InternalError(err, w, r)
|
return header, InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
cascadeForumPerms(fperms, user)
|
cascadeForumPerms(fperms, user)
|
||||||
return headerLite, nil
|
return header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (header *Header, rerr RouteError) {
|
func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (header *Header, rerr RouteError) {
|
||||||
@ -53,13 +54,10 @@ func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int)
|
|||||||
return header, NotFound(w, r, header)
|
return header, NotFound(w, r, header)
|
||||||
}
|
}
|
||||||
|
|
||||||
if VhookSkippable["forum_check_pre_perms"] != nil {
|
skip, rerr := header.Hooks.VhookSkippable("forum_check_pre_perms", w, r, user, &fid, &header)
|
||||||
var skip bool
|
|
||||||
skip, rerr = RunVhookSkippable("forum_check_pre_perms", w, r, user, &fid, &header)
|
|
||||||
if skip || rerr != nil {
|
if skip || rerr != nil {
|
||||||
return header, rerr
|
return header, rerr
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fperms, err := FPStore.Get(fid, user.Group)
|
fperms, err := FPStore.Get(fid, user.Group)
|
||||||
if err == ErrNoRows {
|
if err == ErrNoRows {
|
||||||
@ -99,7 +97,6 @@ func cascadeForumPerms(fperms *ForumPerms, user *User) {
|
|||||||
// TODO: Do a panel specific theme?
|
// TODO: Do a panel specific theme?
|
||||||
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Header, stats PanelStats, rerr RouteError) {
|
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Header, stats PanelStats, rerr RouteError) {
|
||||||
var theme = &Theme{Name: ""}
|
var theme = &Theme{Name: ""}
|
||||||
|
|
||||||
cookie, err := r.Cookie("current_theme")
|
cookie, err := r.Cookie("current_theme")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inTheme, ok := Themes[html.EscapeString(cookie.Value)]
|
inTheme, ok := Themes[html.EscapeString(cookie.Value)]
|
||||||
@ -117,6 +114,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
|||||||
Themes: Themes,
|
Themes: Themes,
|
||||||
Theme: theme,
|
Theme: theme,
|
||||||
CurrentUser: *user,
|
CurrentUser: *user,
|
||||||
|
Hooks: GetHookTable(),
|
||||||
Zone: "panel",
|
Zone: "panel",
|
||||||
Writer: w,
|
Writer: w,
|
||||||
}
|
}
|
||||||
@ -155,7 +153,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
|||||||
}
|
}
|
||||||
|
|
||||||
func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) {
|
func simplePanelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerLite *HeaderLite, rerr RouteError) {
|
||||||
return simpleUserCheck(w, r, user)
|
return SimpleUserCheck(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimpleUserCheck is back from the grave, yay :D
|
// SimpleUserCheck is back from the grave, yay :D
|
||||||
@ -163,6 +161,7 @@ func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
|||||||
return &HeaderLite{
|
return &HeaderLite{
|
||||||
Site: Site,
|
Site: Site,
|
||||||
Settings: SettingBox.Load().(SettingMap),
|
Settings: SettingBox.Load().(SettingMap),
|
||||||
|
Hooks: GetHookTable(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,6 +186,7 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Head
|
|||||||
Themes: Themes,
|
Themes: Themes,
|
||||||
Theme: theme,
|
Theme: theme,
|
||||||
CurrentUser: *user, // ! Some things rely on this being a pointer downstream from this function
|
CurrentUser: *user, // ! Some things rely on this being a pointer downstream from this function
|
||||||
|
Hooks: GetHookTable(),
|
||||||
Zone: "frontend",
|
Zone: "frontend",
|
||||||
Writer: w,
|
Writer: w,
|
||||||
}
|
}
|
||||||
|
@ -258,7 +258,7 @@ func (tList *DefaultTopicList) getList(page int, orderby string, argList []inter
|
|||||||
topicItem.RelativeLastReplyAt = RelativeTime(topicItem.LastReplyAt)
|
topicItem.RelativeLastReplyAt = RelativeTime(topicItem.LastReplyAt)
|
||||||
|
|
||||||
// TODO: Rename this Vhook to better reflect moving the topic list from /routes/ to /common/
|
// TODO: Rename this Vhook to better reflect moving the topic list from /routes/ to /common/
|
||||||
RunVhook("topics_topic_row_assign", &topicItem, &forum)
|
GetHookTable().Vhook("topics_topic_row_assign", &topicItem, &forum)
|
||||||
topicList = append(topicList, &topicItem)
|
topicList = append(topicList, &topicItem)
|
||||||
reqUserList[topicItem.CreatedBy] = true
|
reqUserList[topicItem.CreatedBy] = true
|
||||||
reqUserList[topicItem.LastReplyBy] = true
|
reqUserList[topicItem.LastReplyBy] = true
|
||||||
|
@ -121,6 +121,7 @@
|
|||||||
"page":"Page",
|
"page":"Page",
|
||||||
"topics":"All Topics",
|
"topics":"All Topics",
|
||||||
"forums":"Forum List",
|
"forums":"Forum List",
|
||||||
|
"create_topic":"Create Topic",
|
||||||
"login":"Login",
|
"login":"Login",
|
||||||
"login_mfa_verify":"2FA Verify",
|
"login_mfa_verify":"2FA Verify",
|
||||||
"register":"Registration",
|
"register":"Registration",
|
||||||
|
17
misc_test.go
17
misc_test.go
@ -967,14 +967,23 @@ func TestPluginManager(t *testing.T) {
|
|||||||
expectNilErr(t, plugin.SetActive(false))
|
expectNilErr(t, plugin.SetActive(false))
|
||||||
|
|
||||||
// Hook tests
|
// Hook tests
|
||||||
expect(t, common.RunSshook("haha", "ho") == "ho", "Sshook shouldn't have anything bound to it yet")
|
expect(t, common.GetHookTable().Sshook("haha", "ho") == "ho", "Sshook shouldn't have anything bound to it yet")
|
||||||
var handle = func(in string) (out string) {
|
handle := func(in string) (out string) {
|
||||||
return in + "hi"
|
return in + "hi"
|
||||||
}
|
}
|
||||||
plugin.AddHook("haha", handle)
|
plugin.AddHook("haha", handle)
|
||||||
expect(t, common.RunSshook("haha", "ho") == "hohi", "Sshook didn't give hohi")
|
expect(t, common.GetHookTable().Sshook("haha", "ho") == "hohi", "Sshook didn't give hohi")
|
||||||
plugin.RemoveHook("haha", handle)
|
plugin.RemoveHook("haha", handle)
|
||||||
expect(t, common.RunSshook("haha", "ho") == "ho", "Sshook shouldn't have anything bound to it anymore")
|
expect(t, common.GetHookTable().Sshook("haha", "ho") == "ho", "Sshook shouldn't have anything bound to it anymore")
|
||||||
|
|
||||||
|
expect(t, common.GetHookTable().Hook("haha", "ho") == "ho", "Hook shouldn't have anything bound to it yet")
|
||||||
|
handle2 := func(inI interface{}) (out interface{}) {
|
||||||
|
return inI.(string) + "hi"
|
||||||
|
}
|
||||||
|
plugin.AddHook("hehe", handle2)
|
||||||
|
expect(t, common.GetHookTable().Hook("hehe", "ho").(string) == "hohi", "Hook didn't give hohi")
|
||||||
|
plugin.RemoveHook("hehe", handle2)
|
||||||
|
expect(t, common.GetHookTable().Hook("hehe", "ho").(string) == "ho", "Hook shouldn't have anything bound to it anymore")
|
||||||
|
|
||||||
// TODO: Add tests for more hook types
|
// TODO: Add tests for more hook types
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import "testing"
|
|||||||
|
|
||||||
// go test -v
|
// go test -v
|
||||||
|
|
||||||
|
// TODO: Write a test for Hello World?
|
||||||
|
|
||||||
type MEPair struct {
|
type MEPair struct {
|
||||||
Msg string
|
Msg string
|
||||||
Expects string
|
Expects string
|
||||||
|
@ -81,7 +81,7 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, sfid st
|
|||||||
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
|
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
|
||||||
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
|
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
|
||||||
|
|
||||||
common.RunVhookNoreturn("forum_trow_assign", &topicItem, &forum)
|
header.Hooks.VhookNoRet("forum_trow_assign", &topicItem, &forum)
|
||||||
topicList = append(topicList, &topicItem)
|
topicList = append(topicList, &topicItem)
|
||||||
reqUserList[topicItem.CreatedBy] = true
|
reqUserList[topicItem.CreatedBy] = true
|
||||||
reqUserList[topicItem.LastReplyBy] = true
|
reqUserList[topicItem.LastReplyBy] = true
|
||||||
|
@ -47,7 +47,7 @@ func ForumList(w http.ResponseWriter, r *http.Request, user common.User) common.
|
|||||||
} else {
|
} else {
|
||||||
forum.LastTopicTime = ""
|
forum.LastTopicTime = ""
|
||||||
}
|
}
|
||||||
common.RunHook("forums_frow_assign", &forum)
|
header.Hooks.Hook("forums_frow_assign", &forum)
|
||||||
forumList = append(forumList, forum)
|
forumList = append(forumList, forum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ReportSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError {
|
func ReportSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError {
|
||||||
_, ferr := common.SimpleUserCheck(w, r, &user)
|
headerLite, ferr := common.SimpleUserCheck(w, r, &user)
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ func ReportSubmit(w http.ResponseWriter, r *http.Request, user common.User, site
|
|||||||
title = "Topic: " + topic.Title
|
title = "Topic: " + topic.Title
|
||||||
content = topic.Content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID)
|
content = topic.Content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID)
|
||||||
} else {
|
} else {
|
||||||
_, hasHook := common.RunVhookNeedHook("report_preassign", &itemID, &itemType)
|
_, hasHook := headerLite.Hooks.VhookNeedHook("report_preassign", &itemID, &itemType)
|
||||||
if hasHook {
|
if hasHook {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
|
|||||||
likedQueryList = append(likedQueryList, replyItem.ID)
|
likedQueryList = append(likedQueryList, replyItem.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
common.RunVhookNoreturn("topic_reply_row_assign", &tpage, &replyItem)
|
header.Hooks.VhookNoRet("topic_reply_row_assign", &tpage, &replyItem)
|
||||||
// TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis?
|
// TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis?
|
||||||
tpage.ItemList = append(tpage.ItemList, replyItem)
|
tpage.ItemList = append(tpage.ItemList, replyItem)
|
||||||
}
|
}
|
||||||
@ -235,6 +235,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
|
|||||||
// TODO: Add a permission to stop certain users from using custom avatars
|
// TODO: Add a permission to stop certain users from using custom avatars
|
||||||
// ? - Log username changes and put restrictions on this?
|
// ? - Log username changes and put restrictions on this?
|
||||||
// TODO: Test this
|
// TODO: Test this
|
||||||
|
// TODO: Revamp this route
|
||||||
func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
|
||||||
var fid int
|
var fid int
|
||||||
var err error
|
var err error
|
||||||
@ -256,13 +257,13 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid
|
|||||||
return common.NoPermissions(w, r, user)
|
return common.NoPermissions(w, r, user)
|
||||||
}
|
}
|
||||||
// TODO: Add a phrase for this
|
// TODO: Add a phrase for this
|
||||||
header.Title = "Create Topic"
|
header.Title = common.GetTitlePhrase("create_topic")
|
||||||
header.Zone = "create_topic"
|
header.Zone = "create_topic"
|
||||||
|
|
||||||
// Lock this to the forum being linked?
|
// Lock this to the forum being linked?
|
||||||
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)
|
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)
|
||||||
var strictmode bool
|
var strictmode bool
|
||||||
common.RunVhookNoreturn("topic_create_pre_loop", w, r, fid, &header, &user, &strictmode)
|
header.Hooks.VhookNoRet("topic_create_pre_loop", w, r, fid, &header, &user, &strictmode)
|
||||||
|
|
||||||
// TODO: Re-add support for plugin_guilds
|
// TODO: Re-add support for plugin_guilds
|
||||||
var forumList []common.Forum
|
var forumList []common.Forum
|
||||||
@ -295,12 +296,9 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid
|
|||||||
if forum.Name != "" && forum.Active {
|
if forum.Name != "" && forum.Active {
|
||||||
fcopy := forum.Copy()
|
fcopy := forum.Copy()
|
||||||
// TODO: Abstract this
|
// TODO: Abstract this
|
||||||
if common.Hooks["topic_create_frow_assign"] != nil {
|
if header.Hooks.HookSkippable("topic_create_frow_assign", &fcopy) {
|
||||||
// TODO: Add the skip feature to all the other row based hooks?
|
|
||||||
if common.RunHook("topic_create_frow_assign", &fcopy).(bool) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
forumList = append(forumList, fcopy)
|
forumList = append(forumList, fcopy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="rowblock rowhead">
|
<div class="rowblock rowhead">
|
||||||
<div class="rowitem"><h1>{{lang "create_topic_head"}}</h1></div>
|
<div class="rowitem"><h1>{{lang "create_topic_head"}}</h1></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rowblock">
|
<div class="rowblock the_form">
|
||||||
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?session={{.CurrentUser.Session}}" method="post"></form>
|
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?session={{.CurrentUser.Session}}" method="post"></form>
|
||||||
<div class="formrow real_first_child">
|
<div class="formrow real_first_child">
|
||||||
<div class="formitem formlabel"><a>{{lang "create_topic_board"}}</a></div>
|
<div class="formitem formlabel"><a>{{lang "create_topic_board"}}</a></div>
|
||||||
|
Loading…
Reference in New Issue
Block a user