From 10e43949456b8da1c4514f0eb674c306139df05b Mon Sep 17 00:00:00 2001 From: Chris Smith <chris.eureka@jalakai.co.uk> Date: Sun, 18 Jan 2009 20:01:43 +0100 Subject: [PATCH] Major rework of pluginutils This patch completely reworks pluginutils to: - reduce the number of file accesses to enumerate and load plugins - change the way disabled plugins are recorded. a disabled plugin will now have ".disabled" added to its directory name (this halves the number of file accesses required to enumerate installed plugins) - place the guts of pluginutils code inside a class, Doku_Plugin_Controller, the existing access routines are preserved and no changes are required. - add two globals, $plugin_controller_class & $plugin_controller this allows preload.php to define its own plugin controller class - update config plugin to support new plugin structure config plugin now issues a PLUGIN_CONFIG_PLUGINLIST event before it finalizes the list of plugins it will be working with. Handlers of this event can remove plugins from the list. - update plugin manager plugin to support new plugin structure plugin manager now issues a PLUGIN_PLUGINMANAGER_PLUGINLIST event similarly to config plugin. - plugin manager updated to redirect after changes to plugins and to use msg() Finally, this patch contains a one-shot action plugin which will automatically convert a plugins directory from the old style disabled file to the new style. Note for darcs users, the new disabled format will mean a couple of old oneshot plugins, importoldchangelog and importoldindex, will have their directory names changed, which could lead to darcs wanting to record the change. darcs-hash:20090118190143-f07c6-d2e79af546a49a4af5817dd0c5cc27066e67c4d0.gz --- inc/pluginutils.php | 221 ++++++++++++------ lib/plugins/config/settings/config.class.php | 60 ++--- lib/plugins/plugin/admin.php | 60 +++-- lib/plugins/upgradeplugindirectory/action.php | 97 ++++++++ 4 files changed, 325 insertions(+), 113 deletions(-) create mode 100644 lib/plugins/upgradeplugindirectory/action.php diff --git a/inc/pluginutils.php b/inc/pluginutils.php index 100f781f2..61a2939d3 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 8610f2c81..c3531d6f3 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 a5c906a0b..237fe11b8 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 000000000..9461299df --- /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 -- GitLab