diff --git a/lib/plugins/plugin/admin.php b/lib/plugins/plugin/admin.php
index c0918c70f5424b4cc7987693d3b59d51aea058d2..bf0b0b88a4240bdfe3a45da0c38c4e2a0fef1b99 100644
--- a/lib/plugins/plugin/admin.php
+++ b/lib/plugins/plugin/admin.php
@@ -15,15 +15,16 @@ if(!defined('DOKU_INC')) die();
 
 if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
 require_once(DOKU_PLUGIN.'admin.php');
+require_once(DOKU_PLUGIN."/plugin/classes/ap_manage.class.php");
 
-    //--------------------------[ GLOBALS ]------------------------------------------------
-    // note: probably should be dokuwiki wide globals, where they can be accessed by pluginutils.php
-    // global $plugin_types;
-    // $plugin_types = array('syntax', 'admin');
+//--------------------------[ GLOBALS ]------------------------------------------------
+// note: probably should be dokuwiki wide globals, where they can be accessed by pluginutils.php
+// global $plugin_types;
+// $plugin_types = array('syntax', 'admin');
 
-    // plugins that are an integral part of dokuwiki, they shouldn't be disabled or deleted
-    global $plugin_protected;
-    $plugin_protected = array('acl','plugin','config','info','usermanager','revert');
+// plugins that are an integral part of dokuwiki, they shouldn't be disabled or deleted
+global $plugin_protected;
+$plugin_protected = array('acl','plugin','config','info','usermanager','revert');
 
 /**
  * All DokuWiki plugins to extend the admin function
@@ -44,88 +45,73 @@ class admin_plugin_plugin extends DokuWiki_Admin_Plugin {
     var $error = '';
 
     function admin_plugin_plugin() {
-      global $conf;
-      $this->disabled = (isset($conf['pluginmanager']) && ($conf['pluginmanager'] == 0));
+        global $conf;
+        $this->disabled = (isset($conf['pluginmanager']) && ($conf['pluginmanager'] == 0));
     }
 
     /**
      * return some info
      */
     function getInfo(){
-      $disabled = ($this->disabled) ? '(disabled)' : '';
-
-      return array(
-        'author' => 'Christopher Smith',
-        'email'  => 'chris@jalakai.co.uk',
-        'date'   => '2005-08-10',
-        'name'   => 'Plugin Manager',
-        'desc'   => "Manage Plugins, including automated plugin installer $disabled",
-        'url'    => 'http://dokuwiki.org/plugin:plugin',
-      );
-    }
-
-    /**
-     * return prompt for admin menu
-     */
-    function getMenuText($language) {
-
-        if (!$this->disabled)
-          return parent::getMenuText($language);
+        $disabled = ($this->disabled) ? '(disabled)' : '';
 
-        return '';
+        return array(
+                'author' => 'Christopher Smith',
+                'email'  => 'chris@jalakai.co.uk',
+                'date'   => '2009-07-25',
+                'name'   => 'Plugin Manager',
+                'desc'   => "Manage Plugins, including automated plugin installer $disabled",
+                'url'    => 'http://dokuwiki.org/plugin:plugin',
+                );
     }
 
     /**
      * return sort order for position in admin menu
      */
     function getMenuSort() {
-      return 20;
+        return 20;
     }
 
     /**
      * handle user request
      */
     function handle() {
+        // enable direct access to language strings
+        $this->setupLocale();
 
-      if ($this->disabled) return;
-
-      // enable direct access to language strings
-      $this->setupLocale();
-
-//      $this->plugin = $_REQUEST['plugin'];
-//      $this->cmd = $_REQUEST['fn'];
-//      if (is_array($this->cmd)) $this->cmd = key($this->cmd);
-
-      $fn = $_REQUEST['fn'];
-      if (is_array($fn)) {
-          $this->cmd = key($fn);
-          $this->plugin = is_array($fn[$this->cmd]) ? key($fn[$this->cmd]) : null;
-      } else {
-          $this->cmd = $fn;
-          $this->plugin = null;
-      }
+        $fn = $_REQUEST['fn'];
+        if (is_array($fn)) {
+            $this->cmd = key($fn);
+            $this->plugin = is_array($fn[$this->cmd]) ? key($fn[$this->cmd]) : null;
+        } else {
+            $this->cmd = $fn;
+            $this->plugin = null;
+        }
 
-      $this->_get_plugin_list();
+        $this->_get_plugin_list();
 
-      // verify $_REQUEST vars
-      if (in_array($this->cmd, $this->commands)) {
-        $this->plugin = '';
-      } else if (!in_array($this->cmd, $this->functions) || !in_array($this->plugin, $this->plugin_list)) {
-        $this->cmd = 'manage';
-        $this->plugin = '';
-      }
+        // verify $_REQUEST vars
+        if (in_array($this->cmd, $this->commands)) {
+            $this->plugin = '';
+        } else if (!in_array($this->cmd, $this->functions) || !in_array($this->plugin, $this->plugin_list)) {
+            $this->cmd = 'manage';
+            $this->plugin = '';
+        }
 
-      if(($this->cmd != 'manage' || $this->plugin != '') && !checkSecurityToken()){
-        $this->cmd = 'manage';
-        $this->plugin = '';
-      }
+        if(($this->cmd != 'manage' || $this->plugin != '') && !checkSecurityToken()){
+            $this->cmd = 'manage';
+            $this->plugin = '';
+        }
 
-      // create object to handle the command
-      $class = "ap_".$this->cmd;
-      if (!class_exists($class)) $class = 'ap_manage';
+        // create object to handle the command
+        $class = "ap_".$this->cmd;
+        @require_once(DOKU_PLUGIN."/plugin/classes/$class.class.php");
+        if (!class_exists($class)){
+            $class = 'ap_manage';
+        }
 
-      $this->handler = & new $class($this, $this->plugin);
-      $this->msg = $this->handler->process();
+        $this->handler = & new $class($this, $this->plugin);
+        $this->msg = $this->handler->process();
 
     }
 
@@ -133,656 +119,34 @@ class admin_plugin_plugin extends DokuWiki_Admin_Plugin {
      * output appropriate html
      */
     function html() {
+        // enable direct access to language strings
+        $this->setupLocale();
+        $this->_get_plugin_list();
 
-      if ($this->disabled) return;
-
-      // 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->handler === NULL) $this->handler = & new ap_manage($this, $this->plugin);
-
-      ptln('<div id="plugin__manager">');
-      $this->handler->html();
-      ptln('</div><!-- #plugin_manager -->');
+        ptln('<div id="plugin__manager">');
+        $this->handler->html();
+        ptln('</div><!-- #plugin_manager -->');
     }
 
+    /**
+     * Returns a list of all plugins, including the disabled ones
+     */
     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 {
-
-        var $manager = NULL;
-        var $lang = array();
-        var $plugin = '';
-        var $downloaded = array();
-
-        function ap_manage(&$manager, $plugin) {
-            $this->manager = & $manager;
-            $this->plugin = $plugin;
-            $this->lang = & $manager->lang;
+        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;
         }
-
-        function process() {
-            return '';
-        }
-
-        function html() {
-          print $this->manager->locale_xhtml('admin_plugin');
-          $this->html_menu();
-        }
-
-        // build our standard menu
-        function html_menu($listPlugins = true) {
-          global $ID;
-
-          ptln('<div class="pm_menu">');
-
-          ptln('<div class="common">');
-          ptln('  <h2>'.$this->lang['download'].'</h2>');
-          ptln('  <form action="'.wl($ID).'" method="post">');
-          ptln('    <fieldset class="hidden">',4);
-          ptln('      <input type="hidden" name="do"   value="admin" />');
-          ptln('      <input type="hidden" name="page" value="plugin" />');
-          formSecurityToken();
-          ptln('    </fieldset>');
-          ptln('    <fieldset>');
-          ptln('      <legend>'.$this->lang['download'].'</legend>');
-          ptln('      <label for="dw__url">'.$this->lang['url'].'<input name="url" id="dw__url" class="edit" type="text" maxlength="200" /></label>');
-          ptln('      <input type="submit" class="button" name="fn[download]" value="'.$this->lang['btn_download'].'" />');
-          ptln('    </fieldset>');
-          ptln('  </form>');
-          ptln('</div>');
-
-          if ($listPlugins) {
-            ptln('<h2>'.$this->lang['manage'].'</h2>');
-
-            ptln('<form action="'.wl($ID).'" method="post" class="plugins">');
-//            ptln('  <div class="plugins">');
-
-            ptln('  <fieldset class="hidden">');
-            ptln('    <input type="hidden" name="do"     value="admin" />');
-            ptln('    <input type="hidden" name="page"   value="plugin" />');
-            formSecurityToken();
-            ptln('  </fieldset>');
-
-            $this->html_pluginlist();
-
-            ptln('  <fieldset class="buttons">');
-            ptln('    <input type="submit" class="button" name="fn[enable]" value="'.$this->lang['btn_enable'].'" />');
-            ptln('  </fieldset>');
-
-//            ptln('  </div>');
-            ptln('</form>');
-          }
-
-          ptln('</div>');
-        }
-
-        function html_pluginlist() {
-          global $ID;
-          global $plugin_protected;
-
-          foreach ($this->manager->plugin_list as $plugin) {
-
-            $disabled = plugin_isdisabled($plugin);
-            $protected = in_array($plugin,$plugin_protected);
-
-            $checked = ($disabled) ? '' : ' checked="checked"';
-            $check_disabled = ($protected) ? ' disabled="disabled"' : '';
-
-            // determine display class(es)
-            $class = array();
-            if (in_array($plugin, $this->downloaded)) $class[] = 'new';
-            if ($disabled) $class[] = 'disabled';
-            if ($protected) $class[] = 'protected';
-
-            $class = count($class) ? ' class="'.join(' ', $class).'"' : '';
-
-            ptln('    <fieldset'.$class.'>');
-            ptln('      <legend>'.$plugin.'</legend>');
-            ptln('      <input type="checkbox" class="enable" name="enabled[]" value="'.$plugin.'"'.$checked.$check_disabled.' />');
-            ptln('      <h3 class="legend">'.$plugin.'</h3>');
-
-            $this->html_button($plugin, 'info', false, 6);
-            if (in_array('settings', $this->manager->functions)) {
-              $this->html_button($plugin, 'settings', !@file_exists(DOKU_PLUGIN.$plugin.'/settings.php'), 6);
-            }
-            $this->html_button($plugin, 'update', !$this->plugin_readlog($plugin, 'url'), 6);
-            $this->html_button($plugin, 'delete', $protected, 6);
-
-            ptln('    </fieldset>');
-            }
-        }
-
-        function html_button($plugin, $btn, $disabled=false, $indent=0) {
-            $disabled = ($disabled) ? 'disabled="disabled"' : '';
-            ptln('<input type="submit" class="button" '.$disabled.' name="fn['.$btn.']['.$plugin.']" value="'.$this->lang['btn_'.$btn].'" />',$indent);
-        }
-
-        /**
-         *  Refresh plugin list
-         */
-        function refresh() {
-            global $MSG,$config_cascade;
-
-            //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(reset($config_cascade['main']['local']));
-
-            // update latest plugin date - FIXME
-            header('Location: '.wl($ID).'?do=admin&page=plugin');
-            exit();
-        }
-
-        function download($url, $overwrite=false) {
-          global $lang;
-
-          // check the url
-          $matches = array();
-          if (!preg_match("/[^\/]*$/", $url, $matches) || !$matches[0]) {
-            $this->manager->error = $this->lang['error_badurl']."\n";
-            return false;
-          }
-
-          $file = $matches[0];
-
-          if (!($tmp = io_mktmpdir())) {
-            $this->manager->error = $this->lang['error_dircreate']."\n";
-            return false;
-          }
-
-          if (!$file = io_download($url, "$tmp/", true, $file)) {
-            $this->manager->error = sprintf($this->lang['error_download'],$url)."\n";
-          }
-
-          if (!$this->manager->error && !ap_decompress("$tmp/$file", $tmp)) {
-            $this->manager->error = sprintf($this->lang['error_decompress'],$file)."\n";
-          }
-
-          // search $tmp for the folder(s) that has been created
-          // move the folder(s) to lib/plugins/
-          if (!$this->manager->error) {
-            if ($dh = @opendir("$tmp/")) {
-              while (false !== ($f = readdir($dh))) {
-                if ($f == '.' || $f == '..' || $f == 'tmp') continue;
-                if (!is_dir("$tmp/$f")) continue;
-
-                // check to make sure we aren't overwriting anything
-                if (!$overwrite && @file_exists(DOKU_PLUGIN.$f)) {
-                   // remember our settings, ask the user to confirm overwrite, FIXME
-                   continue;
-                }
-
-                $instruction = @file_exists(DOKU_PLUGIN.$f) ? 'update' : 'install';
-
-                if (ap_copy("$tmp/$f", DOKU_PLUGIN.$f)) {
-                  $this->downloaded[] = $f;
-                  $this->plugin_writelog($f, $instruction, array($url));
-                } else {
-                  $this->manager->error .= sprintf($this->lang['error_copy']."\n", $f);
-                }
-              }
-              closedir($dh);
-            } else {
-              $this->manager->error = $this->lang['error']."\n";
-            }
-          }
-
-          // cleanup
-          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;
-          }
-
-          return false;
-        }
-
-        // log
-        function plugin_writelog($plugin, $cmd, $data) {
-
-            $file = DOKU_PLUGIN.$plugin.'/manager.dat';
-
-            switch ($cmd) {
-              case 'install' :
-                $url = $data[0];
-                $date = date('r');
-                if (!$fp = @fopen($file, 'w')) return;
-                fwrite($fp, "installed=$date\nurl=$url\n");
-                fclose($fp);
-                break;
-
-              case 'update' :
-                $date = date('r');
-                if (!$fp = @fopen($file, 'a')) return;
-                fwrite($fp, "updated=$date\n");
-                fclose($fp);
-                break;
-            }
-        }
-
-        function plugin_readlog($plugin, $field) {
-            static $log = array();
-            $file = DOKU_PLUGIN.plugin_directory($plugin).'/manager.dat';
-
-            if (!isset($log[$plugin])) {
-                $tmp = @file_get_contents($file);
-                if (!$tmp) return '';
-                $log[$plugin] = & $tmp;
-            }
-
-            if ($field == 'ALL') {
-                return $log[$plugin];
-            }
-
-            $match = array();
-            if (preg_match_all('/'.$field.'=(.*)$/m',$log[$plugin], $match))
-                return implode("\n", $match[1]);
-
-            return '';
-        }
-    }
-
-    class ap_download extends ap_manage {
-
-        var $overwrite = false;
-
-        function process() {
-          global $lang;
-
-          $plugin_url = $_REQUEST['url'];
-          $this->download($plugin_url, $this->overwrite);
-          return '';
-        }
-
-        function html() {
-            parent::html();
-
-            ptln('<div class="pm_info">');
-            ptln('<h2>'.$this->lang['downloading'].'</h2>');
-
-            if ($this->manager->error) {
-                ptln('<div class="error">'.str_replace("\n","<br />",$this->manager->error).'</div>');
-            } else if (count($this->downloaded) == 1) {
-                ptln('<p>'.sprintf($this->lang['downloaded'],$this->downloaded[0]).'</p>');
-            } else if (count($this->downloaded)) {   // more than one plugin in the download
-                ptln('<p>'.$this->lang['downloads'].'</p>');
-                ptln('<ul>');
-                foreach ($this->downloaded as $plugin) {
-                    ptln('<li><div class="li">'.$plugin.'</div></li>',2);
-                }
-                ptln('</ul>');
-            } else {        // none found in download
-                ptln('<p>'.$this->lang['download_none'].'</p>');
-            }
-            ptln('</div>');
-        }
-
+        return $this->plugin_list;
     }
 
-    class ap_delete extends ap_manage {
-
-        function process() {
-
-            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();
-            }
-        }
-
-        function html() {
-            parent::html();
-
-            ptln('<div class="pm_info">');
-            ptln('<h2>'.$this->lang['deleting'].'</h2>');
-
-            if ($this->manager->error) {
-              ptln('<div class="error">'.str_replace("\n","<br />",$this->manager->error).'</div>');
-            } else {
-              ptln('<p>'.sprintf($this->lang['deleted'],$this->plugin).'</p>');
-            }
-            ptln('</div>');
-        }
-    }
-
-    class ap_info extends ap_manage {
-
-        var $plugin_info = array();        // the plugin itself
-        var $details = array();            // any component plugins
-
-        function process() {
-
-          // sanity check
-          if (!$this->manager->plugin) { return; }
-
-          $component_list = ap_plugin_components($this->manager->plugin);
-          usort($component_list, 'ap_component_sort');
-
-          foreach ($component_list as $component) {
-              if ($obj = & plugin_load($component['type'],$component['name']) === NULL) continue;
-
-            $this->details[] = array_merge($obj->getInfo(), array('type' => $component['type']));
-            unset($obj);
-          }
-
-          // review details to simplify things
-          foreach($this->details as $info) {
-            foreach($info as $item => $value) {
-              if (!isset($this->plugin_info[$item])) { $this->plugin_info[$item] = $value; continue; }
-              if ($this->plugin_info[$item] != $value) $this->plugin_info[$item] = '';
-            }
-          }
-        }
-
-        function html() {
-
-          // output the standard menu stuff
-          parent::html();
-
-          // sanity check
-          if (!$this->manager->plugin) { return; }
-
-          ptln('<div class="pm_info">');
-          ptln("<h2>".$this->manager->getLang('plugin')." {$this->manager->plugin}</h2>");
-
-          // collect pertinent information from the log
-          $installed = $this->plugin_readlog($this->manager->plugin, 'installed');
-          $source = $this->plugin_readlog($this->manager->plugin, 'url');
-          $updated = $this->plugin_readlog($this->manager->plugin, 'updated');
-          if (strrpos($updated, "\n") !== false) $updated = substr($updated, strrpos($updated, "\n")+1);
-
-          ptln("<dl>",2);
-          ptln("<dt>".$this->manager->getLang('source').'</dt><dd>'.($source ? $source : $this->manager->getLang('unknown'))."</dd>",4);
-          ptln("<dt>".$this->manager->getLang('installed').'</dt><dd>'.($installed ? $installed : $this->manager->getLang('unknown'))."</dd>",4);
-          if ($updated) ptln("<dt>".$this->manager->getLang('lastupdate').'</dt><dd>'.$updated."</dd>",4);
-          ptln("</dl>",2);
-
-          if (count($this->details) == 0) {
-              ptln("<p>".$this->manager->getLang('noinfo')."</p>",2);
-          } else {
-
-            ptln("<dl>",2);
-            if ($this->plugin_info['name']) ptln("<dt>".$this->manager->getLang('name')."</dt><dd>".$this->out($this->plugin_info['name'])."</dd>",4);
-            if ($this->plugin_info['date']) ptln("<dt>".$this->manager->getLang('date')."</dt><dd>".$this->out($this->plugin_info['date'])."</dd>",4);
-            if ($this->plugin_info['type']) ptln("<dt>".$this->manager->getLang('type')."</dt><dd>".$this->out($this->plugin_info['type'])."</dd>",4);
-            if ($this->plugin_info['desc']) ptln("<dt>".$this->manager->getLang('desc')."</dt><dd>".$this->out($this->plugin_info['desc'])."</dd>",4);
-            if ($this->plugin_info['author']) ptln("<dt>".$this->manager->getLang('author')."</dt><dd>".$this->manager->email($this->plugin_info['email'], $this->plugin_info['author'])."</dd>",4);
-            if ($this->plugin_info['url']) ptln("<dt>".$this->manager->getLang('www')."</dt><dd>".$this->manager->external_link($this->plugin_info['url'], '', 'urlextern')."</dd>",4);
-            ptln("</dl>",2);
-
-            if (count($this->details) > 1) {
-              ptln("<h3>".$this->manager->getLang('components')."</h3>",2);
-              ptln("<div>",2);
-
-              foreach ($this->details as $info) {
-
-                ptln("<dl>",4);
-                ptln("<dt>".$this->manager->getLang('name')."</dt><dd>".$this->out($info['name'])."</dd>",6);
-                if (!$this->plugin_info['date']) ptln("<dt>".$this->manager->getLang('date')."</dt><dd>".$this->out($info['date'])."</dd>",6);
-                if (!$this->plugin_info['type']) ptln("<dt>".$this->manager->getLang('type')."</dt><dd>".$this->out($info['type'])."</dd>",6);
-                if (!$this->plugin_info['desc']) ptln("<dt>".$this->manager->getLang('desc')."</dt><dd>".$this->out($info['desc'])."</dd>",6);
-                if (!$this->plugin_info['author']) ptln("<dt>".$this->manager->getLang('author')."</dt><dd>".$this->manager->email($info['email'], $info['author'])."</dd>",6);
-                if (!$this->plugin_info['url']) ptln("<dt>".$this->manager->getLang('www')."</dt><dd>".$this->manager->external_link($info['url'], '', 'urlextern')."</dd>",6);
-                ptln("</dl>",4);
-
-              }
-              ptln("</div>",2);
-            }
-          }
-          ptln("</div>");
-        }
-
-        // simple output filter, make html entities safe and convert new lines to <br />
-        function out($text) {
-            return str_replace("\n",'<br />',htmlspecialchars($text));
-        }
-
-    }
-
-    //--------------[ to do ]---------------------------------------
-    class ap_update extends ap_manage {
-
-        var $overwrite = true;
-
-        function process() {
-          global $lang;
-
-          $plugin_url = $this->plugin_readlog($this->plugin, 'url');
-          $this->download($plugin_url, $this->overwrite);
-          return '';
-        }
-
-        function html() {
-            parent::html();
-
-            ptln('<div class="pm_info">');
-            ptln('<h2>'.$this->lang['updating'].'</h2>');
-
-            if ($this->manager->error) {
-                ptln('<div class="error">'.str_replace("\n","<br />", $this->manager->error).'</div>');
-            } else if (count($this->downloaded) == 1) {
-                ptln('<p>'.sprintf($this->lang['updated'],$this->downloaded[0]).'</p>');
-            } else if (count($this->downloaded)) {   // more than one plugin in the download
-                ptln('<p>'.$this->lang['updates'].'</p>');
-                ptln('<ul>');
-                foreach ($this->downloaded as $plugin) {
-                    ptln('<li><div class="li">'.$plugin.'</div></li>',2);
-                }
-                ptln('</ul>');
-            } else {        // none found in download
-                ptln('<p>'.$this->lang['update_none'].'</p>');
-            }
-            ptln('</div>');
-        }
-    }
-    class ap_settings extends ap_manage {}
-
-    class ap_enable extends ap_manage {
-
-      var $enabled = array();
-
-      function process() {
-        global $plugin_protected;
-        $count_enabled = $count_disabled = 0;
-
-        $this->enabled = isset($_REQUEST['enabled']) ? $_REQUEST['enabled'] : array();
-
-        foreach ($this->manager->plugin_list as $plugin) {
-          if (in_array($plugin, $plugin_protected)) continue;
-
-          $new = in_array($plugin, $this->enabled);
-          $old = !plugin_isdisabled($plugin);
-
-          if ($new != $old) {
-            switch ($new) {
-              // enable plugin
-              case true :
-                if(plugin_enable($plugin)){
-                    msg(sprintf($this->lang['enabled'],$plugin),1);
-                    $count_enabled++;
-                }else{
-                    msg(sprintf($this->lang['notenabled'],$plugin),-1);
-                }
-                break;
-              case false:
-                if(plugin_disable($plugin)){
-                    msg(sprintf($this->lang['disabled'],$plugin),1);
-                    $count_disabled++;
-                }else{
-                    msg(sprintf($this->lang['notdisabled'],$plugin),-1);
-                }
-                break;
-            }
-          }
-        }
-
-        // refresh plugins, including expiring any dokuwiki cache(s)
-        if ($count_enabled || $count_disabled) {
-          $this->refresh();
-        }
-      }
-
-    }
-
-    //--------------[ utilities ]-----------------------------------
-
-    function is_css($f) { return (substr($f, -4) == '.css'); }
-
-    // generate an admin plugin href
-    function apl($pl, $fn) {
-      global $ID;
-      return wl($ID,"do=admin&amp;page=plugin".($pl?"&amp;plugin=$pl":"").($fn?"&amp;fn=$fn":""));
-    }
-
-    // decompress wrapper
-    function ap_decompress($file, $target) {
-        global $conf;
-
-        // decompression library doesn't like target folders ending in "/"
-        if (substr($target, -1) == "/") $target = substr($target, 0, -1);
-        $ext = substr($file, strrpos($file,'.')+1);
-
-        // .tar, .tar.bz, .tar.gz, .tgz
-        if (in_array($ext, array('tar','bz','bz2','gz','tgz'))) {
-
-          require_once(DOKU_INC."inc/TarLib.class.php");
-
-          if (strpos($ext, 'bz') !== false) $compress_type = COMPRESS_BZIP;
-          else if (strpos($ext,'gz') !== false) $compress_type = COMPRESS_GZIP;
-          else $compress_type = COMPRESS_NONE;
-
-          $tar = new TarLib($file, $compress_type);
-          if($tar->_initerror < 0){
-            if($conf['allowdebug']){
-                msg('TarLib Error: '.$tar->TarErrorStr($tar->_initerror),-1);
-            }
-            return false;
-          }
-          $ok = $tar->Extract(FULL_ARCHIVE, $target, '', 0777);
-
-          if($ok<1){
-            if($conf['allowdebug']){
-                msg('TarLib Error: '.$tar->TarErrorStr($ok),-1);
-            }
-            return false;
-          }
-          return true;
-        } else if ($ext == 'zip') {
-
-          require_once(DOKU_INC."inc/ZipLib.class.php");
-
-          $zip = new ZipLib();
-          $ok = $zip->Extract($file, $target);
-
-          // FIXME sort something out for handling zip error messages meaningfully
-          return ($ok==-1?false:true);
-
-        }  else if ($ext == "rar") {
-          // not yet supported -- fix me
-          return false;
-        }
-
-        // unsupported file type
-        return false;
-    }
-
-    // copy with recursive sub-directory support
-    function ap_copy($src, $dst) {
-        global $conf;
-
-        if (is_dir($src)) {
-          if (!$dh = @opendir($src)) return false;
-
-          if ($ok = io_mkdir_p($dst)) {
-            while ($ok && (false !== ($f = readdir($dh)))) {
-              if ($f == '..' || $f == '.') continue;
-              $ok = ap_copy("$src/$f", "$dst/$f");
-            }
-          }
-
-          closedir($dh);
-          return $ok;
-
-        } else {
-            $exists = @file_exists($dst);
-
-            if (!@copy($src,$dst)) return false;
-            if (!$exists && !empty($conf['fperm'])) chmod($dst, $conf['fperm']);
-            @touch($dst,filemtime($src));
-        }
-
-        return true;
-    }
-
-    // delete, with recursive sub-directory support
-    function ap_delete($path) {
-
-        if (!is_string($path) || $path == "") return false;
-
-        if (is_dir($path)) {
-          if (!$dh = @opendir($path)) return false;
-
-          while ($f = readdir($dh)) {
-            if ($f == '..' || $f == '.') continue;
-            ap_delete("$path/$f");
-          }
-
-          closedir($dh);
-          return @rmdir($path);
-        } else {
-          return @unlink($path);
-        }
-
-        return false;
-    }
-
-    // return a list (name & type) of all the component plugins that make up this plugin
-    // can this move to pluginutils?
-    function ap_plugin_components($plugin) {
+}
 
-      global $plugin_types;
 
-      $components = array();
-      $path = DOKU_PLUGIN.plugin_directory($plugin).'/';
 
-      foreach ($plugin_types as $type) {
-        if (@file_exists($path.$type.'.php')) { $components[] = array('name'=>$plugin, 'type'=>$type); continue; }
 
-        if ($dh = @opendir($path.$type.'/')) {
-          while (false !== ($cp = readdir($dh))) {
-            if ($cp == '.' || $cp == '..' || strtolower(substr($cp,-4)) != '.php') continue;
-            $components[] = array('name'=>$plugin.'_'.substr($cp, 0, -4), 'type'=>$type);
-          }
-          closedir($dh);
-        }
-      }
-      return $components;
-    }
 
-    function ap_component_sort($a, $b) {
-        if ($a['name'] == $b['name']) return 0;
-        return ($a['name'] < $b['name']) ? -1 : 1;
-    }
 
diff --git a/lib/plugins/plugin/classes/ap_delete.class.php b/lib/plugins/plugin/classes/ap_delete.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..231147479078e76bf25c3a9796e20c1ba962ca2a
--- /dev/null
+++ b/lib/plugins/plugin/classes/ap_delete.class.php
@@ -0,0 +1,28 @@
+<?php
+class ap_delete extends ap_manage {
+
+    function process() {
+
+        if (!$this->dir_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();
+        }
+    }
+
+    function html() {
+        parent::html();
+
+        ptln('<div class="pm_info">');
+        ptln('<h2>'.$this->lang['deleting'].'</h2>');
+
+        if ($this->manager->error) {
+            ptln('<div class="error">'.str_replace("\n","<br />",$this->manager->error).'</div>');
+        } else {
+            ptln('<p>'.sprintf($this->lang['deleted'],$this->plugin).'</p>');
+        }
+        ptln('</div>');
+    }
+}
+
diff --git a/lib/plugins/plugin/classes/ap_download.class.php b/lib/plugins/plugin/classes/ap_download.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..465cb2da178473100564f1b218929fdccbf5412e
--- /dev/null
+++ b/lib/plugins/plugin/classes/ap_download.class.php
@@ -0,0 +1,201 @@
+<?php
+class ap_download extends ap_manage {
+
+    var $overwrite = false;
+
+    /**
+     * Initiate the plugin download
+     */
+    function process() {
+        global $lang;
+
+        $plugin_url = $_REQUEST['url'];
+        $this->download($plugin_url, $this->overwrite);
+        return '';
+    }
+
+    /**
+     * Print results of the download
+     */
+    function html() {
+        parent::html();
+
+        ptln('<div class="pm_info">');
+        ptln('<h2>'.$this->lang['downloading'].'</h2>');
+
+        if ($this->manager->error) {
+            ptln('<div class="error">'.str_replace("\n","<br />",$this->manager->error).'</div>');
+        } else if (count($this->downloaded) == 1) {
+            ptln('<p>'.sprintf($this->lang['downloaded'],$this->downloaded[0]).'</p>');
+        } else if (count($this->downloaded)) {   // more than one plugin in the download
+            ptln('<p>'.$this->lang['downloads'].'</p>');
+            ptln('<ul>');
+            foreach ($this->downloaded as $plugin) {
+                ptln('<li><div class="li">'.$plugin.'</div></li>',2);
+            }
+            ptln('</ul>');
+        } else {        // none found in download
+            ptln('<p>'.$this->lang['download_none'].'</p>');
+        }
+        ptln('</div>');
+    }
+
+    /**
+     * Process the downloaded file
+     */
+    function download($url, $overwrite=false) {
+        global $lang;
+
+        // check the url
+        $matches = array();
+        if (!preg_match("/[^\/]*$/", $url, $matches) || !$matches[0]) {
+            $this->manager->error = $this->lang['error_badurl']."\n";
+            return false;
+        }
+
+        $file = $matches[0];
+
+        if (!($tmp = io_mktmpdir())) {
+            $this->manager->error = $this->lang['error_dircreate']."\n";
+            return false;
+        }
+
+        if (!$file = io_download($url, "$tmp/", true, $file)) {
+            $this->manager->error = sprintf($this->lang['error_download'],$url)."\n";
+        }
+
+        if (!$this->manager->error && !$this->decompress("$tmp/$file", $tmp)) {
+            $this->manager->error = sprintf($this->lang['error_decompress'],$file)."\n";
+        }
+
+        // search $tmp for the folder(s) that has been created
+        // move the folder(s) to lib/plugins/
+        if (!$this->manager->error) {
+            if ($dh = @opendir("$tmp/")) {
+                while (false !== ($f = readdir($dh))) {
+                    if ($f == '.' || $f == '..' || $f == 'tmp') continue;
+                    if (!is_dir("$tmp/$f")) continue;
+
+                    // check to make sure we aren't overwriting anything
+                    if (!$overwrite && @file_exists(DOKU_PLUGIN.$f)) {
+                        // remember our settings, ask the user to confirm overwrite, FIXME
+                        continue;
+                    }
+
+                    $instruction = @file_exists(DOKU_PLUGIN.$f) ? 'update' : 'install';
+
+                    if ($this->dircopy("$tmp/$f", DOKU_PLUGIN.$f)) {
+                        $this->downloaded[] = $f;
+                        $this->plugin_writelog($f, $instruction, array($url));
+                    } else {
+                        $this->manager->error .= sprintf($this->lang['error_copy']."\n", $f);
+                    }
+                }
+                closedir($dh);
+            } else {
+                $this->manager->error = $this->lang['error']."\n";
+            }
+        }
+
+        // cleanup
+        if ($tmp) $this->dir_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;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Decompress a given file to the given target directory
+     *
+     * Determines the compression type from the file extension
+     */
+    function decompress($file, $target) {
+        global $conf;
+
+        // decompression library doesn't like target folders ending in "/"
+        if (substr($target, -1) == "/") $target = substr($target, 0, -1);
+        $ext = substr($file, strrpos($file,'.')+1);
+
+        // .tar, .tar.bz, .tar.gz, .tgz
+        if (in_array($ext, array('tar','bz','bz2','gz','tgz'))) {
+
+            require_once(DOKU_INC."inc/TarLib.class.php");
+
+            if (strpos($ext, 'bz') !== false) $compress_type = COMPRESS_BZIP;
+            else if (strpos($ext,'gz') !== false) $compress_type = COMPRESS_GZIP;
+            else $compress_type = COMPRESS_NONE;
+
+            $tar = new TarLib($file, $compress_type);
+            if($tar->_initerror < 0){
+                if($conf['allowdebug']){
+                    msg('TarLib Error: '.$tar->TarErrorStr($tar->_initerror),-1);
+                }
+                return false;
+            }
+            $ok = $tar->Extract(FULL_ARCHIVE, $target, '', 0777);
+
+            if($ok<1){
+                if($conf['allowdebug']){
+                    msg('TarLib Error: '.$tar->TarErrorStr($ok),-1);
+                }
+                return false;
+            }
+            return true;
+        } else if ($ext == 'zip') {
+
+            require_once(DOKU_INC."inc/ZipLib.class.php");
+
+            $zip = new ZipLib();
+            $ok = $zip->Extract($file, $target);
+
+            // FIXME sort something out for handling zip error messages meaningfully
+            return ($ok==-1?false:true);
+
+        }  else if ($ext == "rar") {
+            // not yet supported -- fix me
+            return false;
+        }
+
+        // unsupported file type
+        return false;
+    }
+
+    /**
+     * Copy with recursive sub-directory support
+     */
+    function dircopy($src, $dst) {
+        global $conf;
+
+        if (is_dir($src)) {
+            if (!$dh = @opendir($src)) return false;
+
+            if ($ok = io_mkdir_p($dst)) {
+                while ($ok && (false !== ($f = readdir($dh)))) {
+                    if ($f == '..' || $f == '.') continue;
+                    $ok = $this->dircopy("$src/$f", "$dst/$f");
+                }
+            }
+
+            closedir($dh);
+            return $ok;
+
+        } else {
+            $exists = @file_exists($dst);
+
+            if (!@copy($src,$dst)) return false;
+            if (!$exists && !empty($conf['fperm'])) chmod($dst, $conf['fperm']);
+            @touch($dst,filemtime($src));
+        }
+
+        return true;
+    }
+
+
+}
+
diff --git a/lib/plugins/plugin/classes/ap_enable.class.php b/lib/plugins/plugin/classes/ap_enable.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..35450a9073dd43768f3896007fbc9743f0cef386
--- /dev/null
+++ b/lib/plugins/plugin/classes/ap_enable.class.php
@@ -0,0 +1,49 @@
+<?php
+
+class ap_enable extends ap_manage {
+
+    var $enabled = array();
+
+    function process() {
+        global $plugin_protected;
+        $count_enabled = $count_disabled = 0;
+
+        $this->enabled = isset($_REQUEST['enabled']) ? $_REQUEST['enabled'] : array();
+
+        foreach ($this->manager->plugin_list as $plugin) {
+            if (in_array($plugin, $plugin_protected)) continue;
+
+            $new = in_array($plugin, $this->enabled);
+            $old = !plugin_isdisabled($plugin);
+
+            if ($new != $old) {
+                switch ($new) {
+                    // enable plugin
+                    case true :
+                        if(plugin_enable($plugin)){
+                            msg(sprintf($this->lang['enabled'],$plugin),1);
+                            $count_enabled++;
+                        }else{
+                            msg(sprintf($this->lang['notenabled'],$plugin),-1);
+                        }
+                        break;
+                    case false:
+                        if(plugin_disable($plugin)){
+                            msg(sprintf($this->lang['disabled'],$plugin),1);
+                            $count_disabled++;
+                        }else{
+                            msg(sprintf($this->lang['notdisabled'],$plugin),-1);
+                        }
+                        break;
+                }
+            }
+        }
+
+        // refresh plugins, including expiring any dokuwiki cache(s)
+        if ($count_enabled || $count_disabled) {
+            $this->refresh();
+        }
+    }
+
+}
+
diff --git a/lib/plugins/plugin/classes/ap_info.class.php b/lib/plugins/plugin/classes/ap_info.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..dd1765f6ca388ebceb8d46f4b841adb74a494ecf
--- /dev/null
+++ b/lib/plugins/plugin/classes/ap_info.class.php
@@ -0,0 +1,129 @@
+<?php
+
+class ap_info extends ap_manage {
+
+    var $plugin_info = array();        // the plugin itself
+    var $details = array();            // any component plugins
+
+    function process() {
+
+        // sanity check
+        if (!$this->manager->plugin) { return; }
+
+        $component_list = $this->get_plugin_components($this->manager->plugin);
+        usort($component_list, array($this,'component_sort'));
+
+        foreach ($component_list as $component) {
+            if ($obj = & plugin_load($component['type'],$component['name']) === NULL) continue;
+
+            $this->details[] = array_merge($obj->getInfo(), array('type' => $component['type']));
+            unset($obj);
+        }
+
+        // review details to simplify things
+        foreach($this->details as $info) {
+            foreach($info as $item => $value) {
+                if (!isset($this->plugin_info[$item])) { $this->plugin_info[$item] = $value; continue; }
+                if ($this->plugin_info[$item] != $value) $this->plugin_info[$item] = '';
+            }
+        }
+    }
+
+    function html() {
+
+        // output the standard menu stuff
+        parent::html();
+
+        // sanity check
+        if (!$this->manager->plugin) { return; }
+
+        ptln('<div class="pm_info">');
+        ptln("<h2>".$this->manager->getLang('plugin')." {$this->manager->plugin}</h2>");
+
+        // collect pertinent information from the log
+        $installed = $this->plugin_readlog($this->manager->plugin, 'installed');
+        $source = $this->plugin_readlog($this->manager->plugin, 'url');
+        $updated = $this->plugin_readlog($this->manager->plugin, 'updated');
+        if (strrpos($updated, "\n") !== false) $updated = substr($updated, strrpos($updated, "\n")+1);
+
+        ptln("<dl>",2);
+        ptln("<dt>".$this->manager->getLang('source').'</dt><dd>'.($source ? $source : $this->manager->getLang('unknown'))."</dd>",4);
+        ptln("<dt>".$this->manager->getLang('installed').'</dt><dd>'.($installed ? $installed : $this->manager->getLang('unknown'))."</dd>",4);
+        if ($updated) ptln("<dt>".$this->manager->getLang('lastupdate').'</dt><dd>'.$updated."</dd>",4);
+        ptln("</dl>",2);
+
+        if (count($this->details) == 0) {
+            ptln("<p>".$this->manager->getLang('noinfo')."</p>",2);
+        } else {
+
+            ptln("<dl>",2);
+            if ($this->plugin_info['name']) ptln("<dt>".$this->manager->getLang('name')."</dt><dd>".$this->out($this->plugin_info['name'])."</dd>",4);
+            if ($this->plugin_info['date']) ptln("<dt>".$this->manager->getLang('date')."</dt><dd>".$this->out($this->plugin_info['date'])."</dd>",4);
+            if ($this->plugin_info['type']) ptln("<dt>".$this->manager->getLang('type')."</dt><dd>".$this->out($this->plugin_info['type'])."</dd>",4);
+            if ($this->plugin_info['desc']) ptln("<dt>".$this->manager->getLang('desc')."</dt><dd>".$this->out($this->plugin_info['desc'])."</dd>",4);
+            if ($this->plugin_info['author']) ptln("<dt>".$this->manager->getLang('author')."</dt><dd>".$this->manager->email($this->plugin_info['email'], $this->plugin_info['author'])."</dd>",4);
+            if ($this->plugin_info['url']) ptln("<dt>".$this->manager->getLang('www')."</dt><dd>".$this->manager->external_link($this->plugin_info['url'], '', 'urlextern')."</dd>",4);
+            ptln("</dl>",2);
+
+            if (count($this->details) > 1) {
+                ptln("<h3>".$this->manager->getLang('components')."</h3>",2);
+                ptln("<div>",2);
+
+                foreach ($this->details as $info) {
+
+                    ptln("<dl>",4);
+                    ptln("<dt>".$this->manager->getLang('name')."</dt><dd>".$this->out($info['name'])."</dd>",6);
+                    if (!$this->plugin_info['date']) ptln("<dt>".$this->manager->getLang('date')."</dt><dd>".$this->out($info['date'])."</dd>",6);
+                    if (!$this->plugin_info['type']) ptln("<dt>".$this->manager->getLang('type')."</dt><dd>".$this->out($info['type'])."</dd>",6);
+                    if (!$this->plugin_info['desc']) ptln("<dt>".$this->manager->getLang('desc')."</dt><dd>".$this->out($info['desc'])."</dd>",6);
+                    if (!$this->plugin_info['author']) ptln("<dt>".$this->manager->getLang('author')."</dt><dd>".$this->manager->email($info['email'], $info['author'])."</dd>",6);
+                    if (!$this->plugin_info['url']) ptln("<dt>".$this->manager->getLang('www')."</dt><dd>".$this->manager->external_link($info['url'], '', 'urlextern')."</dd>",6);
+                    ptln("</dl>",4);
+
+                }
+                ptln("</div>",2);
+            }
+        }
+        ptln("</div>");
+    }
+
+    // simple output filter, make html entities safe and convert new lines to <br />
+    function out($text) {
+        return str_replace("\n",'<br />',htmlspecialchars($text));
+    }
+
+
+    /**
+     * return a list (name & type) of all the component plugins that make up this plugin
+     *
+     * @todo can this move to pluginutils?
+     */
+    function get_plugin_components($plugin) {
+
+        global $plugin_types;
+
+        $components = array();
+        $path = DOKU_PLUGIN.plugin_directory($plugin).'/';
+
+        foreach ($plugin_types as $type) {
+            if (@file_exists($path.$type.'.php')) { $components[] = array('name'=>$plugin, 'type'=>$type); continue; }
+
+            if ($dh = @opendir($path.$type.'/')) {
+                while (false !== ($cp = readdir($dh))) {
+                    if ($cp == '.' || $cp == '..' || strtolower(substr($cp,-4)) != '.php') continue;
+                    $components[] = array('name'=>$plugin.'_'.substr($cp, 0, -4), 'type'=>$type);
+                }
+                closedir($dh);
+            }
+        }
+        return $components;
+    }
+
+    /**
+     * usort callback to sort plugin components
+     */
+    function component_sort($a, $b) {
+        if ($a['name'] == $b['name']) return 0;
+        return ($a['name'] < $b['name']) ? -1 : 1;
+    }
+}
diff --git a/lib/plugins/plugin/classes/ap_manage.class.php b/lib/plugins/plugin/classes/ap_manage.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..aea04f487d3a8ad8097b98c58e53dc41b9eb0ba4
--- /dev/null
+++ b/lib/plugins/plugin/classes/ap_manage.class.php
@@ -0,0 +1,203 @@
+<?php
+
+class ap_manage {
+
+    var $manager = NULL;
+    var $lang = array();
+    var $plugin = '';
+    var $downloaded = array();
+
+    function ap_manage(&$manager, $plugin) {
+        $this->manager = & $manager;
+        $this->plugin = $plugin;
+        $this->lang = & $manager->lang;
+    }
+
+    function process() {
+        return '';
+    }
+
+    function html() {
+        print $this->manager->locale_xhtml('admin_plugin');
+        $this->html_menu();
+    }
+
+    // build our standard menu
+    function html_menu($listPlugins = true) {
+        global $ID;
+
+        ptln('<div class="pm_menu">');
+
+        ptln('<div class="common">');
+        ptln('  <h2>'.$this->lang['download'].'</h2>');
+        ptln('  <form action="'.wl($ID).'" method="post">');
+        ptln('    <fieldset class="hidden">',4);
+        ptln('      <input type="hidden" name="do"   value="admin" />');
+        ptln('      <input type="hidden" name="page" value="plugin" />');
+        formSecurityToken();
+        ptln('    </fieldset>');
+        ptln('    <fieldset>');
+        ptln('      <legend>'.$this->lang['download'].'</legend>');
+        ptln('      <label for="dw__url">'.$this->lang['url'].'<input name="url" id="dw__url" class="edit" type="text" maxlength="200" /></label>');
+        ptln('      <input type="submit" class="button" name="fn[download]" value="'.$this->lang['btn_download'].'" />');
+        ptln('    </fieldset>');
+        ptln('  </form>');
+        ptln('</div>');
+
+        if ($listPlugins) {
+            ptln('<h2>'.$this->lang['manage'].'</h2>');
+
+            ptln('<form action="'.wl($ID).'" method="post" class="plugins">');
+
+            ptln('  <fieldset class="hidden">');
+            ptln('    <input type="hidden" name="do"     value="admin" />');
+            ptln('    <input type="hidden" name="page"   value="plugin" />');
+            formSecurityToken();
+            ptln('  </fieldset>');
+
+            $this->html_pluginlist();
+
+            ptln('  <fieldset class="buttons">');
+            ptln('    <input type="submit" class="button" name="fn[enable]" value="'.$this->lang['btn_enable'].'" />');
+            ptln('  </fieldset>');
+
+            //            ptln('  </div>');
+            ptln('</form>');
+        }
+
+        ptln('</div>');
+    }
+
+    function html_pluginlist() {
+        global $ID;
+        global $plugin_protected;
+
+        foreach ($this->manager->plugin_list as $plugin) {
+
+            $disabled = plugin_isdisabled($plugin);
+            $protected = in_array($plugin,$plugin_protected);
+
+            $checked = ($disabled) ? '' : ' checked="checked"';
+            $check_disabled = ($protected) ? ' disabled="disabled"' : '';
+
+            // determine display class(es)
+            $class = array();
+            if (in_array($plugin, $this->downloaded)) $class[] = 'new';
+            if ($disabled) $class[] = 'disabled';
+            if ($protected) $class[] = 'protected';
+
+            $class = count($class) ? ' class="'.join(' ', $class).'"' : '';
+
+            ptln('    <fieldset'.$class.'>');
+            ptln('      <legend>'.$plugin.'</legend>');
+            ptln('      <input type="checkbox" class="enable" name="enabled[]" value="'.$plugin.'"'.$checked.$check_disabled.' />');
+            ptln('      <h3 class="legend">'.$plugin.'</h3>');
+
+            $this->html_button($plugin, 'info', false, 6);
+            if (in_array('settings', $this->manager->functions)) {
+                $this->html_button($plugin, 'settings', !@file_exists(DOKU_PLUGIN.$plugin.'/settings.php'), 6);
+            }
+            $this->html_button($plugin, 'update', !$this->plugin_readlog($plugin, 'url'), 6);
+            $this->html_button($plugin, 'delete', $protected, 6);
+
+            ptln('    </fieldset>');
+        }
+    }
+
+    function html_button($plugin, $btn, $disabled=false, $indent=0) {
+        $disabled = ($disabled) ? 'disabled="disabled"' : '';
+        ptln('<input type="submit" class="button" '.$disabled.' name="fn['.$btn.']['.$plugin.']" value="'.$this->lang['btn_'.$btn].'" />',$indent);
+    }
+
+    /**
+     *  Refresh plugin list
+     */
+    function refresh() {
+        global $MSG,$config_cascade;
+
+        //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(reset($config_cascade['main']['local']));
+
+        // update latest plugin date - FIXME
+        header('Location: '.wl($ID).'?do=admin&page=plugin');
+        exit();
+    }
+
+    // log
+    function plugin_writelog($plugin, $cmd, $data) {
+
+        $file = DOKU_PLUGIN.$plugin.'/manager.dat';
+
+        switch ($cmd) {
+            case 'install' :
+                $url = $data[0];
+                $date = date('r');
+                if (!$fp = @fopen($file, 'w')) return;
+                fwrite($fp, "installed=$date\nurl=$url\n");
+                fclose($fp);
+                break;
+
+            case 'update' :
+                $date = date('r');
+                if (!$fp = @fopen($file, 'a')) return;
+                fwrite($fp, "updated=$date\n");
+                fclose($fp);
+                break;
+        }
+    }
+
+    function plugin_readlog($plugin, $field) {
+        static $log = array();
+        $file = DOKU_PLUGIN.plugin_directory($plugin).'/manager.dat';
+
+        if (!isset($log[$plugin])) {
+            $tmp = @file_get_contents($file);
+            if (!$tmp) return '';
+            $log[$plugin] = & $tmp;
+        }
+
+        if ($field == 'ALL') {
+            return $log[$plugin];
+        }
+
+        $match = array();
+        if (preg_match_all('/'.$field.'=(.*)$/m',$log[$plugin], $match))
+            return implode("\n", $match[1]);
+
+        return '';
+    }
+
+    /**
+     * delete, with recursive sub-directory support
+     */
+    function dir_delete($path) {
+        if (!is_string($path) || $path == "") return false;
+
+        if (is_dir($path)) {
+            if (!$dh = @opendir($path)) return false;
+
+            while ($f = readdir($dh)) {
+                if ($f == '..' || $f == '.') continue;
+                $this->dir_delete("$path/$f");
+            }
+
+            closedir($dh);
+            return @rmdir($path);
+        } else {
+            return @unlink($path);
+        }
+
+        return false;
+    }
+
+
+}
diff --git a/lib/plugins/plugin/classes/ap_update.class.php b/lib/plugins/plugin/classes/ap_update.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ac002991470cf2cfe4d226bd0192e389493b055
--- /dev/null
+++ b/lib/plugins/plugin/classes/ap_update.class.php
@@ -0,0 +1,38 @@
+<?php
+
+class ap_update extends ap_manage {
+
+    var $overwrite = true;
+
+    function process() {
+        global $lang;
+
+        $plugin_url = $this->plugin_readlog($this->plugin, 'url');
+        $this->download($plugin_url, $this->overwrite);
+        return '';
+    }
+
+    function html() {
+        parent::html();
+
+        ptln('<div class="pm_info">');
+        ptln('<h2>'.$this->lang['updating'].'</h2>');
+
+        if ($this->manager->error) {
+            ptln('<div class="error">'.str_replace("\n","<br />", $this->manager->error).'</div>');
+        } else if (count($this->downloaded) == 1) {
+            ptln('<p>'.sprintf($this->lang['updated'],$this->downloaded[0]).'</p>');
+        } else if (count($this->downloaded)) {   // more than one plugin in the download
+            ptln('<p>'.$this->lang['updates'].'</p>');
+            ptln('<ul>');
+            foreach ($this->downloaded as $plugin) {
+                ptln('<li><div class="li">'.$plugin.'</div></li>',2);
+            }
+            ptln('</ul>');
+        } else {        // none found in download
+            ptln('<p>'.$this->lang['update_none'].'</p>');
+        }
+        ptln('</div>');
+    }
+}
+