Mereged updates from DokuWiki 38
[sudaraka-org:dokuwiki-mods.git] / inc / plugincontroller.class.php
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     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 = '';
18
19     /**
20      * Populates the master list of plugins
21      */
22     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 plugin names
38      *
39      * @author Andreas Gohr <andi@splitbrain.org>
40      */
41     function getList($type='',$all=false){
42
43         // request the complete list
44         if (!$type) {
45             return $all ? array_keys($this->tmp_plugins) : array_keys(array_filter($this->tmp_plugins));
46         }
47
48         if (!isset($this->list_bytype[$type]['enabled'])) {
49             $this->list_bytype[$type]['enabled'] = $this->_getListByType($type,true);
50         }
51         if ($all && !isset($this->list_bytype[$type]['disabled'])) {
52             $this->list_bytype[$type]['disabled'] = $this->_getListByType($type,false);
53         }
54
55         return $all ? array_merge($this->list_bytype[$type]['enabled'],$this->list_bytype[$type]['disabled']) : $this->list_bytype[$type]['enabled'];
56     }
57
58     /**
59      * Loads the given plugin and creates an object of it
60      *
61      * @author Andreas Gohr <andi@splitbrain.org>
62      *
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
68      */
69     function load($type,$name,$new=false,$disabled=false){
70
71         //we keep all loaded plugins available in global scope for reuse
72         global $DOKU_PLUGINS;
73
74         list($plugin,$component) = $this->_splitName($name);
75
76         // check if disabled
77         if(!$disabled && $this->isdisabled($plugin)){
78             return null;
79         }
80
81         $class = $type.'_plugin_'.$name;
82
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;
87             } else {
88                 return $DOKU_PLUGINS[$type][$name];
89             }
90         }
91
92         //construct class and instantiate
93         if (!class_exists($class, true)) {
94
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);
103             }
104             return null;
105         }
106
107         $DOKU_PLUGINS[$type][$name] = new $class;
108         return $DOKU_PLUGINS[$type][$name];
109     }
110
111     function isdisabled($plugin) {
112         return empty($this->tmp_plugins[$plugin]);
113     }
114
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();
119     }
120
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();
125     }
126
127     function get_directory($plugin) {
128         return $plugin;
129     }
130
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
137
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
142                     continue;
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;
148                     } else {
149                         $all_plugins[$plugin] = 1;
150                     }
151                     $this->plugin_cascade['default'][$plugin] = 0;
152
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;
156
157                 } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 1)) {
158                     $all_plugins[$plugin] = 1;
159                 } else {
160                     $all_plugins[$plugin] = 1;
161                 }
162             }
163             $this->tmp_plugins = $all_plugins;
164             if (!file_exists($this->last_local_config_file)) {
165                 $this->saveList(true);
166             }
167         }
168     }
169
170     protected function checkRequire($files) {
171         $plugins = array();
172         foreach($files as $file) {
173             if(file_exists($file)) {
174                 include_once($file);
175             }
176         }
177         return $plugins;
178     }
179
180     function getCascade() {
181         return $this->plugin_cascade;
182     }
183
184     /**
185      * Save the current list of plugins
186      */
187     function saveList($forceSave = false) {
188         global $conf;
189
190         if (empty($this->tmp_plugins)) return false;
191
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";
201             }
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']);
208             }
209             //check if can open for writing, else restore
210             return io_saveFile($file,$out);
211         }
212         return false;
213     }
214
215     /**
216      * Rebuild the set of local plugins
217      * @return array array of plugins to be saved in end($config_cascade['plugins']['local'])
218      */
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);
231         //The final list
232         return array_merge($sure_plugins,array_diff_assoc($conflicts,$this->plugin_cascade['default']));
233     }
234
235     /**
236      * Build the list of plugins and cascade
237      * 
238      */
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]);
244         }
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));
250         }
251         $this->tmp_plugins = array_merge($this->plugin_cascade['default'],$this->plugin_cascade['local'],$this->plugin_cascade['protected']);
252     }
253
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')));
256
257         $plugins = array();
258         foreach ($master_list as $plugin) {
259             $dir = $this->get_directory($plugin);
260
261             if (@file_exists(DOKU_PLUGIN."$dir/$type.php")){
262                 $plugins[] = $plugin;
263             } else {
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);
269                         }
270                     }
271                     closedir($dp);
272                 }
273             }
274         }
275
276         return $plugins;
277     }
278
279     function _splitName($name) {
280         if (array_search($name, array_keys($this->tmp_plugins)) === false) {
281             return explode('_',$name,2);
282         }
283
284         return array($name,'');
285     }
286     function negate($input) {
287         return !(bool) $input;
288     }
289 }