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 (
"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()
})

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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 {

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)
}
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
}

View File

@ -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 {

View File

@ -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()

View File

@ -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"),

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">
<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>
<span style="float: right;">
<span class="to_right">
{{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>
{{else if .Installable}}