From 50d5be6f32db7030b806351d15e05e0864b528a5 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sun, 29 Jul 2018 20:54:12 +1000 Subject: [PATCH] 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. --- common/extend.go | 63 +++++++++++- gen_mssql.go | 36 ------- gen_mysql.go | 32 ------ gen_pgsql.go | 24 ----- gen_router.go | 32 +++--- misc_test.go | 108 ++++++++++++++++++++ panel_routes.go | 179 --------------------------------- query_gen/lib/acc_builders.go | 50 ++++++---- query_gen/lib/accumulator.go | 16 +++ query_gen/main.go | 8 -- router_gen/routes.go | 8 +- routes/panel/plugins.go | 181 ++++++++++++++++++++++++++++++++++ templates/panel_plugins.html | 2 +- 13 files changed, 421 insertions(+), 318 deletions(-) create mode 100644 routes/panel/plugins.go diff --git a/common/extend.go b/common/extend.go index dd3882b4..2b8470a3 100644 --- a/common/extend.go +++ b/common/extend.go @@ -8,14 +8,18 @@ package common import ( "database/sql" + "errors" "log" "net/http" "../query_gen/lib" ) +var ErrPluginNotInstallable = errors.New("This plugin is not installable") + type PluginList map[string]*Plugin +// TODO: Have a proper store rather than a map? 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? @@ -152,7 +156,7 @@ type Plugin struct { Init func() error Activate func() error - Deactivate func() + Deactivate func() // TODO: We might want to let this return an error? Install 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 } +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 { getPlugins *sql.Stmt + + isActive *sql.Stmt + setActive *sql.Stmt + setInstalled *sql.Stmt + add *sql.Stmt } var extendStmts ExtendStmts @@ -170,6 +226,11 @@ func init() { DbInits.Add(func(acc *qgen.Accumulator) error { extendStmts = ExtendStmts{ 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() }) diff --git a/gen_mssql.go b/gen_mssql.go index 46ddf0be..922c9d7a 100644 --- a/gen_mssql.go +++ b/gen_mssql.go @@ -9,17 +9,13 @@ import "./common" // nolint type Stmts struct { - isPluginActive *sql.Stmt isThemeDefault *sql.Stmt forumEntryExists *sql.Stmt groupEntryExists *sql.Stmt getForumTopics *sql.Stmt addForumPermsToForum *sql.Stmt - addPlugin *sql.Stmt addTheme *sql.Stmt createWordFilter *sql.Stmt - updatePlugin *sql.Stmt - updatePluginInstall *sql.Stmt updateTheme *sql.Stmt updateGroupPerms *sql.Stmt updateGroup *sql.Stmt @@ -44,14 +40,6 @@ type Stmts struct { func _gen_mssql() (err error) { 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.") stmts.isThemeDefault, err = db.Prepare("SELECT [default] FROM [themes] WHERE [uname] = ?1") if err != nil { @@ -92,14 +80,6 @@ func _gen_mssql() (err error) { 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.") stmts.addTheme, err = db.Prepare("INSERT INTO [themes] ([uname],[default]) VALUES (?,?)") if err != nil { @@ -116,22 +96,6 @@ func _gen_mssql() (err error) { 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.") stmts.updateTheme, err = db.Prepare("UPDATE [themes] SET [default] = ? WHERE [uname] = ?") if err != nil { diff --git a/gen_mysql.go b/gen_mysql.go index 030fde40..5e1cc495 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -11,17 +11,13 @@ import "./common" // nolint type Stmts struct { - isPluginActive *sql.Stmt isThemeDefault *sql.Stmt forumEntryExists *sql.Stmt groupEntryExists *sql.Stmt getForumTopics *sql.Stmt addForumPermsToForum *sql.Stmt - addPlugin *sql.Stmt addTheme *sql.Stmt createWordFilter *sql.Stmt - updatePlugin *sql.Stmt - updatePluginInstall *sql.Stmt updateTheme *sql.Stmt updateGroupPerms *sql.Stmt updateGroup *sql.Stmt @@ -46,13 +42,6 @@ type Stmts struct { func _gen_mysql() (err error) { 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.") stmts.isThemeDefault, err = db.Prepare("SELECT `default` FROM `themes` WHERE `uname` = ?") if err != nil { @@ -88,13 +77,6 @@ func _gen_mysql() (err error) { 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.") stmts.addTheme, err = db.Prepare("INSERT INTO `themes`(`uname`,`default`) VALUES (?,?)") if err != nil { @@ -109,20 +91,6 @@ func _gen_mysql() (err error) { 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.") stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") if err != nil { diff --git a/gen_pgsql.go b/gen_pgsql.go index e6897249..cbff962d 100644 --- a/gen_pgsql.go +++ b/gen_pgsql.go @@ -10,11 +10,8 @@ import "./common" // nolint type Stmts struct { addForumPermsToForum *sql.Stmt - addPlugin *sql.Stmt addTheme *sql.Stmt createWordFilter *sql.Stmt - updatePlugin *sql.Stmt - updatePluginInstall *sql.Stmt updateTheme *sql.Stmt updateGroupPerms *sql.Stmt updateGroup *sql.Stmt @@ -44,13 +41,6 @@ func _gen_pgsql() (err error) { 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.") stmts.addTheme, err = db.Prepare("INSERT INTO "themes"("uname","default") VALUES (?,?)") if err != nil { @@ -65,20 +55,6 @@ func _gen_pgsql() (err error) { 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.") stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") if err != nil { diff --git a/gen_router.go b/gen_router.go index e7e461ed..7d6e9093 100644 --- a/gen_router.go +++ b/gen_router.go @@ -65,10 +65,10 @@ var RouteMap = map[string]interface{}{ "routePanelThemesMenuItemCreateSubmit": routePanelThemesMenuItemCreateSubmit, "routePanelThemesMenuItemDeleteSubmit": routePanelThemesMenuItemDeleteSubmit, "routePanelThemesMenuItemOrderSubmit": routePanelThemesMenuItemOrderSubmit, - "routePanelPlugins": routePanelPlugins, - "routePanelPluginsActivate": routePanelPluginsActivate, - "routePanelPluginsDeactivate": routePanelPluginsDeactivate, - "routePanelPluginsInstall": routePanelPluginsInstall, + "panel.Plugins": panel.Plugins, + "panel.PluginsActivate": panel.PluginsActivate, + "panel.PluginsDeactivate": panel.PluginsDeactivate, + "panel.PluginsInstall": panel.PluginsInstall, "panel.Users": panel.Users, "panel.UsersEdit": panel.UsersEdit, "panel.UsersEditSubmit": panel.UsersEditSubmit, @@ -194,10 +194,10 @@ var routeMapEnum = map[string]int{ "routePanelThemesMenuItemCreateSubmit": 41, "routePanelThemesMenuItemDeleteSubmit": 42, "routePanelThemesMenuItemOrderSubmit": 43, - "routePanelPlugins": 44, - "routePanelPluginsActivate": 45, - "routePanelPluginsDeactivate": 46, - "routePanelPluginsInstall": 47, + "panel.Plugins": 44, + "panel.PluginsActivate": 45, + "panel.PluginsDeactivate": 46, + "panel.PluginsInstall": 47, "panel.Users": 48, "panel.UsersEdit": 49, "panel.UsersEditSubmit": 50, @@ -321,10 +321,10 @@ var reverseRouteMapEnum = map[int]string{ 41: "routePanelThemesMenuItemCreateSubmit", 42: "routePanelThemesMenuItemDeleteSubmit", 43: "routePanelThemesMenuItemOrderSubmit", - 44: "routePanelPlugins", - 45: "routePanelPluginsActivate", - 46: "routePanelPluginsDeactivate", - 47: "routePanelPluginsInstall", + 44: "panel.Plugins", + 45: "panel.PluginsActivate", + 46: "panel.PluginsDeactivate", + 47: "panel.PluginsInstall", 48: "panel.Users", 49: "panel.UsersEdit", 50: "panel.UsersEditSubmit", @@ -1223,7 +1223,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { err = routePanelThemesMenuItemOrderSubmit(w,req,user,extraData) case "/panel/plugins/": counters.RouteViewCounter.Bump(44) - err = routePanelPlugins(w,req,user) + err = panel.Plugins(w,req,user) case "/panel/plugins/activate/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1232,7 +1232,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(45) - err = routePanelPluginsActivate(w,req,user,extraData) + err = panel.PluginsActivate(w,req,user,extraData) case "/panel/plugins/deactivate/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1241,7 +1241,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(46) - err = routePanelPluginsDeactivate(w,req,user,extraData) + err = panel.PluginsDeactivate(w,req,user,extraData) case "/panel/plugins/install/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1250,7 +1250,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(47) - err = routePanelPluginsInstall(w,req,user,extraData) + err = panel.PluginsInstall(w,req,user,extraData) case "/panel/users/": counters.RouteViewCounter.Bump(48) err = panel.Users(w,req,user) diff --git a/misc_test.go b/misc_test.go index cd495510..9dcc0483 100644 --- a/misc_test.go +++ b/misc_test.go @@ -828,6 +828,114 @@ func TestProfileReplyStore(t *testing.T) { // 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) { var res string var msgList []MEPair diff --git a/panel_routes.go b/panel_routes.go index 5cd1c0ba..b3bf9294 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -300,185 +300,6 @@ func routePanelWordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, u 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 { header, stats, ferr := common.PanelUserCheck(w, r, &user) if ferr != nil { diff --git a/query_gen/lib/acc_builders.go b/query_gen/lib/acc_builders.go index 59a1c9fd..0431b1bf 100644 --- a/query_gen/lib/acc_builders.go +++ b/query_gen/lib/acc_builders.go @@ -139,6 +139,15 @@ func (selectItem *AccSelectBuilder) Prepare() *sql.Stmt { 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) { stmt := selectItem.Prepare() 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 -func (selectItem *AccSelectBuilder) QueryRow(args ...interface{}) *AccRowWrap { - stmt := selectItem.Prepare() +func (builder *AccSelectBuilder) QueryRow(args ...interface{}) *AccRowWrap { + stmt := builder.Prepare() if stmt != nil { return &AccRowWrap{stmt.QueryRow(args...), nil} } - return &AccRowWrap{nil, selectItem.build.FirstError()} + return &AccRowWrap{nil, builder.build.FirstError()} } // Experimental, reduces lines -func (selectItem *AccSelectBuilder) Each(handle func(*sql.Rows) error) error { - rows, err := selectItem.Query() +func (builder *AccSelectBuilder) Each(handle func(*sql.Rows) error) error { + query, err := builder.query() + if err != nil { + return err + } + rows, err := builder.build.query(query) if err != nil { return err } @@ -184,8 +197,12 @@ func (selectItem *AccSelectBuilder) Each(handle func(*sql.Rows) error) error { } return rows.Err() } -func (selectItem *AccSelectBuilder) EachInt(handle func(int) error) error { - rows, err := selectItem.Query() +func (builder *AccSelectBuilder) EachInt(handle func(int) error) error { + query, err := builder.query() + if err != nil { + return err + } + rows, err := builder.build.query(query) if err != nil { return err } @@ -227,21 +244,20 @@ func (insert *accInsertBuilder) Prepare() *sql.Stmt { return insert.build.SimpleInsert(insert.table, insert.columns, insert.fields) } -func (insert *accInsertBuilder) Exec(args ...interface{}) (res sql.Result, err error) { - stmt := insert.Prepare() - if stmt != nil { - return stmt.Exec(args...) +func (builder *accInsertBuilder) Exec(args ...interface{}) (res sql.Result, err error) { + query, err := builder.build.adapter.SimpleInsert("_builder", builder.table, builder.columns, builder.fields) + if err != nil { + return res, err } - return res, insert.build.FirstError() + return builder.build.exec(query, args...) } func (builder *accInsertBuilder) Run(args ...interface{}) (int, error) { - stmt := builder.Prepare() - if stmt == nil { - return 0, builder.build.FirstError() + query, err := builder.build.adapter.SimpleInsert("_builder", builder.table, builder.columns, builder.fields) + if err != nil { + return 0, err } - - res, err := stmt.Exec(args...) + res, err := builder.build.exec(query, args...) if err != nil { return 0, err } diff --git a/query_gen/lib/accumulator.go b/query_gen/lib/accumulator.go index 787b9e42..e55b40c4 100644 --- a/query_gen/lib/accumulator.go +++ b/query_gen/lib/accumulator.go @@ -58,6 +58,22 @@ func (build *Accumulator) prepare(res string, err error) *sql.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) { tx, err := build.conn.Begin() if err != nil { diff --git a/query_gen/main.go b/query_gen/main.go index 17810e8c..7bcf892f 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -256,8 +256,6 @@ func writeSelects(adapter qgen.Adapter) error { // 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("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("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse() - build.Insert("addTheme").Table("themes").Columns("uname, default").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 { 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("updateGroupPerms").Table("users_groups").Set("permissions = ?").Where("gid = ?").Parse() diff --git a/router_gen/routes.go b/router_gen/routes.go index 7c7f63d9..ade75fbf 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -177,10 +177,10 @@ func buildPanelRoutes() { Action("routePanelThemesMenuItemDeleteSubmit", "/panel/themes/menus/item/delete/submit/", "extraData"), Action("routePanelThemesMenuItemOrderSubmit", "/panel/themes/menus/item/order/edit/submit/", "extraData"), - View("routePanelPlugins", "/panel/plugins/"), - Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extraData"), - Action("routePanelPluginsDeactivate", "/panel/plugins/deactivate/", "extraData"), - Action("routePanelPluginsInstall", "/panel/plugins/install/", "extraData"), + View("panel.Plugins", "/panel/plugins/"), + Action("panel.PluginsActivate", "/panel/plugins/activate/", "extraData"), + Action("panel.PluginsDeactivate", "/panel/plugins/deactivate/", "extraData"), + Action("panel.PluginsInstall", "/panel/plugins/install/", "extraData"), View("panel.Users", "/panel/users/"), View("panel.UsersEdit", "/panel/users/edit/", "extraData"), diff --git a/routes/panel/plugins.go b/routes/panel/plugins.go new file mode 100644 index 00000000..7986a63e --- /dev/null +++ b/routes/panel/plugins.go @@ -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 +} diff --git a/templates/panel_plugins.html b/templates/panel_plugins.html index b76e8d65..1cb92dcc 100644 --- a/templates/panel_plugins.html +++ b/templates/panel_plugins.html @@ -11,7 +11,7 @@
{{.Name}}
{{lang "panel_plugins_author_prefix"}}{{.Author}} - + {{if .Settings}}{{lang "panel_plugins_settings"}}{{end}} {{if .Active}}{{lang "panel_plugins_deactivate"}} {{else if .Installable}}