diff --git a/include/class.forms.php b/include/class.forms.php
index 35121204b94e991dcb25764a49a2fd2377bbace7..67b05e16ddf8aaae0f6e37056b2cade65b5595db 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -87,6 +87,7 @@ class Form {
                 $this->_clean[$key] = $this->_clean[$field->get('name')]
                     = $field->getClean();
             }
+            unset($this->_clean[""]);
         }
         return $this->_clean;
     }
diff --git a/include/class.plugin.php b/include/class.plugin.php
index 76ad2d24c5aecc6b16e72310b632573e5883695e..74fddc0766906a4f4966f573f06311453e4cee03 100644
--- a/include/class.plugin.php
+++ b/include/class.plugin.php
@@ -50,22 +50,24 @@ class PluginConfig extends Config {
      */
     function commit(&$errors=array()) {
         $f = $this->getForm();
+        $commit = false;
         if ($f->isValid()) {
             $config = $f->getClean();
-            $this->pre_save($config, $errors);
+            $commit = $this->pre_save($config, $errors);
         }
         $errors += $f->errors();
-        if (count($errors) === 0)
+        if ($commit && count($errors) === 0)
             return $this->updateAll($config);
         return false;
     }
 
     /**
      * Pre-save hook to check configuration for errors (other than obvious
-     * validation errors) prior to saving
+     * validation errors) prior to saving. Add an error to the errors list
+     * or return boolean FALSE if the config commit should be aborted.
      */
     function pre_save($config, &$errors) {
-        return;
+        return true;
     }
 
     /**
@@ -81,6 +83,7 @@ class PluginConfig extends Config {
 
 class PluginManager {
     static private $plugin_info = array();
+    static private $plugin_list = array();
 
     /**
      * boostrap
@@ -104,14 +107,12 @@ class PluginManager {
      * and active plugins
      */
     static function allInstalled() {
-        static $plugins = null;
-        if ($plugins !== null)
-            return $plugins;
+        if (static::$plugin_list)
+            return static::$plugin_list;
 
-        $plugins = array();
         $sql = 'SELECT * FROM '.PLUGIN_TABLE;
         if (!($res = db_query($sql)))
-            return $plugins;
+            return static::$plugin_list;
 
         $infos = static::allInfos();
         while ($ht = db_fetch_array($res)) {
@@ -121,15 +122,20 @@ class PluginManager {
                 $info = $infos[$ht['install_path']];
                 if ($ht['isactive']) {
                     list($path, $class) = explode(':', $info['plugin']);
-                    require_once(INCLUDE_DIR . '/' . $ht['install_path'] . '/' . $path);
-                    $plugins[$ht['install_path']] = new $class($ht['id']);
+                    if (!$class)
+                        $class = $path;
+                    else
+                        require_once(INCLUDE_DIR . $ht['install_path']
+                            . '/' . $path);
+                    static::$plugin_list[$ht['install_path']]
+                        = new $class($ht['id']);
                 }
                 else {
-                    $plugins[$ht['install_path']] = $ht;
+                    static::$plugin_list[$ht['install_path']] = $ht;
                 }
             }
         }
-        return $plugins;
+        return static::$plugin_list;
     }
 
     static function allActive() {
@@ -193,7 +199,10 @@ class PluginManager {
                 return $ht;
             // Usually this happens when the plugin is being enabled
             list($path, $class) = explode(':', $info['plugin']);
-            require_once(INCLUDE_DIR . $info['install_path'] . '/' . $path);
+            if (!$class)
+                $class = $path;
+            else
+                require_once(INCLUDE_DIR . $info['install_path'] . '/' . $path);
             $instances[$path] = new $class($ht['id']);
         }
         return $instances[$path];
@@ -209,10 +218,14 @@ class PluginManager {
         if (!($info = $this->getInfoForPath($path)))
             return false;
 
-        $sql='INSERT INTO '.PLUGIN_TABLE.'SET installed=NOW() '
+        $sql='INSERT INTO '.PLUGIN_TABLE.' SET installed=NOW() '
             .', install_path='.db_input($path)
             .', name='.db_input($info['name']);
-        return (db_query($sql) && db_affected_rows());
+        if (!db_query($sql) || !db_affected_rows())
+            return false;
+
+        static::$plugin_list = array();
+        return true;
     }
 }
 
@@ -265,6 +278,9 @@ class Plugin {
      * reinstalled, it will have to be reconfigured.
      */
     function uninstall() {
+        if ($this->pre_uninstall() === false)
+            return false;
+
         $sql = 'DELETE FROM '.PLUGIN_TABLE
             .' WHERE id='.db_input($this->getId());
         if (db_query($sql) && db_affected_rows())
@@ -272,6 +288,17 @@ class Plugin {
         return false;
     }
 
+    /**
+     * pre_uninstall
+     *
+     * Hook function to veto the uninstallation request. Return boolean
+     * FALSE if the uninstall operation should be aborted.
+     * TODO: Recommend a location to stash an error message if aborting
+     */
+    function pre_uninstall() {
+        return true;
+    }
+
     function enable() {
         $sql = 'UPDATE '.PLUGIN_TABLE
             .' SET isactive=1 WHERE id='.db_input($this->getId());
diff --git a/include/staff/plugin-add.inc.php b/include/staff/plugin-add.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..46251f66d1c2269e8c2417ed0e238613d1e1f8e6
--- /dev/null
+++ b/include/staff/plugin-add.inc.php
@@ -0,0 +1,35 @@
+
+<h2>Install a new plugin</h2>
+<p>
+To add a plugin into the system, download and place the plugin into the
+<code>include/plugins</code> folder. Once in the plugin is in the
+<code>plugins/</code> folder, it will be shown in the list below.
+</p>
+
+<form method="post" action="?">
+    <?php echo csrf_token(); ?>
+    <input type="hidden" name="do" value="install"/>
+<table class="list" width="100%"><tbody>
+<?php
+
+$installed = $ost->plugins->allInstalled();
+foreach ($ost->plugins->allInfos() as $info) {
+    // Ignore installed plugins
+    if (isset($installed[$info['install_path']]))
+        continue;
+    ?>
+        <tr><td><button type="submit" name="install_path"
+            value="<?php echo $info['install_path'];
+            ?>">Install</button></td>
+        <td>
+    <div><strong><?php echo $info['name']; ?></strong><br/>
+        <div><?php echo $info['description']; ?></div>
+        <div class="faded"><em>Version: <?php echo $info['version']; ?></em></div>
+        <div class="faded"><em>Author: <?php echo $info['author']; ?></em></div>
+    </div>
+    </td></tr>
+    <?php
+}
+?>
+</tbody></table>
+</form>
diff --git a/include/staff/plugin.inc.php b/include/staff/plugin.inc.php
index f57b12b93a7f86d969231f20bb88ae7819990311..89c056d006dec8d368208b6f14658c0c29af09f0 100644
--- a/include/staff/plugin.inc.php
+++ b/include/staff/plugin.inc.php
@@ -9,12 +9,6 @@ if($plugin && $_REQUEST['a']!='add') {
     $action = 'update';
     $submit_text='Save Changes';
     $info = $plugin->ht;
-    $newcount=2;
-} else {
-    $title = 'Install plugin';
-    $action = 'add';
-    $submit_text='Install';
-    $newcount=4;
 }
 $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 ?>
diff --git a/scp/plugins.php b/scp/plugins.php
index 7fdcb12aec33022ea9641895c59b3e59bbc1e5cd..c5e7e8918027296970b717289626fb4c5982d6ac 100644
--- a/scp/plugins.php
+++ b/scp/plugins.php
@@ -31,14 +31,30 @@ if($_POST) {
                         $p->disable();
                     }
                 }
+                break;
+            case 'delete':
+                foreach ($_POST['ids'] as $id) {
+                    if ($p = Plugin::lookup($id)) {
+                        var_dump($p);
+                        $p->uninstall();
+                    }
+                }
+                break;
             }
         }
+        break;
+    case 'install':
+        if ($ost->plugins->install($_POST['install_path']))
+            $msg = 'Plugin successfully installed';
+        break;
     }
 }
 
 $page = 'plugins.inc.php';
 if ($plugin)
     $page = 'plugin.inc.php';
+elseif ($_REQUEST['a']=='add')
+    $page = 'plugin-add.inc.php';
 
 $nav->setTabActive('manage');
 require(STAFFINC_DIR.'header.inc.php');