Creating repository for dokuwiki modifications for sudaraka.org
[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         global $lang;
74
75         list($plugin,$component) = $this->_splitName($name);
76
77         // check if disabled
78         if(!$disabled && $this->isdisabled($plugin)){
79             return null;
80         }
81
82         $class = $type.'_plugin_'.$name;
83
84         //plugin already loaded?
85         if(!empty($DOKU_PLUGINS[$type][$name])){
86             if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
87                 return class_exists($class, true) ? new $class : null;
88             } else {
89                 return $DOKU_PLUGINS[$type][$name];
90             }
91         }
92
93         //construct class and instantiate
94         if (!class_exists($class, true)) {
95
96             # the plugin might be in the wrong directory
97             $dir = $this->get_directory($plugin);
98             $inf = confToHash(DOKU_PLUGIN."$dir/plugin.info.txt");
99             if($inf['base'] && $inf['base'] != $plugin){
100                 msg(sprintf($lang['plugin_install_err'],hsc($plugin),hsc($inf['base'])),-1);
101             }
102             return null;
103         }
104
105         $DOKU_PLUGINS[$type][$name] = new $class;
106         return $DOKU_PLUGINS[$type][$name];
107     }
108
109     function isdisabled($plugin) {
110         return empty($this->tmp_plugins[$plugin]);
111     }
112
113     function disable($plugin) {
114         if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false;
115         $this->tmp_plugins[$plugin] = 0;
116         return $this->saveList();
117     }
118
119     function enable($plugin) {
120         if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false;
121         $this->tmp_plugins[$plugin] = 1;
122         return $this->saveList();
123     }
124
125     function get_directory($plugin) {
126         return $plugin;
127     }
128
129     protected function _populateMasterList() {
130         if ($dh = @opendir(DOKU_PLUGIN)) {
131             $all_plugins = array();
132             while (false !== ($plugin = readdir($dh))) {
133                 if ($plugin[0] == '.') continue;               // skip hidden entries
134                 if (is_file(DOKU_PLUGIN.$plugin)) continue;    // skip files, we're only interested in directories
135
136                 if (substr($plugin,-9) == '.disabled') {
137                     // the plugin was disabled by rc2009-01-26
138                     // disabling mechanism was changed back very soon again
139                     // to keep everything simple we just skip the plugin completely
140                 } elseif (@file_exists(DOKU_PLUGIN.$plugin.'/disabled')) {
141                     // treat this as a default disabled plugin(over-rideable by the plugin manager)
142                     // deprecated 2011-09-10 (usage of disabled files)
143                     if (empty($this->plugin_cascade['local'][$plugin])) {
144                         $all_plugins[$plugin] = 0;
145                     } else {
146                         $all_plugins[$plugin] = 1;
147                     }
148                     $this->plugin_cascade['default'][$plugin] = 0;
149
150                 } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 0) ||
151                           ($plugin === 'plugin' && isset($conf['pluginmanager']) && !$conf['pluginmanager'])){
152                     $all_plugins[$plugin] = 0;
153
154                 } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 1)) {
155                     $all_plugins[$plugin] = 1;
156                 } else {
157                     $all_plugins[$plugin] = 1;
158                 }
159             }
160             $this->tmp_plugins = $all_plugins;
161             if (!file_exists($this->last_local_config_file)) {
162                 $this->saveList(true);
163             }
164         }
165     }
166
167     protected function checkRequire($files) {
168         $plugins = array();
169         foreach($files as $file) {
170             if(file_exists($file)) {
171                 @include_once($file);
172             }
173         }
174         return $plugins;
175     }
176
177     function getCascade() {
178         return $this->plugin_cascade;
179     }
180
181     /**
182      * Save the current list of plugins
183      */
184     function saveList($forceSave = false) {
185         global $conf;
186
187         if (empty($this->tmp_plugins)) return false;
188
189         // Rebuild list of local settings
190         $local_plugins = $this->rebuildLocal();
191         if($local_plugins != $this->plugin_cascade['local'] || $forceSave) {
192             $file = $this->last_local_config_file;
193             $out = "<?php\n/*\n * Local plugin enable/disable settings\n * Auto-generated through plugin/extension manager\n *\n".
194                    " * NOTE: Plugins will not be added to this file unless there is a need to override a default setting. Plugins are\n".
195                    " *       enabled by default, unless having a 'disabled' file in their plugin folder.\n */\n";
196             foreach ($local_plugins as $plugin => $value) {
197                 $out .= "\$plugins['$plugin'] = $value;\n";
198             }
199             // backup current file (remove any existing backup)
200             if (@file_exists($file)) {
201                 $backup = $file.'.bak';
202                 if (@file_exists($backup)) @unlink($backup);
203                 if (!@copy($file,$backup)) return false;
204                 if ($conf['fperm']) chmod($backup, $conf['fperm']);
205             }
206             //check if can open for writing, else restore
207             return io_saveFile($file,$out);
208         }
209         return false;
210     }
211
212     /**
213      * Rebuild the set of local plugins
214      * @return array array of plugins to be saved in end($config_cascade['plugins']['local'])
215      */
216     function rebuildLocal() {
217         //assign to local variable to avoid overwriting
218         $backup = $this->tmp_plugins;
219         //Can't do anything about protected one so rule them out completely
220         $local_default = array_diff_key($backup,$this->plugin_cascade['protected']);
221         //Diff between local+default and default
222         //gives us the ones we need to check and save
223         $diffed_ones = array_diff_key($local_default,$this->plugin_cascade['default']);
224         //The ones which we are sure of (list of 0s not in default)
225         $sure_plugins = array_filter($diffed_ones,array($this,'negate'));
226         //the ones in need of diff
227         $conflicts = array_diff_key($local_default,$diffed_ones);
228         //The final list
229         return array_merge($sure_plugins,array_diff_assoc($conflicts,$this->plugin_cascade['default']));
230     }
231
232     /**
233      * Build the list of plugins and cascade
234      * 
235      */
236     function loadConfig() {
237         global $config_cascade;
238         foreach(array('default','protected') as $type) {
239             if(array_key_exists($type,$config_cascade['plugins']))
240                 $this->plugin_cascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]);
241         }
242         $local = $config_cascade['plugins']['local'];
243         $this->last_local_config_file = array_pop($local);
244         $this->plugin_cascade['local'] = $this->checkRequire(array($this->last_local_config_file));
245         if(is_array($local)) {
246             $this->plugin_cascade['default'] = array_merge($this->plugin_cascade['default'],$this->checkRequire($local));
247         }
248         $this->tmp_plugins = array_merge($this->plugin_cascade['default'],$this->plugin_cascade['local'],$this->plugin_cascade['protected']);
249     }
250
251     function _getListByType($type, $enabled) {
252         $master_list = $enabled ? array_keys(array_filter($this->tmp_plugins)) : array_keys(array_filter($this->tmp_plugins,array($this,'negate')));
253
254         $plugins = array();
255         foreach ($master_list as $plugin) {
256             $dir = $this->get_directory($plugin);
257
258             if (@file_exists(DOKU_PLUGIN."$dir/$type.php")){
259                 $plugins[] = $plugin;
260             } else {
261                 if ($dp = @opendir(DOKU_PLUGIN."$dir/$type/")) {
262                     while (false !== ($component = readdir($dp))) {
263                         if (substr($component,0,1) == '.' || strtolower(substr($component, -4)) != ".php") continue;
264                         if (is_file(DOKU_PLUGIN."$dir/$type/$component")) {
265                             $plugins[] = $plugin.'_'.substr($component, 0, -4);
266                         }
267                     }
268                     closedir($dp);
269                 }
270             }
271         }
272
273         return $plugins;
274     }
275
276     function _splitName($name) {
277         if (array_search($name, array_keys($this->tmp_plugins)) === false) {
278             return explode('_',$name,2);
279         }
280
281         return array($name,'');
282     }
283     function negate($input) {
284         return !(bool) $input;
285     }
286 }