[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/Extension/ -> PluginController.php (source)

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