[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 namespace dokuwiki\Extension; 4 5 use dokuwiki\ErrorHandler; 6 7 /** 8 * Class to encapsulate access to dokuwiki plugins 9 * 10 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 11 * @author Christopher Smith <chris@jalakai.co.uk> 12 */ 13 class PluginController 14 { 15 /** @var array the types of plugins DokuWiki supports */ 16 const PLUGIN_TYPES = ['auth', 'admin', 'syntax', 'action', 'renderer', 'helper', 'remote', 'cli']; 17 18 protected $listByType = []; 19 /** @var array all installed plugins and their enabled state [plugin=>enabled] */ 20 protected $masterList = []; 21 protected $pluginCascade = ['default' => [], 'local' => [], 'protected' => []]; 22 protected $lastLocalConfigFile = ''; 23 24 /** 25 * Populates the master list of plugins 26 */ 27 public function __construct() 28 { 29 $this->loadConfig(); 30 $this->populateMasterList(); 31 } 32 33 /** 34 * Returns a list of available plugins of given type 35 * 36 * @param $type string, plugin_type name; 37 * the type of plugin to return, 38 * use empty string for all types 39 * @param $all bool; 40 * false to only return enabled plugins, 41 * true to return both enabled and disabled plugins 42 * 43 * @return array of 44 * - plugin names when $type = '' 45 * - or plugin component names when a $type is given 46 * 47 * @author Andreas Gohr <andi@splitbrain.org> 48 */ 49 public function getList($type = '', $all = false) 50 { 51 52 // request the complete list 53 if (!$type) { 54 return $all ? array_keys($this->masterList) : array_keys(array_filter($this->masterList)); 55 } 56 57 if (!isset($this->listByType[$type]['enabled'])) { 58 $this->listByType[$type]['enabled'] = $this->getListByType($type, true); 59 } 60 if ($all && !isset($this->listByType[$type]['disabled'])) { 61 $this->listByType[$type]['disabled'] = $this->getListByType($type, false); 62 } 63 64 return $all 65 ? array_merge($this->listByType[$type]['enabled'], $this->listByType[$type]['disabled']) 66 : $this->listByType[$type]['enabled']; 67 } 68 69 /** 70 * Loads the given plugin and creates an object of it 71 * 72 * @param $type string type of plugin to load 73 * @param $name string name of the plugin to load 74 * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance 75 * @param $disabled bool true to load even disabled plugins 76 * @return PluginInterface|null the plugin object or null on failure 77 * @author Andreas Gohr <andi@splitbrain.org> 78 * 79 */ 80 public function load($type, $name, $new = false, $disabled = false) 81 { 82 83 //we keep all loaded plugins available in global scope for reuse 84 global $DOKU_PLUGINS; 85 86 list($plugin, /* $component */) = $this->splitName($name); 87 88 // check if disabled 89 if (!$disabled && !$this->isEnabled($plugin)) { 90 return null; 91 } 92 93 $class = $type . '_plugin_' . $name; 94 95 try { 96 //plugin already loaded? 97 if (!empty($DOKU_PLUGINS[$type][$name])) { 98 if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) { 99 100 return class_exists($class, true) ? new $class : null; 101 } 102 103 return $DOKU_PLUGINS[$type][$name]; 104 } 105 106 //construct class and instantiate 107 if (!class_exists($class, true)) { 108 # the plugin might be in the wrong directory 109 $inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt"); 110 if ($inf['base'] && $inf['base'] != $plugin) { 111 msg( 112 sprintf( 113 "Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.", 114 hsc($plugin), 115 hsc( 116 $inf['base'] 117 ) 118 ), -1 119 ); 120 } elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) { 121 msg( 122 sprintf( 123 "Plugin name '%s' is not a valid plugin name, only the characters a-z ". 124 "and 0-9 are allowed. " . 125 'Maybe the plugin has been installed in the wrong directory?', hsc($plugin) 126 ), -1 127 ); 128 } 129 return null; 130 } 131 $DOKU_PLUGINS[$type][$name] = new $class; 132 133 } catch (\Throwable $e) { 134 ErrorHandler::showExceptionMsg($e, sprintf('Failed to load plugin %s', $plugin)); 135 return null; 136 } 137 138 return $DOKU_PLUGINS[$type][$name]; 139 } 140 141 /** 142 * Whether plugin is disabled 143 * 144 * @param string $plugin name of plugin 145 * @return bool true disabled, false enabled 146 * @deprecated in favor of the more sensible isEnabled where the return value matches the enabled state 147 */ 148 public function isDisabled($plugin) 149 { 150 dbg_deprecated('isEnabled()'); 151 return !$this->isEnabled($plugin); 152 } 153 154 /** 155 * Check whether plugin is disabled 156 * 157 * @param string $plugin name of plugin 158 * @return bool true enabled, false disabled 159 */ 160 public function isEnabled($plugin) 161 { 162 return !empty($this->masterList[$plugin]); 163 } 164 165 /** 166 * Disable the plugin 167 * 168 * @param string $plugin name of plugin 169 * @return bool true saving succeed, false saving failed 170 */ 171 public function disable($plugin) 172 { 173 if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false; 174 $this->masterList[$plugin] = 0; 175 return $this->saveList(); 176 } 177 178 /** 179 * Enable the plugin 180 * 181 * @param string $plugin name of plugin 182 * @return bool true saving succeed, false saving failed 183 */ 184 public function enable($plugin) 185 { 186 if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false; 187 $this->masterList[$plugin] = 1; 188 return $this->saveList(); 189 } 190 191 /** 192 * Returns cascade of the config files 193 * 194 * @return array with arrays of plugin configs 195 */ 196 public function getCascade() 197 { 198 return $this->pluginCascade; 199 } 200 201 /** 202 * Read all installed plugins and their current enabled state 203 */ 204 protected function populateMasterList() 205 { 206 if ($dh = @opendir(DOKU_PLUGIN)) { 207 $all_plugins = array(); 208 while (false !== ($plugin = readdir($dh))) { 209 if ($plugin[0] === '.') continue; // skip hidden entries 210 if (is_file(DOKU_PLUGIN . $plugin)) continue; // skip files, we're only interested in directories 211 212 if (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 0) { 213 $all_plugins[$plugin] = 0; 214 215 } elseif (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 1) { 216 $all_plugins[$plugin] = 1; 217 } else { 218 $all_plugins[$plugin] = 1; 219 } 220 } 221 $this->masterList = $all_plugins; 222 if (!file_exists($this->lastLocalConfigFile)) { 223 $this->saveList(true); 224 } 225 } 226 } 227 228 /** 229 * Includes the plugin config $files 230 * and returns the entries of the $plugins array set in these files 231 * 232 * @param array $files list of files to include, latter overrides previous 233 * @return array with entries of the $plugins arrays of the included files 234 */ 235 protected function checkRequire($files) 236 { 237 $plugins = array(); 238 foreach ($files as $file) { 239 if (file_exists($file)) { 240 include_once($file); 241 } 242 } 243 return $plugins; 244 } 245 246 /** 247 * Save the current list of plugins 248 * 249 * @param bool $forceSave ; 250 * false to save only when config changed 251 * true to always save 252 * @return bool true saving succeed, false saving failed 253 */ 254 protected function saveList($forceSave = false) 255 { 256 global $conf; 257 258 if (empty($this->masterList)) return false; 259 260 // Rebuild list of local settings 261 $local_plugins = $this->rebuildLocal(); 262 if ($local_plugins != $this->pluginCascade['local'] || $forceSave) { 263 $file = $this->lastLocalConfigFile; 264 $out = "<?php\n/*\n * Local plugin enable/disable settings\n" . 265 " * Auto-generated through plugin/extension manager\n *\n" . 266 " * NOTE: Plugins will not be added to this file unless there " . 267 "is a need to override a default setting. Plugins are\n" . 268 " * enabled by default.\n */\n"; 269 foreach ($local_plugins as $plugin => $value) { 270 $out .= "\$plugins['$plugin'] = $value;\n"; 271 } 272 // backup current file (remove any existing backup) 273 if (file_exists($file)) { 274 $backup = $file . '.bak'; 275 if (file_exists($backup)) @unlink($backup); 276 if (!@copy($file, $backup)) return false; 277 if ($conf['fperm']) chmod($backup, $conf['fperm']); 278 } 279 //check if can open for writing, else restore 280 return io_saveFile($file, $out); 281 } 282 return false; 283 } 284 285 /** 286 * Rebuild the set of local plugins 287 * 288 * @return array array of plugins to be saved in end($config_cascade['plugins']['local']) 289 */ 290 protected function rebuildLocal() 291 { 292 //assign to local variable to avoid overwriting 293 $backup = $this->masterList; 294 //Can't do anything about protected one so rule them out completely 295 $local_default = array_diff_key($backup, $this->pluginCascade['protected']); 296 //Diff between local+default and default 297 //gives us the ones we need to check and save 298 $diffed_ones = array_diff_key($local_default, $this->pluginCascade['default']); 299 //The ones which we are sure of (list of 0s not in default) 300 $sure_plugins = array_filter($diffed_ones, array($this, 'negate')); 301 //the ones in need of diff 302 $conflicts = array_diff_key($local_default, $diffed_ones); 303 //The final list 304 return array_merge($sure_plugins, array_diff_assoc($conflicts, $this->pluginCascade['default'])); 305 } 306 307 /** 308 * Build the list of plugins and cascade 309 * 310 */ 311 protected function loadConfig() 312 { 313 global $config_cascade; 314 foreach (array('default', 'protected') as $type) { 315 if (array_key_exists($type, $config_cascade['plugins'])) { 316 $this->pluginCascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]); 317 } 318 } 319 $local = $config_cascade['plugins']['local']; 320 $this->lastLocalConfigFile = array_pop($local); 321 $this->pluginCascade['local'] = $this->checkRequire(array($this->lastLocalConfigFile)); 322 if (is_array($local)) { 323 $this->pluginCascade['default'] = array_merge( 324 $this->pluginCascade['default'], 325 $this->checkRequire($local) 326 ); 327 } 328 $this->masterList = array_merge( 329 $this->pluginCascade['default'], 330 $this->pluginCascade['local'], 331 $this->pluginCascade['protected'] 332 ); 333 } 334 335 /** 336 * Returns a list of available plugin components of given type 337 * 338 * @param string $type plugin_type name; the type of plugin to return, 339 * @param bool $enabled true to return enabled plugins, 340 * false to return disabled plugins 341 * @return array of plugin components of requested type 342 */ 343 protected function getListByType($type, $enabled) 344 { 345 $master_list = $enabled 346 ? array_keys(array_filter($this->masterList)) 347 : array_keys(array_filter($this->masterList, array($this, 'negate'))); 348 $plugins = array(); 349 350 foreach ($master_list as $plugin) { 351 352 if (file_exists(DOKU_PLUGIN . "$plugin/$type.php")) { 353 $plugins[] = $plugin; 354 continue; 355 } 356 357 $typedir = DOKU_PLUGIN . "$plugin/$type/"; 358 if (is_dir($typedir)) { 359 if ($dp = opendir($typedir)) { 360 while (false !== ($component = readdir($dp))) { 361 if (strpos($component, '.') === 0 || strtolower(substr($component, -4)) !== '.php') continue; 362 if (is_file($typedir . $component)) { 363 $plugins[] = $plugin . '_' . substr($component, 0, -4); 364 } 365 } 366 closedir($dp); 367 } 368 } 369 370 }//foreach 371 372 return $plugins; 373 } 374 375 /** 376 * Split name in a plugin name and a component name 377 * 378 * @param string $name 379 * @return array with 380 * - plugin name 381 * - and component name when available, otherwise empty string 382 */ 383 protected function splitName($name) 384 { 385 if (!isset($this->masterList[$name])) { 386 return sexplode('_', $name, 2, ''); 387 } 388 389 return array($name, ''); 390 } 391 392 /** 393 * Returns inverse boolean value of the input 394 * 395 * @param mixed $input 396 * @return bool inversed boolean value of input 397 */ 398 protected function negate($input) 399 { 400 return !(bool)$input; 401 } 402 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body