Moved the plugin manager to the routes package.

Added many, many tests for the plugin system.
Refactored some of the plugin system queries.

Fixed a bug where prepared statements would build up and crash Gosora.
Removed the inline CSS from the plugin rows.
This commit is contained in:
Azareal 2018-07-29 20:54:12 +10:00
parent d74a221467
commit 50d5be6f32
13 changed files with 421 additions and 318 deletions

View File

@ -8,14 +8,18 @@ package common
import ( import (
"database/sql" "database/sql"
"errors"
"log" "log"
"net/http" "net/http"
"../query_gen/lib" "../query_gen/lib"
) )
var ErrPluginNotInstallable = errors.New("This plugin is not installable")
type PluginList map[string]*Plugin type PluginList map[string]*Plugin
// TODO: Have a proper store rather than a map?
var Plugins PluginList = make(map[string]*Plugin) var Plugins PluginList = make(map[string]*Plugin)
// 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? // 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?
@ -152,7 +156,7 @@ type Plugin struct {
Init func() error Init func() error
Activate func() error Activate func() error
Deactivate func() Deactivate func() // TODO: We might want to let this return an error?
Install func() error Install func() error
Uninstall func() error Uninstall func() error
@ -160,8 +164,60 @@ type Plugin struct {
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
} }
func (plugin *Plugin) BypassActive() (active bool, err error) {
err = extendStmts.isActive.QueryRow(plugin.UName).Scan(&active)
if err != nil && err != sql.ErrNoRows {
return false, err
}
return active, nil
}
func (plugin *Plugin) InDatabase() (exists bool, err error) {
var sink bool
err = extendStmts.isActive.QueryRow(plugin.UName).Scan(&sink)
if err != nil && err != sql.ErrNoRows {
return false, err
}
return err == nil, nil
}
// TODO: Silently add to the database, if it doesn't exist there rather than forcing users to call AddToDatabase instead?
func (plugin *Plugin) SetActive(active bool) (err error) {
_, err = extendStmts.setActive.Exec(active, plugin.UName)
if err == nil {
plugin.Active = active
}
return err
}
// TODO: Silently add to the database, if it doesn't exist there rather than forcing users to call AddToDatabase instead?
func (plugin *Plugin) SetInstalled(installed bool) (err error) {
if !plugin.Installable {
return ErrPluginNotInstallable
}
_, err = extendStmts.setInstalled.Exec(installed, plugin.UName)
if err == nil {
plugin.Installed = installed
}
return err
}
func (plugin *Plugin) AddToDatabase(active bool, installed bool) (err error) {
_, err = extendStmts.add.Exec(plugin.UName, active, installed)
if err == nil {
plugin.Active = active
plugin.Installed = installed
}
return err
}
type ExtendStmts struct { type ExtendStmts struct {
getPlugins *sql.Stmt getPlugins *sql.Stmt
isActive *sql.Stmt
setActive *sql.Stmt
setInstalled *sql.Stmt
add *sql.Stmt
} }
var extendStmts ExtendStmts var extendStmts ExtendStmts
@ -170,6 +226,11 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { DbInits.Add(func(acc *qgen.Accumulator) error {
extendStmts = ExtendStmts{ extendStmts = ExtendStmts{
getPlugins: acc.Select("plugins").Columns("uname, active, installed").Prepare(), getPlugins: acc.Select("plugins").Columns("uname, active, installed").Prepare(),
isActive: acc.Select("plugins").Columns("active").Where("uname = ?").Prepare(),
setActive: acc.Update("plugins").Set("active = ?").Where("uname = ?").Prepare(),
setInstalled: acc.Update("plugins").Set("installed = ?").Where("uname = ?").Prepare(),
add: acc.Insert("plugins").Columns("uname, active, installed").Fields("?,?,?").Prepare(),
} }
return acc.FirstError() return acc.FirstError()
}) })

View File

@ -9,17 +9,13 @@ import "./common"
// nolint // nolint
type Stmts struct { type Stmts struct {
isPluginActive *sql.Stmt
isThemeDefault *sql.Stmt isThemeDefault *sql.Stmt
forumEntryExists *sql.Stmt forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
updateGroupPerms *sql.Stmt updateGroupPerms *sql.Stmt
updateGroup *sql.Stmt updateGroup *sql.Stmt
@ -44,14 +40,6 @@ type Stmts struct {
func _gen_mssql() (err error) { func _gen_mssql() (err error) {
common.DebugLog("Building the generated statements") common.DebugLog("Building the generated statements")
common.DebugLog("Preparing isPluginActive statement.")
stmts.isPluginActive, err = db.Prepare("SELECT [active] FROM [plugins] WHERE [uname] = ?1")
if err != nil {
log.Print("Error in isPluginActive statement.")
log.Print("Bad Query: ","SELECT [active] FROM [plugins] WHERE [uname] = ?1")
return err
}
common.DebugLog("Preparing isThemeDefault statement.") common.DebugLog("Preparing isThemeDefault statement.")
stmts.isThemeDefault, err = db.Prepare("SELECT [default] FROM [themes] WHERE [uname] = ?1") stmts.isThemeDefault, err = db.Prepare("SELECT [default] FROM [themes] WHERE [uname] = ?1")
if err != nil { if err != nil {
@ -92,14 +80,6 @@ func _gen_mssql() (err error) {
return err return err
} }
common.DebugLog("Preparing addPlugin statement.")
stmts.addPlugin, err = db.Prepare("INSERT INTO [plugins] ([uname],[active],[installed]) VALUES (?,?,?)")
if err != nil {
log.Print("Error in addPlugin statement.")
log.Print("Bad Query: ","INSERT INTO [plugins] ([uname],[active],[installed]) VALUES (?,?,?)")
return err
}
common.DebugLog("Preparing addTheme statement.") common.DebugLog("Preparing addTheme statement.")
stmts.addTheme, err = db.Prepare("INSERT INTO [themes] ([uname],[default]) VALUES (?,?)") stmts.addTheme, err = db.Prepare("INSERT INTO [themes] ([uname],[default]) VALUES (?,?)")
if err != nil { if err != nil {
@ -116,22 +96,6 @@ func _gen_mssql() (err error) {
return err return err
} }
common.DebugLog("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE [plugins] SET [active] = ? WHERE [uname] = ?")
if err != nil {
log.Print("Error in updatePlugin statement.")
log.Print("Bad Query: ","UPDATE [plugins] SET [active] = ? WHERE [uname] = ?")
return err
}
common.DebugLog("Preparing updatePluginInstall statement.")
stmts.updatePluginInstall, err = db.Prepare("UPDATE [plugins] SET [installed] = ? WHERE [uname] = ?")
if err != nil {
log.Print("Error in updatePluginInstall statement.")
log.Print("Bad Query: ","UPDATE [plugins] SET [installed] = ? WHERE [uname] = ?")
return err
}
common.DebugLog("Preparing updateTheme statement.") common.DebugLog("Preparing updateTheme statement.")
stmts.updateTheme, err = db.Prepare("UPDATE [themes] SET [default] = ? WHERE [uname] = ?") stmts.updateTheme, err = db.Prepare("UPDATE [themes] SET [default] = ? WHERE [uname] = ?")
if err != nil { if err != nil {

View File

@ -11,17 +11,13 @@ import "./common"
// nolint // nolint
type Stmts struct { type Stmts struct {
isPluginActive *sql.Stmt
isThemeDefault *sql.Stmt isThemeDefault *sql.Stmt
forumEntryExists *sql.Stmt forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
updateGroupPerms *sql.Stmt updateGroupPerms *sql.Stmt
updateGroup *sql.Stmt updateGroup *sql.Stmt
@ -46,13 +42,6 @@ type Stmts struct {
func _gen_mysql() (err error) { func _gen_mysql() (err error) {
common.DebugLog("Building the generated statements") common.DebugLog("Building the generated statements")
common.DebugLog("Preparing isPluginActive statement.")
stmts.isPluginActive, err = db.Prepare("SELECT `active` FROM `plugins` WHERE `uname` = ?")
if err != nil {
log.Print("Error in isPluginActive statement.")
return err
}
common.DebugLog("Preparing isThemeDefault statement.") common.DebugLog("Preparing isThemeDefault statement.")
stmts.isThemeDefault, err = db.Prepare("SELECT `default` FROM `themes` WHERE `uname` = ?") stmts.isThemeDefault, err = db.Prepare("SELECT `default` FROM `themes` WHERE `uname` = ?")
if err != nil { if err != nil {
@ -88,13 +77,6 @@ func _gen_mysql() (err error) {
return err return err
} }
common.DebugLog("Preparing addPlugin statement.")
stmts.addPlugin, err = db.Prepare("INSERT INTO `plugins`(`uname`,`active`,`installed`) VALUES (?,?,?)")
if err != nil {
log.Print("Error in addPlugin statement.")
return err
}
common.DebugLog("Preparing addTheme statement.") common.DebugLog("Preparing addTheme statement.")
stmts.addTheme, err = db.Prepare("INSERT INTO `themes`(`uname`,`default`) VALUES (?,?)") stmts.addTheme, err = db.Prepare("INSERT INTO `themes`(`uname`,`default`) VALUES (?,?)")
if err != nil { if err != nil {
@ -109,20 +91,6 @@ func _gen_mysql() (err error) {
return err return err
} }
common.DebugLog("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?")
if err != nil {
log.Print("Error in updatePlugin statement.")
return err
}
common.DebugLog("Preparing updatePluginInstall statement.")
stmts.updatePluginInstall, err = db.Prepare("UPDATE `plugins` SET `installed` = ? WHERE `uname` = ?")
if err != nil {
log.Print("Error in updatePluginInstall statement.")
return err
}
common.DebugLog("Preparing updateTheme statement.") common.DebugLog("Preparing updateTheme statement.")
stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?")
if err != nil { if err != nil {

View File

@ -10,11 +10,8 @@ import "./common"
// nolint // nolint
type Stmts struct { type Stmts struct {
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
createWordFilter *sql.Stmt createWordFilter *sql.Stmt
updatePlugin *sql.Stmt
updatePluginInstall *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
updateGroupPerms *sql.Stmt updateGroupPerms *sql.Stmt
updateGroup *sql.Stmt updateGroup *sql.Stmt
@ -44,13 +41,6 @@ func _gen_pgsql() (err error) {
return err return err
} }
common.DebugLog("Preparing addPlugin statement.")
stmts.addPlugin, err = db.Prepare("INSERT INTO "plugins"("uname","active","installed") VALUES (?,?,?)")
if err != nil {
log.Print("Error in addPlugin statement.")
return err
}
common.DebugLog("Preparing addTheme statement.") common.DebugLog("Preparing addTheme statement.")
stmts.addTheme, err = db.Prepare("INSERT INTO "themes"("uname","default") VALUES (?,?)") stmts.addTheme, err = db.Prepare("INSERT INTO "themes"("uname","default") VALUES (?,?)")
if err != nil { if err != nil {
@ -65,20 +55,6 @@ func _gen_pgsql() (err error) {
return err return err
} }
common.DebugLog("Preparing updatePlugin statement.")
stmts.updatePlugin, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?")
if err != nil {
log.Print("Error in updatePlugin statement.")
return err
}
common.DebugLog("Preparing updatePluginInstall statement.")
stmts.updatePluginInstall, err = db.Prepare("UPDATE `plugins` SET `installed` = ? WHERE `uname` = ?")
if err != nil {
log.Print("Error in updatePluginInstall statement.")
return err
}
common.DebugLog("Preparing updateTheme statement.") common.DebugLog("Preparing updateTheme statement.")
stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?")
if err != nil { if err != nil {

View File

@ -65,10 +65,10 @@ var RouteMap = map[string]interface{}{
"routePanelThemesMenuItemCreateSubmit": routePanelThemesMenuItemCreateSubmit, "routePanelThemesMenuItemCreateSubmit": routePanelThemesMenuItemCreateSubmit,
"routePanelThemesMenuItemDeleteSubmit": routePanelThemesMenuItemDeleteSubmit, "routePanelThemesMenuItemDeleteSubmit": routePanelThemesMenuItemDeleteSubmit,
"routePanelThemesMenuItemOrderSubmit": routePanelThemesMenuItemOrderSubmit, "routePanelThemesMenuItemOrderSubmit": routePanelThemesMenuItemOrderSubmit,
"routePanelPlugins": routePanelPlugins, "panel.Plugins": panel.Plugins,
"routePanelPluginsActivate": routePanelPluginsActivate, "panel.PluginsActivate": panel.PluginsActivate,
"routePanelPluginsDeactivate": routePanelPluginsDeactivate, "panel.PluginsDeactivate": panel.PluginsDeactivate,
"routePanelPluginsInstall": routePanelPluginsInstall, "panel.PluginsInstall": panel.PluginsInstall,
"panel.Users": panel.Users, "panel.Users": panel.Users,
"panel.UsersEdit": panel.UsersEdit, "panel.UsersEdit": panel.UsersEdit,
"panel.UsersEditSubmit": panel.UsersEditSubmit, "panel.UsersEditSubmit": panel.UsersEditSubmit,
@ -194,10 +194,10 @@ var routeMapEnum = map[string]int{
"routePanelThemesMenuItemCreateSubmit": 41, "routePanelThemesMenuItemCreateSubmit": 41,
"routePanelThemesMenuItemDeleteSubmit": 42, "routePanelThemesMenuItemDeleteSubmit": 42,
"routePanelThemesMenuItemOrderSubmit": 43, "routePanelThemesMenuItemOrderSubmit": 43,
"routePanelPlugins": 44, "panel.Plugins": 44,
"routePanelPluginsActivate": 45, "panel.PluginsActivate": 45,
"routePanelPluginsDeactivate": 46, "panel.PluginsDeactivate": 46,
"routePanelPluginsInstall": 47, "panel.PluginsInstall": 47,
"panel.Users": 48, "panel.Users": 48,
"panel.UsersEdit": 49, "panel.UsersEdit": 49,
"panel.UsersEditSubmit": 50, "panel.UsersEditSubmit": 50,
@ -321,10 +321,10 @@ var reverseRouteMapEnum = map[int]string{
41: "routePanelThemesMenuItemCreateSubmit", 41: "routePanelThemesMenuItemCreateSubmit",
42: "routePanelThemesMenuItemDeleteSubmit", 42: "routePanelThemesMenuItemDeleteSubmit",
43: "routePanelThemesMenuItemOrderSubmit", 43: "routePanelThemesMenuItemOrderSubmit",
44: "routePanelPlugins", 44: "panel.Plugins",
45: "routePanelPluginsActivate", 45: "panel.PluginsActivate",
46: "routePanelPluginsDeactivate", 46: "panel.PluginsDeactivate",
47: "routePanelPluginsInstall", 47: "panel.PluginsInstall",
48: "panel.Users", 48: "panel.Users",
49: "panel.UsersEdit", 49: "panel.UsersEdit",
50: "panel.UsersEditSubmit", 50: "panel.UsersEditSubmit",
@ -1223,7 +1223,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
err = routePanelThemesMenuItemOrderSubmit(w,req,user,extraData) err = routePanelThemesMenuItemOrderSubmit(w,req,user,extraData)
case "/panel/plugins/": case "/panel/plugins/":
counters.RouteViewCounter.Bump(44) counters.RouteViewCounter.Bump(44)
err = routePanelPlugins(w,req,user) err = panel.Plugins(w,req,user)
case "/panel/plugins/activate/": case "/panel/plugins/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1232,7 +1232,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(45) counters.RouteViewCounter.Bump(45)
err = routePanelPluginsActivate(w,req,user,extraData) err = panel.PluginsActivate(w,req,user,extraData)
case "/panel/plugins/deactivate/": case "/panel/plugins/deactivate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1241,7 +1241,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(46) counters.RouteViewCounter.Bump(46)
err = routePanelPluginsDeactivate(w,req,user,extraData) err = panel.PluginsDeactivate(w,req,user,extraData)
case "/panel/plugins/install/": case "/panel/plugins/install/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1250,7 +1250,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(47) counters.RouteViewCounter.Bump(47)
err = routePanelPluginsInstall(w,req,user,extraData) err = panel.PluginsInstall(w,req,user,extraData)
case "/panel/users/": case "/panel/users/":
counters.RouteViewCounter.Bump(48) counters.RouteViewCounter.Bump(48)
err = panel.Users(w,req,user) err = panel.Users(w,req,user)

View File

@ -828,6 +828,114 @@ func TestProfileReplyStore(t *testing.T) {
// TODO: Test profileReply.SetBody() and profileReply.Creator() // TODO: Test profileReply.SetBody() and profileReply.Creator()
} }
func TestPluginManager(t *testing.T) {
if !gloinited {
gloinit()
}
if !common.PluginsInited {
common.InitPlugins()
}
_, ok := common.Plugins["fairy-dust"]
expect(t, !ok, "Plugin fairy-dust shouldn't exist")
plugin, ok := common.Plugins["bbcode"]
expect(t, ok, "Plugin bbcode should exist")
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
active, err := plugin.BypassActive()
expectNilErr(t, err)
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
hasPlugin, err := plugin.InDatabase()
expectNilErr(t, err)
expect(t, !hasPlugin, "Plugin bbcode shouldn't exist in the database")
// TODO: Add some test cases for SetActive and SetInstalled before calling AddToDatabase
expectNilErr(t, plugin.AddToDatabase(true, false))
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
expect(t, plugin.Active, "Plugin bbcode should be active")
active, err = plugin.BypassActive()
expectNilErr(t, err)
expect(t, active, "Plugin bbcode should be active in the database too")
hasPlugin, err = plugin.InDatabase()
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should exist in the database")
expect(t, plugin.Init != nil, "Plugin bbcode should have an init function")
expectNilErr(t, plugin.Init())
expectNilErr(t, plugin.SetActive(true))
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
expect(t, plugin.Active, "Plugin bbcode should still be active")
active, err = plugin.BypassActive()
expectNilErr(t, err)
expect(t, active, "Plugin bbcode should still be active in the database too")
hasPlugin, err = plugin.InDatabase()
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
expectNilErr(t, plugin.SetActive(false))
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
active, err = plugin.BypassActive()
expectNilErr(t, err)
expect(t, !active, "Plugin bbcode shouldn't be active in the database")
hasPlugin, err = plugin.InDatabase()
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
expect(t, plugin.Deactivate != nil, "Plugin bbcode should have an init function")
plugin.Deactivate() // Returns nothing
// Not installable, should not be mutated
expect(t, plugin.SetInstalled(true) == common.ErrPluginNotInstallable, "Plugin was set as installed despite not being installable")
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
active, err = plugin.BypassActive()
expectNilErr(t, err)
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
hasPlugin, err = plugin.InDatabase()
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
expect(t, plugin.SetInstalled(false) == common.ErrPluginNotInstallable, "Plugin was set as not installed despite not being installable")
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
active, err = plugin.BypassActive()
expectNilErr(t, err)
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
hasPlugin, err = plugin.InDatabase()
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
// This isn't really installable, but we want to get a few tests done before getting plugins which are stateful
plugin.Installable = true
expectNilErr(t, plugin.SetInstalled(true))
expect(t, plugin.Installable, "Plugin bbcode should be installable")
expect(t, plugin.Installed, "Plugin bbcode should be 'installed'")
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
active, err = plugin.BypassActive()
expectNilErr(t, err)
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
hasPlugin, err = plugin.InDatabase()
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
expectNilErr(t, plugin.SetInstalled(false))
expect(t, plugin.Installable, "Plugin bbcode should be installable")
expect(t, !plugin.Installed, "Plugin bbcode shouldn't be 'installed'")
expect(t, !plugin.Active, "Plugin bbcode shouldn't be active")
active, err = plugin.BypassActive()
expectNilErr(t, err)
expect(t, !active, "Plugin bbcode shouldn't be active in the database either")
hasPlugin, err = plugin.InDatabase()
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
}
func TestSlugs(t *testing.T) { func TestSlugs(t *testing.T) {
var res string var res string
var msgList []MEPair var msgList []MEPair

View File

@ -300,185 +300,6 @@ func routePanelWordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, u
return nil return nil
} }
func routePanelPlugins(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
header, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return common.NoPermissions(w, r, user)
}
header.Title = common.GetTitlePhrase("panel_plugins")
var pluginList []interface{}
for _, plugin := range common.Plugins {
pluginList = append(pluginList, plugin)
}
pi := common.PanelPage{&common.BasePanelPage{header, stats, "plugins", common.ReportForumID}, pluginList, nil}
return panelRenderTemplate("panel_plugins", w, r, user, &pi)
}
func routePanelPluginsActivate(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
_, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return common.NoPermissions(w, r, user)
}
plugin, ok := common.Plugins[uname]
if !ok {
return common.LocalError("The plugin isn't registered in the system", w, r, user)
}
if plugin.Installable && !plugin.Installed {
return common.LocalError("You can't activate this plugin without installing it first", w, r, user)
}
var active bool
err := stmts.isPluginActive.QueryRow(uname).Scan(&active)
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
var hasPlugin = (err == nil)
if common.Plugins[uname].Activate != nil {
err = common.Plugins[uname].Activate()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
}
if hasPlugin {
if active {
return common.LocalError("The plugin is already active", w, r, user)
}
_, err = stmts.updatePlugin.Exec(1, uname)
} else {
_, err = stmts.addPlugin.Exec(uname, 1, 0)
}
if err != nil {
return common.InternalError(err, w, r)
}
log.Printf("Activating plugin '%s'", plugin.Name)
plugin.Active = true
common.Plugins[uname] = plugin
err = common.Plugins[uname].Init()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil
}
func routePanelPluginsDeactivate(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
_, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return common.NoPermissions(w, r, user)
}
plugin, ok := common.Plugins[uname]
if !ok {
return common.LocalError("The plugin isn't registered in the system", w, r, user)
}
var active bool
err := stmts.isPluginActive.QueryRow(uname).Scan(&active)
if err == ErrNoRows {
return common.LocalError("The plugin you're trying to deactivate isn't active", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
}
if !active {
return common.LocalError("The plugin you're trying to deactivate isn't active", w, r, user)
}
_, err = stmts.updatePlugin.Exec(0, uname)
if err != nil {
return common.InternalError(err, w, r)
}
plugin.Active = false
common.Plugins[uname] = plugin
common.Plugins[uname].Deactivate()
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil
}
func routePanelPluginsInstall(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
_, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return common.NoPermissions(w, r, user)
}
plugin, ok := common.Plugins[uname]
if !ok {
return common.LocalError("The plugin isn't registered in the system", w, r, user)
}
if !plugin.Installable {
return common.LocalError("This plugin is not installable", w, r, user)
}
if plugin.Installed {
return common.LocalError("This plugin has already been installed", w, r, user)
}
var active bool
err := stmts.isPluginActive.QueryRow(uname).Scan(&active)
if err != nil && err != ErrNoRows {
return common.InternalError(err, w, r)
}
var hasPlugin = (err == nil)
if common.Plugins[uname].Install != nil {
err = common.Plugins[uname].Install()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
}
if common.Plugins[uname].Activate != nil {
err = common.Plugins[uname].Activate()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
}
if hasPlugin {
_, err = stmts.updatePluginInstall.Exec(1, uname)
if err != nil {
return common.InternalError(err, w, r)
}
_, err = stmts.updatePlugin.Exec(1, uname)
} else {
_, err = stmts.addPlugin.Exec(uname, 1, 1)
}
if err != nil {
return common.InternalError(err, w, r)
}
log.Printf("Installing plugin '%s'", plugin.Name)
plugin.Active = true
plugin.Installed = true
common.Plugins[uname] = plugin
err = common.Plugins[uname].Init()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil
}
func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
header, stats, ferr := common.PanelUserCheck(w, r, &user) header, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {

View File

@ -139,6 +139,15 @@ func (selectItem *AccSelectBuilder) Prepare() *sql.Stmt {
return selectItem.build.SimpleSelect(selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit) return selectItem.build.SimpleSelect(selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.limit)
} }
func (builder *AccSelectBuilder) query() (string, error) {
// TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL.
if builder.dateCutoff != nil || builder.inChain != nil {
selectBuilder := builder.build.GetAdapter().Builder().Select().FromAcc(builder)
return builder.build.GetAdapter().ComplexSelect(selectBuilder)
}
return builder.build.adapter.SimpleSelect("_builder", builder.table, builder.columns, builder.where, builder.orderby, builder.limit)
}
func (selectItem *AccSelectBuilder) Query(args ...interface{}) (*sql.Rows, error) { func (selectItem *AccSelectBuilder) Query(args ...interface{}) (*sql.Rows, error) {
stmt := selectItem.Prepare() stmt := selectItem.Prepare()
if stmt != nil { if stmt != nil {
@ -160,17 +169,21 @@ func (wrap *AccRowWrap) Scan(dest ...interface{}) error {
} }
// TODO: Test to make sure the errors are passed up properly // TODO: Test to make sure the errors are passed up properly
func (selectItem *AccSelectBuilder) QueryRow(args ...interface{}) *AccRowWrap { func (builder *AccSelectBuilder) QueryRow(args ...interface{}) *AccRowWrap {
stmt := selectItem.Prepare() stmt := builder.Prepare()
if stmt != nil { if stmt != nil {
return &AccRowWrap{stmt.QueryRow(args...), nil} return &AccRowWrap{stmt.QueryRow(args...), nil}
} }
return &AccRowWrap{nil, selectItem.build.FirstError()} return &AccRowWrap{nil, builder.build.FirstError()}
} }
// Experimental, reduces lines // Experimental, reduces lines
func (selectItem *AccSelectBuilder) Each(handle func(*sql.Rows) error) error { func (builder *AccSelectBuilder) Each(handle func(*sql.Rows) error) error {
rows, err := selectItem.Query() query, err := builder.query()
if err != nil {
return err
}
rows, err := builder.build.query(query)
if err != nil { if err != nil {
return err return err
} }
@ -184,8 +197,12 @@ func (selectItem *AccSelectBuilder) Each(handle func(*sql.Rows) error) error {
} }
return rows.Err() return rows.Err()
} }
func (selectItem *AccSelectBuilder) EachInt(handle func(int) error) error { func (builder *AccSelectBuilder) EachInt(handle func(int) error) error {
rows, err := selectItem.Query() query, err := builder.query()
if err != nil {
return err
}
rows, err := builder.build.query(query)
if err != nil { if err != nil {
return err return err
} }
@ -227,21 +244,20 @@ func (insert *accInsertBuilder) Prepare() *sql.Stmt {
return insert.build.SimpleInsert(insert.table, insert.columns, insert.fields) return insert.build.SimpleInsert(insert.table, insert.columns, insert.fields)
} }
func (insert *accInsertBuilder) Exec(args ...interface{}) (res sql.Result, err error) { func (builder *accInsertBuilder) Exec(args ...interface{}) (res sql.Result, err error) {
stmt := insert.Prepare() query, err := builder.build.adapter.SimpleInsert("_builder", builder.table, builder.columns, builder.fields)
if stmt != nil { if err != nil {
return stmt.Exec(args...) return res, err
} }
return res, insert.build.FirstError() return builder.build.exec(query, args...)
} }
func (builder *accInsertBuilder) Run(args ...interface{}) (int, error) { func (builder *accInsertBuilder) Run(args ...interface{}) (int, error) {
stmt := builder.Prepare() query, err := builder.build.adapter.SimpleInsert("_builder", builder.table, builder.columns, builder.fields)
if stmt == nil { if err != nil {
return 0, builder.build.FirstError() return 0, err
} }
res, err := builder.build.exec(query, args...)
res, err := stmt.Exec(args...)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -58,6 +58,22 @@ func (build *Accumulator) prepare(res string, err error) *sql.Stmt {
return stmt return stmt
} }
func (build *Accumulator) query(query string, args ...interface{}) (rows *sql.Rows, err error) {
err = build.FirstError()
if err != nil {
return rows, err
}
return build.conn.Query(query, args...)
}
func (build *Accumulator) exec(query string, args ...interface{}) (res sql.Result, err error) {
err = build.FirstError()
if err != nil {
return res, err
}
return build.conn.Exec(query, args...)
}
func (build *Accumulator) Tx(handler func(*TransactionBuilder) error) { func (build *Accumulator) Tx(handler func(*TransactionBuilder) error) {
tx, err := build.conn.Begin() tx, err := build.conn.Begin()
if err != nil { if err != nil {

View File

@ -256,8 +256,6 @@ func writeSelects(adapter qgen.Adapter) error {
// Looking for getTopic? Your statement is in another castle // Looking for getTopic? Your statement is in another castle
build.Select("isPluginActive").Table("plugins").Columns("active").Where("uname = ?").Parse()
//build.Select("isPluginInstalled").Table("plugins").Columns("installed").Where("uname = ?").Parse() //build.Select("isPluginInstalled").Table("plugins").Columns("installed").Where("uname = ?").Parse()
build.Select("isThemeDefault").Table("themes").Columns("default").Where("uname = ?").Parse() build.Select("isThemeDefault").Table("themes").Columns("default").Where("uname = ?").Parse()
@ -284,8 +282,6 @@ func writeInserts(adapter qgen.Adapter) error {
build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse() build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse()
build.Insert("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse()
build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse() build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse()
build.Insert("createWordFilter").Table("word_filters").Columns("find, replacement").Fields("?,?").Parse() build.Insert("createWordFilter").Table("word_filters").Columns("find, replacement").Fields("?,?").Parse()
@ -296,10 +292,6 @@ func writeInserts(adapter qgen.Adapter) error {
func writeUpdates(adapter qgen.Adapter) error { func writeUpdates(adapter qgen.Adapter) error {
build := adapter.Builder() build := adapter.Builder()
build.Update("updatePlugin").Table("plugins").Set("active = ?").Where("uname = ?").Parse()
build.Update("updatePluginInstall").Table("plugins").Set("installed = ?").Where("uname = ?").Parse()
build.Update("updateTheme").Table("themes").Set("default = ?").Where("uname = ?").Parse() build.Update("updateTheme").Table("themes").Set("default = ?").Where("uname = ?").Parse()
build.Update("updateGroupPerms").Table("users_groups").Set("permissions = ?").Where("gid = ?").Parse() build.Update("updateGroupPerms").Table("users_groups").Set("permissions = ?").Where("gid = ?").Parse()

View File

@ -177,10 +177,10 @@ func buildPanelRoutes() {
Action("routePanelThemesMenuItemDeleteSubmit", "/panel/themes/menus/item/delete/submit/", "extraData"), Action("routePanelThemesMenuItemDeleteSubmit", "/panel/themes/menus/item/delete/submit/", "extraData"),
Action("routePanelThemesMenuItemOrderSubmit", "/panel/themes/menus/item/order/edit/submit/", "extraData"), Action("routePanelThemesMenuItemOrderSubmit", "/panel/themes/menus/item/order/edit/submit/", "extraData"),
View("routePanelPlugins", "/panel/plugins/"), View("panel.Plugins", "/panel/plugins/"),
Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extraData"), Action("panel.PluginsActivate", "/panel/plugins/activate/", "extraData"),
Action("routePanelPluginsDeactivate", "/panel/plugins/deactivate/", "extraData"), Action("panel.PluginsDeactivate", "/panel/plugins/deactivate/", "extraData"),
Action("routePanelPluginsInstall", "/panel/plugins/install/", "extraData"), Action("panel.PluginsInstall", "/panel/plugins/install/", "extraData"),
View("panel.Users", "/panel/users/"), View("panel.Users", "/panel/users/"),
View("panel.UsersEdit", "/panel/users/edit/", "extraData"), View("panel.UsersEdit", "/panel/users/edit/", "extraData"),

181
routes/panel/plugins.go Normal file
View File

@ -0,0 +1,181 @@
package panel
import (
"errors"
"log"
"net/http"
"../../common"
)
//routePanelPlugins
func Plugins(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
basePage, ferr := buildBasePage(w, r, &user, "plugins", "plugins")
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return common.NoPermissions(w, r, user)
}
var pluginList []interface{}
for _, plugin := range common.Plugins {
pluginList = append(pluginList, plugin)
}
pi := common.PanelPage{basePage, pluginList, nil}
return panelRenderTemplate("panel_plugins", w, r, user, &pi)
}
//routePanelPluginsActivate
// TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily
func PluginsActivate(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
_, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return common.NoPermissions(w, r, user)
}
plugin, ok := common.Plugins[uname]
if !ok {
return common.LocalError("The plugin isn't registered in the system", w, r, user)
}
if plugin.Installable && !plugin.Installed {
return common.LocalError("You can't activate this plugin without installing it first", w, r, user)
}
active, err := plugin.BypassActive()
hasPlugin, err2 := plugin.InDatabase()
if err != nil || err2 != nil {
return common.InternalError(err, w, r)
}
if plugin.Activate != nil {
err = plugin.Activate()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
}
if hasPlugin {
if active {
return common.LocalError("The plugin is already active", w, r, user)
}
err = plugin.SetActive(true)
} else {
err = plugin.AddToDatabase(true, false)
}
if err != nil {
return common.InternalError(err, w, r)
}
log.Printf("Activating plugin '%s'", plugin.Name)
err = plugin.Init()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil
}
//routePanelPluginsDeactivate
func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
_, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return common.NoPermissions(w, r, user)
}
plugin, ok := common.Plugins[uname]
if !ok {
return common.LocalError("The plugin isn't registered in the system", w, r, user)
}
active, err := plugin.BypassActive()
if !active {
return common.LocalError("The plugin you're trying to deactivate isn't active", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
}
err = plugin.SetActive(false)
if err != nil {
return common.InternalError(err, w, r)
}
plugin.Deactivate()
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil
}
//routePanelPluginsInstall
func PluginsInstall(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
_, ferr := common.SimplePanelUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
if !user.Perms.ManagePlugins {
return common.NoPermissions(w, r, user)
}
plugin, ok := common.Plugins[uname]
if !ok {
return common.LocalError("The plugin isn't registered in the system", w, r, user)
}
if !plugin.Installable {
return common.LocalError("This plugin is not installable", w, r, user)
}
if plugin.Installed {
return common.LocalError("This plugin has already been installed", w, r, user)
}
active, err := plugin.BypassActive()
hasPlugin, err2 := plugin.InDatabase()
if err != nil || err2 != nil {
return common.InternalError(err, w, r)
}
if active {
return common.InternalError(errors.New("An uninstalled plugin is still active"), w, r)
}
if plugin.Install != nil {
err = plugin.Install()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
}
if plugin.Activate != nil {
err = plugin.Activate()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
}
if hasPlugin {
err = plugin.SetInstalled(true)
if err != nil {
return common.InternalError(err, w, r)
}
err = plugin.SetActive(true)
} else {
err = plugin.AddToDatabase(true, true)
}
if err != nil {
return common.InternalError(err, w, r)
}
log.Printf("Installing plugin '%s'", plugin.Name)
err = plugin.Init()
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil
}

View File

@ -11,7 +11,7 @@
<div class="rowitem editable_parent"> <div class="rowitem editable_parent">
<a {{if .URL}}href="{{.URL}}" {{end}}class="editable_block" class="panel_upshift">{{.Name}}</a><br /> <a {{if .URL}}href="{{.URL}}" {{end}}class="editable_block" class="panel_upshift">{{.Name}}</a><br />
<small style="margin-left: 2px;">{{lang "panel_plugins_author_prefix"}}{{.Author}}</small> <small style="margin-left: 2px;">{{lang "panel_plugins_author_prefix"}}{{.Author}}</small>
<span style="float: right;"> <span class="to_right">
{{if .Settings}}<a href="/panel/settings/" class="panel_tag">{{lang "panel_plugins_settings"}}</a>{{end}} {{if .Settings}}<a href="/panel/settings/" class="panel_tag">{{lang "panel_plugins_settings"}}</a>{{end}}
{{if .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">{{lang "panel_plugins_deactivate"}}</a> {{if .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?session={{$.CurrentUser.Session}}" class="panel_tag">{{lang "panel_plugins_deactivate"}}</a>
{{else if .Installable}} {{else if .Installable}}