[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> plugincontroller.class.php (source)

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