diff --git a/inc/pluginutils.php b/inc/pluginutils.php
index 100f781f21c3114974d02d3d4cdee1698410dfe3..61a2939d3c1facf8affcc7688d39440c82a57068 100644
--- a/inc/pluginutils.php
+++ b/inc/pluginutils.php
@@ -8,93 +8,174 @@
 
 // plugin related constants
 if(!defined('DOKU_PLUGIN'))  define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+
 $plugin_types = array('admin','syntax','action','renderer', 'helper');
 
+global $plugin_controller_class, $plugin_controller;
+if (empty($plugin_controller_class)) $plugin_controller_class = 'Doku_Plugin_Controller';
+
+$plugin_controller = new $plugin_controller_class();
+
 /**
- * Returns a list of available plugins of given type
- *
- * @param $type  string, plugin_type name;
- *               the type of plugin to return,
- *               use empty string for all types
- * @param $all   bool;
- *               false to only return enabled plugins,
- *               true to return both enabled and disabled plugins
- *
- * @return       array of plugin names
- *
- * @author Andreas Gohr <andi@splitbrain.org>
+ * Original plugin functions, remain for backwards compatibility
  */
-function plugin_list($type='',$all=false){
-  $plugins = array();
-  if ($dh = opendir(DOKU_PLUGIN)) {
-    while (false !== ($plugin = readdir($dh))) {
-      if ($plugin == '.' || $plugin == '..' || $plugin == 'tmp') continue;
-      if (is_file(DOKU_PLUGIN.$plugin)) continue;
-
-      // if required, skip disabled plugins
-      if (!$all && plugin_isdisabled($plugin)) continue;
-
-      if ($type=='' || @file_exists(DOKU_PLUGIN."$plugin/$type.php")){
-          $plugins[] = $plugin;
+function plugin_list($type='',$all=false) { global $plugin_controller; return $plugin_controller->getList($type,$all); }
+function &plugin_load($type,$name,$new=false) { global $plugin_controller; return $plugin_controller->load($type,$name,$new); }
+function plugin_isdisabled($plugin) { global $plugin_controller; return $plugin_controller->isdisabled($plugin); }
+function plugin_enable($plugin) { global $plugin_controller; return $plugin_controller->enable($plugin); }
+function plugin_disable($plugin) { global $plugin_controller; return $plugin_controller->disable($plugin); }
+function plugin_directory($plugin) { global $plugin_controller; return $plugin_controller->get_directory($plugin); }
+
+class Doku_Plugin_Controller {
+
+  var $list_enabled = array();
+  var $list_disabled = array();
+  var $list_bytype = array();
+
+  function Doku_Plugin_Controller() {
+    $this->_populateMasterList();
+  }
+
+  /**
+   * Returns a list of available plugins of given type
+   *
+   * @param $type  string, plugin_type name;
+   *               the type of plugin to return,
+   *               use empty string for all types
+   * @param $all   bool;
+   *               false to only return enabled plugins,
+   *               true to return both enabled and disabled plugins
+   *
+   * @return       array of plugin names
+   *
+   * @author Andreas Gohr <andi@splitbrain.org>
+   */
+  function getList($type='',$all=false){
+
+    // request the complete list
+    if (!$type) {
+      return $all ? array_merge($this->list_enabled,$this->list_disabled) : $this->list_enabled;
+    }
+
+    if (!isset($this->list_bytype[$type]['enabled'])) {
+      $this->list_bytype[$type]['enabled'] = $this->_getListByType($type,true);
+    }
+    if ($all && !isset($this->list_bytype[$type]['disabled'])) {
+      $this->list_bytype[$type]['disabled'] = $this->_getListByType($type,false);
+    }
+
+    return $all ? array_merge($this->list_bytype[$type]['enabled'],$this->list_bytype[$type]['disabled']) : $this->list_bytype[$type]['enabled'];
+  }
+
+  /**
+   * Loads the given plugin and creates an object of it
+   *
+   * @author Andreas Gohr <andi@splitbrain.org>
+   *
+   * @param  $type string     type of plugin to load
+   * @param  $name string     name of the plugin to load
+   * @param  $new  bool       true to return a new instance of the plugin, false to use an already loaded instance
+   * @return objectreference  the plugin object or null on failure
+   */
+  function &load($type,$name,$new=false){
+    //we keep all loaded plugins available in global scope for reuse
+    global $DOKU_PLUGINS;
+
+    //plugin already loaded?
+    if(!empty($DOKU_PLUGINS[$type][$name])){
+      if ($new) {
+        $class = $type.'_plugin_'.$name;
+        return class_exists($class) ? new $class : null;
+      } else {
+        return $DOKU_PLUGINS[$type][$name];
+      }
+    }
+
+    //try to load the wanted plugin file
+    list($plugin,$component) = $this->_splitName($name);
+    $dir = !$this->isdisabled($plugin) ? $plugin : $plugin.'.disabled';
+    $file = $component ? "$type/$component.php" : "$type.php";
+
+    if (!include_once(DOKU_PLUGIN."$dir/$file")) {
+      return null;
+    }
+
+    //construct class and instantiate
+    $class = $type.'_plugin_'.$name;
+    if (!class_exists($class)) return null;
+
+    $DOKU_PLUGINS[$type][$name] = new $class;
+    return $DOKU_PLUGINS[$type][$name];
+  }
+
+  function isdisabled($plugin) {
+    return (array_search($plugin, $this->list_enabled) === false);
+  }
+
+  function enable($plugin) {
+    if (array_search($plugin, $this->list_disabled) !== false) {
+      return @rename(DOKU_PLUGIN.$plugin.'.disabled',DOKU_PLUGIN.$plugin);
+    }
+    return false;
+  }
+
+  function disable($plugin) {
+   if (array_search($plugin, $this->list_enabled) !== false) {
+      return @rename(DOKU_PLUGIN.$plugin,DOKU_PLUGIN.$plugin.'.disabled');
+    }
+    return false;
+  }
+
+  function get_directory($plugin) {
+    return $this->isdisabled($plugin) ? $plugin.'.disabled' : $plugin;
+  }
+
+  function _populateMasterList() {
+    if ($dh = opendir(DOKU_PLUGIN)) {
+      while (false !== ($plugin = readdir($dh))) {
+        if ($plugin == '.' || $plugin == '..' || $plugin == 'tmp') continue;
+        if (is_file(DOKU_PLUGIN.$plugin)) continue;
+
+        if (substr($plugin,-9) == '.disabled') {
+          $this->list_disabled[] = substr($plugin,0,-9);
+        } else {
+          $this->list_enabled[] = $plugin;
+        }
+      }
+    }
+  }
+
+  function _getListByType($type, $enabled) {
+    $master_list = $enabled ? $this->list_enabled : $this->list_disabled;
+
+    $plugins = array();
+    foreach ($master_list as $plugin) {
+      $dir = $enabled ? $plugin : $plugin.'.disabled';
+
+      if (@file_exists(DOKU_PLUGIN."$dir/$type.php")){
+        $plugins[] = $plugin;
       } else {
-        if ($dp = @opendir(DOKU_PLUGIN."$plugin/$type/")) {
+        if ($dp = @opendir(DOKU_PLUGIN."$dir/$type/")) {
           while (false !== ($component = readdir($dp))) {
             if (substr($component,0,1) == '.' || strtolower(substr($component, -4)) != ".php") continue;
-            if (is_file(DOKU_PLUGIN."$plugin/$type/$component")) {
-              $plugins[] = $plugin.'_'.substr($component, 0, -4);
+            if (is_file(DOKU_PLUGIN."$dir/$type/$component")) {
+                $plugins[] = $plugin.'_'.substr($component, 0, -4);
             }
           }
         closedir($dp);
         }
       }
     }
-    closedir($dh);
-  }
-  return $plugins;
-}
 
-/**
- * Loads the given plugin and creates an object of it
- *
- * @author Andreas Gohr <andi@splitbrain.org>
- *
- * @param  $type string     type of plugin to load
- * @param  $name string     name of the plugin to load
- * @param  $new  bool       true to return a new instance of the plugin, false to use an already loaded instance
- * @return objectreference  the plugin object or null on failure
- */
-function &plugin_load($type,$name,$new=false){
-  //we keep all loaded plugins available in global scope for reuse
-  global $DOKU_PLUGINS;
-
-  //plugin already loaded?
-  if(!empty($DOKU_PLUGINS[$type][$name])){
-    if ($new) {
-      $class = $type.'_plugin_'.$name;
-      return class_exists($class) ? new $class : null;
-    } else {
-      return $DOKU_PLUGINS[$type][$name];
-    }
+    return $plugins;
   }
 
-  //try to load the wanted plugin file
-  if (@file_exists(DOKU_PLUGIN."$name/$type.php")){
-    include_once(DOKU_PLUGIN."$name/$type.php");
-  }else{
-    list($plugin, $component) = preg_split("/_/",$name, 2);
-    if (!$component || !include_once(DOKU_PLUGIN."$plugin/$type/$component.php")) {
-        return null;
+  function _splitName($name) {
+    if (array_search($name, $this->list_enabled + $this->list_disabled) === false) {
+      return explode('_',$name,2);
     }
-  }
 
-  //construct class and instantiate
-  $class = $type.'_plugin_'.$name;
-  if (!class_exists($class)) return null;
+    return array($name,'');
+  }
 
-  $DOKU_PLUGINS[$type][$name] = new $class;
-  return $DOKU_PLUGINS[$type][$name];
 }
-
-function plugin_isdisabled($name) { return @file_exists(DOKU_PLUGIN.$name.'/disabled'); }
-function plugin_enable($name) { return @unlink(DOKU_PLUGIN.$name.'/disabled'); }
-function plugin_disable($name) { return @touch(DOKU_PLUGIN.$name.'/disabled'); }
diff --git a/lib/plugins/config/settings/config.class.php b/lib/plugins/config/settings/config.class.php
index 8610f2c815136627178b8969acfdc0d9053a98f0..c3531d6f343018613af896d220bed177cc3bd607 100644
--- a/lib/plugins/config/settings/config.class.php
+++ b/lib/plugins/config/settings/config.class.php
@@ -237,6 +237,21 @@ if (!class_exists('configuration')) {
         return $out;
     }
 
+    function get_plugin_list() {
+      if (is_null($this->_plugin_list)) {
+        $list = plugin_list('',true);     // all plugins, including disabled ones
+
+        // remove this plugin from the list
+        $idx = array_search('config',$list);
+        unset($list[$idx]);
+
+        trigger_event('PLUGIN_CONFIG_PLUGINLIST',$list);
+        $this->_plugin_list = $list;
+      }
+
+      return $this->_plugin_list;
+    }
+
     /**
      * load metadata for plugin and template settings
      */
@@ -245,25 +260,20 @@ if (!class_exists('configuration')) {
       $class    = '/conf/settings.class.php';
       $metadata = array();
 
-      if ($dh = opendir(DOKU_PLUGIN)) {
-        while (false !== ($plugin = readdir($dh))) {
-          if ($plugin == '.' || $plugin == '..' || $plugin == 'tmp' || $plugin == 'config') continue;
-          if (is_file(DOKU_PLUGIN.$plugin)) continue;
-
-          if (@file_exists(DOKU_PLUGIN.$plugin.$file)){
-            $meta = array();
-            @include(DOKU_PLUGIN.$plugin.$file);
-            @include(DOKU_PLUGIN.$plugin.$class);
-            if (!empty($meta)) {
-              $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.'plugin_settings_name'] = array('fieldset');
-            }
-            foreach ($meta as $key => $value){
-              if ($value[0]=='fieldset') { continue; } //plugins only get one fieldset
-              $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value;
-            }
+      foreach ($this->get_plugin_list() as $plugin) {
+        $plugin_dir = plugin_directory($plugin);
+        if (@file_exists(DOKU_PLUGIN.$plugin_dir.$file)){
+          $meta = array();
+          @include(DOKU_PLUGIN.$plugin_dir.$file);
+          @include(DOKU_PLUGIN.$plugin_dir.$class);
+          if (!empty($meta)) {
+             $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.'plugin_settings_name'] = array('fieldset');
+          }
+          foreach ($meta as $key => $value){
+            if ($value[0]=='fieldset') { continue; } //plugins only get one fieldset
+            $metadata['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value;
           }
         }
-        closedir($dh);
       }
 
       // the same for the active template
@@ -290,17 +300,15 @@ if (!class_exists('configuration')) {
       $file    = '/conf/default.php';
       $default = array();
 
-      if ($dh = opendir(DOKU_PLUGIN)) {
-        while (false !== ($plugin = readdir($dh))) {
-          if (@file_exists(DOKU_PLUGIN.$plugin.$file)){
-            $conf = array();
-            @include(DOKU_PLUGIN.$plugin.$file);
-            foreach ($conf as $key => $value){
-              $default['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value;
-            }
+      foreach ($this->get_plugin_list() as $plugin) {
+        $plugin_dir = plugin_directory($plugin);
+        if (@file_exists(DOKU_PLUGIN.$plugin_dir.$file)){
+          $conf = array();
+          @include(DOKU_PLUGIN.$plugin_dir.$file);
+          foreach ($conf as $key => $value){
+            $default['plugin'.CM_KEYMARKER.$plugin.CM_KEYMARKER.$key] = $value;
           }
         }
-        closedir($dh);
       }
 
       // the same for the active template
diff --git a/lib/plugins/plugin/admin.php b/lib/plugins/plugin/admin.php
index a5c906a0b45562ee598d14acada929be3c5af079..237fe11b85fe6f1e58775123eafe8023ebf63f8d 100644
--- a/lib/plugins/plugin/admin.php
+++ b/lib/plugins/plugin/admin.php
@@ -105,8 +105,7 @@ class admin_plugin_plugin extends DokuWiki_Admin_Plugin {
           $this->plugin = null;
       }
 
-      $this->plugin_list = plugin_list('', true);
-      sort($this->plugin_list);
+      $this->_get_plugin_list();
 
       // verify $_REQUEST vars
       if (in_array($this->cmd, $this->commands)) {
@@ -127,6 +126,7 @@ class admin_plugin_plugin extends DokuWiki_Admin_Plugin {
 
       $this->handler = & new $class($this, $this->plugin);
       $this->msg = $this->handler->process();
+
     }
 
     /**
@@ -138,18 +138,25 @@ class admin_plugin_plugin extends DokuWiki_Admin_Plugin {
 
       // enable direct access to language strings
       $this->setupLocale();
+      $this->_get_plugin_list();
 
       if ($this->handler === NULL) $this->handler = & new ap_manage($this, $this->plugin);
-      if (!$this->plugin_list) {
-        $this->plugin_list = plugin_list('',true);
-        sort($this->plugin_list);
-      }
 
       ptln('<div id="plugin__manager">');
       $this->handler->html();
       ptln('</div><!-- #plugin_manager -->');
     }
 
+    function _get_plugin_list() {
+      if (empty($this->plugin_list)) {
+        $list = plugin_list('',true);     // all plugins, including disabled ones
+        sort($list);
+        trigger_event('PLUGIN_PLUGINMANAGER_PLUGINLIST',$list);
+        $this->plugin_list = $list;
+      }
+      return $this->plugin_list;
+    }
+
 }
 
 class ap_manage {
@@ -266,16 +273,23 @@ class ap_manage {
          *  Refresh plugin list
          */
         function refresh() {
-
-            $this->manager->plugin_list = plugin_list('',true);
-            sort($this->manager->plugin_list);
+            global $MSG;
+
+            //are there any undisplayed messages? keep them in session for display
+            if (isset($MSG) && count($MSG)){
+                //reopen session, store data and close session again
+                @session_start();
+                $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
+                session_write_close();
+            }
 
             // expire dokuwiki caches
             // touching local.php expires wiki page, JS and CSS caches
             @touch(DOKU_CONF.'local.php');
 
             // update latest plugin date - FIXME
-            return (!$this->manager->error);
+            header('Location: '.wl($ID).'?do=admin&page=plugin');
+            exit();
         }
 
         function download($url, $overwrite=false) {
@@ -336,6 +350,7 @@ class ap_manage {
           if ($tmp) ap_delete($tmp);
 
           if (!$this->manager->error) {
+              msg('Plugin package ('.count($this->downloaded).' plugin'.(count($this->downloaded) != 1?'s':'').': '.join(',',$this->downloaded).') successfully installed.',1);
               $this->refresh();
               return true;
           }
@@ -368,7 +383,7 @@ class ap_manage {
 
         function plugin_readlog($plugin, $field) {
             static $log = array();
-            $file = DOKU_PLUGIN.$plugin.'/manager.dat';
+            $file = DOKU_PLUGIN.plugin_directory($plugin).'/manager.dat';
 
             if (!isset($log[$plugin])) {
                 $tmp = @file_get_contents($file);
@@ -380,7 +395,7 @@ class ap_manage {
                 return $log[$plugin];
             }
 
-                        $match = array();
+            $match = array();
             if (preg_match_all('/'.$field.'=(.*)$/m',$log[$plugin], $match))
                 return implode("\n", $match[1]);
 
@@ -429,9 +444,10 @@ class ap_manage {
 
         function process() {
 
-            if (!ap_delete(DOKU_PLUGIN.$this->manager->plugin)) {
+            if (!ap_delete(DOKU_PLUGIN.plugin_directory($this->manager->plugin))) {
               $this->manager->error = sprintf($this->lang['error_delete'],$this->manager->plugin);
             } else {
+              msg("Plugin {$this->manager->plugin} successfully deleted.");
               $this->refresh();
             }
         }
@@ -589,6 +605,7 @@ class ap_manage {
 
       function process() {
         global $plugin_protected;
+        $count_enabled = $count_disabled = 0;
 
         $this->enabled = isset($_REQUEST['enabled']) ? $_REQUEST['enabled'] : array();
 
@@ -601,14 +618,23 @@ class ap_manage {
           if ($new != $old) {
             switch ($new) {
               // enable plugin
-              case true : plugin_enable($plugin); break;
-              case false: plugin_disable($plugin); break;
+              case true :
+                plugin_enable($plugin);
+                $count_enabled++;
+                break;
+              case false:
+                plugin_disable($plugin);
+                $count_disabled++;
+                break;
             }
           }
         }
 
         // refresh plugins, including expiring any dokuwiki cache(s)
-        $this->refresh();
+        if ($count_enabled || $count_disabled) {
+          msg("Plugin state saved, $count_enabled plugins enabled, $count_disabled plugins disabled.");
+          $this->refresh();
+        }
       }
 
     }
@@ -732,7 +758,7 @@ class ap_manage {
       global $plugin_types;
 
       $components = array();
-      $path = DOKU_PLUGIN.$plugin.'/';
+      $path = DOKU_PLUGIN.plugin_directory($plugin).'/';
 
       foreach ($plugin_types as $type) {
         if (@file_exists($path.$type.'.php')) { $components[] = array('name'=>$plugin, 'type'=>$type); continue; }
diff --git a/lib/plugins/upgradeplugindirectory/action.php b/lib/plugins/upgradeplugindirectory/action.php
new file mode 100644
index 0000000000000000000000000000000000000000..9461299df3f600156c7159c07f6286c6cd71c236
--- /dev/null
+++ b/lib/plugins/upgradeplugindirectory/action.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Action Plugin:
+ *   Upgrades the plugin directory from the "old style" of disabling plugins to the new style
+ *
+ * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author     Christopher Smith <chris@jalakai.co.uk>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+require_once(DOKU_PLUGIN.'action.php');
+
+/**
+ * All DokuWiki plugins to extend the parser/rendering mechanism
+ * need to inherit from this class
+ */
+class action_plugin_upgradeplugindirectory extends DokuWiki_Action_Plugin {
+
+    /**
+     * return some info
+     */
+    function getInfo(){
+      return array(
+        'author' => 'Christopher Smith',
+        'email'  => 'chris@jalakai.co.uk',
+        'date'   => '2009-01-18',
+        'name'   => 'Upgrade Plugin Directory',
+        'desc'   => 'Silently updates plugin disabled indicator to new more efficient format',
+        'url'    => 'http://wiki.splitbrain.org/plugin:upgradeplugindirectory',
+      );
+    }
+
+    /*
+     * plugin should use this method to register its handlers with the dokuwiki's event controller
+     */
+    function register(&$controller) {
+      $controller->register_hook('DOKUWIKI_STARTED','BEFORE', $this, 'handle_upgrade','before');
+    }
+
+    function handle_upgrade(&$event, $param) {
+      global $plugin_controller;
+      $attempts = 0;
+      $success = 0;
+      $updated = 0;
+      $failures = array();
+      $badclean = array();
+
+      if (empty($plugin_controller)) return;
+
+      $plugins = $plugin_controller->getList('',true);    // get all plugins
+      foreach ($plugins as $plugin) {
+      	if ($this->plugin_isdisabled_oldstyle($plugin)) {
+      	  $attempts++;
+      	  if (@$plugin_controller->disable($plugin)) {
+      	  	$updated++;
+      	  	if ($this->plugin_clean($plugin)) {
+      	  	  $success++;
+      	  	} else {
+      	  	  $badclean[] = $plugin;
+      	  	}
+      	  } else {
+      	  	$failures[] = $plugin;
+      	  }
+      	}
+      }
+
+      if ($attempts && auth_isAdmin()) {
+      	$level = $failures ? -1 : ($badclean ? 2 : 1);
+      	msg("Plugin Directory Upgrade, $updated/$attempts plugins updated, $success/$attempts cleaned.",$level);
+      	if ($badclean) msg("- the following disabled plugins were updated, but their directories couldn't be cleaned: ".join(',',$badclean),$level);
+      	if ($failures) {
+      	  msg("- the following disabled plugins couldn't be updated, please update by hand: ".join(',',$failures),$level);
+        }
+        msg("For more information see http://www.dokuwiki.org/update",$level);
+      }
+
+      // no failures, our job is done, disable ourself
+      if (!$failures) {
+        $plugin_controller->disable($this->getPluginName());
+        // redirect to let dokuwiki start cleanly with plugins disabled.
+        act_redirect($ID,'upgradeplugindirectory');
+      }
+
+    }
+
+    /* old style plugin isdisabled function */
+    function plugin_isdisabled_oldstyle($name) {
+      return @file_exists(DOKU_PLUGIN.$name.'/disabled');
+    }
+
+    function plugin_clean($name) {
+      return @unlink(DOKU_PLUGIN.$name.'.disabled/disabled');
+    }
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
\ No newline at end of file