3 * Class to encapsulate access to dokuwiki plugins
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Christopher Smith <chris@jalakai.co.uk>
9 // plugin related constants
10 if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
12 class Doku_Plugin_Controller {
14 var $list_bytype = array();
15 var $tmp_plugins = array();
16 var $plugin_cascade = array('default'=>array(),'local'=>array(),'protected'=>array());
17 var $last_local_config_file = '';
20 * Populates the master list of plugins
22 function __construct() {
24 $this->_populateMasterList();
28 * Returns a list of available plugins of given type
30 * @param $type string, plugin_type name;
31 * the type of plugin to return,
32 * use empty string for all types
34 * false to only return enabled plugins,
35 * true to return both enabled and disabled plugins
37 * @return array of plugin names
39 * @author Andreas Gohr <andi@splitbrain.org>
41 function getList($type='',$all=false){
43 // request the complete list
45 return $all ? array_keys($this->tmp_plugins) : array_keys(array_filter($this->tmp_plugins));
48 if (!isset($this->list_bytype[$type]['enabled'])) {
49 $this->list_bytype[$type]['enabled'] = $this->_getListByType($type,true);
51 if ($all && !isset($this->list_bytype[$type]['disabled'])) {
52 $this->list_bytype[$type]['disabled'] = $this->_getListByType($type,false);
55 return $all ? array_merge($this->list_bytype[$type]['enabled'],$this->list_bytype[$type]['disabled']) : $this->list_bytype[$type]['enabled'];
59 * Loads the given plugin and creates an object of it
61 * @author Andreas Gohr <andi@splitbrain.org>
63 * @param $type string type of plugin to load
64 * @param $name string name of the plugin to load
65 * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance
66 * @param $disabled bool true to load even disabled plugins
67 * @return objectreference the plugin object or null on failure
69 function load($type,$name,$new=false,$disabled=false){
71 //we keep all loaded plugins available in global scope for reuse
74 list($plugin,$component) = $this->_splitName($name);
77 if(!$disabled && $this->isdisabled($plugin)){
81 $class = $type.'_plugin_'.$name;
83 //plugin already loaded?
84 if(!empty($DOKU_PLUGINS[$type][$name])){
85 if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
86 return class_exists($class, true) ? new $class : null;
88 return $DOKU_PLUGINS[$type][$name];
92 //construct class and instantiate
93 if (!class_exists($class, true)) {
95 # the plugin might be in the wrong directory
96 $dir = $this->get_directory($plugin);
97 $inf = confToHash(DOKU_PLUGIN."$dir/plugin.info.txt");
98 if($inf['base'] && $inf['base'] != $plugin){
99 msg(sprintf("Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.", hsc($plugin), hsc($inf['base'])), -1);
100 } elseif (preg_match('/^'.DOKU_PLUGIN_NAME_REGEX.'$/', $plugin) !== 1) {
101 msg(sprintf("Plugin name '%s' is not a valid plugin name, only the characters a-z and 0-9 are allowed. ".
102 'Maybe the plugin has been installed in the wrong directory?', hsc($plugin)), -1);
107 $DOKU_PLUGINS[$type][$name] = new $class;
108 return $DOKU_PLUGINS[$type][$name];
111 function isdisabled($plugin) {
112 return empty($this->tmp_plugins[$plugin]);
115 function disable($plugin) {
116 if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false;
117 $this->tmp_plugins[$plugin] = 0;
118 return $this->saveList();
121 function enable($plugin) {
122 if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false;
123 $this->tmp_plugins[$plugin] = 1;
124 return $this->saveList();
127 function get_directory($plugin) {
131 protected function _populateMasterList() {
132 if ($dh = @opendir(DOKU_PLUGIN)) {
133 $all_plugins = array();
134 while (false !== ($plugin = readdir($dh))) {
135 if ($plugin[0] == '.') continue; // skip hidden entries
136 if (is_file(DOKU_PLUGIN.$plugin)) continue; // skip files, we're only interested in directories
138 if (substr($plugin,-9) == '.disabled') {
139 // the plugin was disabled by rc2009-01-26
140 // disabling mechanism was changed back very soon again
141 // to keep everything simple we just skip the plugin completely
143 } elseif (@file_exists(DOKU_PLUGIN.$plugin.'/disabled')) {
144 // treat this as a default disabled plugin(over-rideable by the plugin manager)
145 // deprecated 2011-09-10 (usage of disabled files)
146 if (empty($this->plugin_cascade['local'][$plugin])) {
147 $all_plugins[$plugin] = 0;
149 $all_plugins[$plugin] = 1;
151 $this->plugin_cascade['default'][$plugin] = 0;
153 } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 0) ||
154 ($plugin === 'plugin' && isset($conf['pluginmanager']) && !$conf['pluginmanager'])){
155 $all_plugins[$plugin] = 0;
157 } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 1)) {
158 $all_plugins[$plugin] = 1;
160 $all_plugins[$plugin] = 1;
163 $this->tmp_plugins = $all_plugins;
164 if (!file_exists($this->last_local_config_file)) {
165 $this->saveList(true);
170 protected function checkRequire($files) {
172 foreach($files as $file) {
173 if(file_exists($file)) {
180 function getCascade() {
181 return $this->plugin_cascade;
185 * Save the current list of plugins
187 function saveList($forceSave = false) {
190 if (empty($this->tmp_plugins)) return false;
192 // Rebuild list of local settings
193 $local_plugins = $this->rebuildLocal();
194 if($local_plugins != $this->plugin_cascade['local'] || $forceSave) {
195 $file = $this->last_local_config_file;
196 $out = "<?php\n/*\n * Local plugin enable/disable settings\n * Auto-generated through plugin/extension manager\n *\n".
197 " * NOTE: Plugins will not be added to this file unless there is a need to override a default setting. Plugins are\n".
198 " * enabled by default, unless having a 'disabled' file in their plugin folder.\n */\n";
199 foreach ($local_plugins as $plugin => $value) {
200 $out .= "\$plugins['$plugin'] = $value;\n";
202 // backup current file (remove any existing backup)
203 if (@file_exists($file)) {
204 $backup = $file.'.bak';
205 if (@file_exists($backup)) @unlink($backup);
206 if (!@copy($file,$backup)) return false;
207 if ($conf['fperm']) chmod($backup, $conf['fperm']);
209 //check if can open for writing, else restore
210 return io_saveFile($file,$out);
216 * Rebuild the set of local plugins
217 * @return array array of plugins to be saved in end($config_cascade['plugins']['local'])
219 function rebuildLocal() {
220 //assign to local variable to avoid overwriting
221 $backup = $this->tmp_plugins;
222 //Can't do anything about protected one so rule them out completely
223 $local_default = array_diff_key($backup,$this->plugin_cascade['protected']);
224 //Diff between local+default and default
225 //gives us the ones we need to check and save
226 $diffed_ones = array_diff_key($local_default,$this->plugin_cascade['default']);
227 //The ones which we are sure of (list of 0s not in default)
228 $sure_plugins = array_filter($diffed_ones,array($this,'negate'));
229 //the ones in need of diff
230 $conflicts = array_diff_key($local_default,$diffed_ones);
232 return array_merge($sure_plugins,array_diff_assoc($conflicts,$this->plugin_cascade['default']));
236 * Build the list of plugins and cascade
239 function loadConfig() {
240 global $config_cascade;
241 foreach(array('default','protected') as $type) {
242 if(array_key_exists($type,$config_cascade['plugins']))
243 $this->plugin_cascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]);
245 $local = $config_cascade['plugins']['local'];
246 $this->last_local_config_file = array_pop($local);
247 $this->plugin_cascade['local'] = $this->checkRequire(array($this->last_local_config_file));
248 if(is_array($local)) {
249 $this->plugin_cascade['default'] = array_merge($this->plugin_cascade['default'],$this->checkRequire($local));
251 $this->tmp_plugins = array_merge($this->plugin_cascade['default'],$this->plugin_cascade['local'],$this->plugin_cascade['protected']);
254 function _getListByType($type, $enabled) {
255 $master_list = $enabled ? array_keys(array_filter($this->tmp_plugins)) : array_keys(array_filter($this->tmp_plugins,array($this,'negate')));
258 foreach ($master_list as $plugin) {
259 $dir = $this->get_directory($plugin);
261 if (@file_exists(DOKU_PLUGIN."$dir/$type.php")){
262 $plugins[] = $plugin;
264 if ($dp = @opendir(DOKU_PLUGIN."$dir/$type/")) {
265 while (false !== ($component = readdir($dp))) {
266 if (substr($component,0,1) == '.' || strtolower(substr($component, -4)) != ".php") continue;
267 if (is_file(DOKU_PLUGIN."$dir/$type/$component")) {
268 $plugins[] = $plugin.'_'.substr($component, 0, -4);
279 function _splitName($name) {
280 if (array_search($name, array_keys($this->tmp_plugins)) === false) {
281 return explode('_',$name,2);
284 return array($name,'');
286 function negate($input) {
287 return !(bool) $input;