summaryrefslogtreecommitdiff
path: root/inc/plugincontroller.class.php
blob: 734331c94a6500823e0bd431292795c9e09a9153 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
<?php
/**
 * Class to encapsulate access to dokuwiki plugins
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Christopher Smith <chris@jalakai.co.uk>
 */

// plugin related constants
if(!defined('DOKU_PLUGIN'))  define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');

class Doku_Plugin_Controller {

    var $list_bytype = array();
    var $tmp_plugins = array();
    var $plugin_cascade = array('default'=>array(),'local'=>array(),'protected'=>array());
    var $last_local_config_file = '';

    /**
     * Populates the master list of plugins
     */
    function __construct() {
        $this->loadConfig();
        $this->_populateMasterList();
    }

    /**
     * Returns a list of available plugins of given type
     *
     * @param $type  string, plugin_type name;
     *               the type of plugin to return,
     *               use empty string for all types
     * @param $all   bool;
     *               false to only return enabled plugins,
     *               true to return both enabled and disabled plugins
     *
     * @return       array of plugin names
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    function getList($type='',$all=false){

        // request the complete list
        if (!$type) {
            return $all ? array_keys($this->tmp_plugins) : array_keys(array_filter($this->tmp_plugins));
        }

        if (!isset($this->list_bytype[$type]['enabled'])) {
            $this->list_bytype[$type]['enabled'] = $this->_getListByType($type,true);
        }
        if ($all && !isset($this->list_bytype[$type]['disabled'])) {
            $this->list_bytype[$type]['disabled'] = $this->_getListByType($type,false);
        }

        return $all ? array_merge($this->list_bytype[$type]['enabled'],$this->list_bytype[$type]['disabled']) : $this->list_bytype[$type]['enabled'];
    }

    /**
     * Loads the given plugin and creates an object of it
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     *
     * @param  $type     string type of plugin to load
     * @param  $name     string name of the plugin to load
     * @param  $new      bool   true to return a new instance of the plugin, false to use an already loaded instance
     * @param  $disabled bool   true to load even disabled plugins
     * @return objectreference  the plugin object or null on failure
     */
    function load($type,$name,$new=false,$disabled=false){

        //we keep all loaded plugins available in global scope for reuse
        global $DOKU_PLUGINS;
        global $lang;

        list($plugin,$component) = $this->_splitName($name);

        // check if disabled
        if(!$disabled && $this->isdisabled($plugin)){
            return null;
        }

        $class = $type.'_plugin_'.$name;

        //plugin already loaded?
        if(!empty($DOKU_PLUGINS[$type][$name])){
            if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
                return class_exists($class, true) ? new $class : null;
            } else {
                return $DOKU_PLUGINS[$type][$name];
            }
        }

        //construct class and instantiate
        if (!class_exists($class, true)) {

            # the plugin might be in the wrong directory
            $dir = $this->get_directory($plugin);
            $inf = confToHash(DOKU_PLUGIN."$dir/plugin.info.txt");
            if($inf['base'] && $inf['base'] != $plugin){
                msg(sprintf($lang['plugin_install_err'],hsc($plugin),hsc($inf['base'])),-1);
            }
            return null;
        }

        $DOKU_PLUGINS[$type][$name] = new $class;
        return $DOKU_PLUGINS[$type][$name];
    }

    function isdisabled($plugin) {
        return empty($this->tmp_plugins[$plugin]);
    }

    function disable($plugin) {
        if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false;
        $this->tmp_plugins[$plugin] = 0;
        return $this->saveList();
    }

    function enable($plugin) {
        if(array_key_exists($plugin,$this->plugin_cascade['protected'])) return false;
        $this->tmp_plugins[$plugin] = 1;
        return $this->saveList();
    }

    function get_directory($plugin) {
        return $plugin;
    }

    protected function _populateMasterList() {
        if ($dh = @opendir(DOKU_PLUGIN)) {
            $all_plugins = array();
            while (false !== ($plugin = readdir($dh))) {
                if ($plugin[0] == '.') continue;               // skip hidden entries
                if (is_file(DOKU_PLUGIN.$plugin)) continue;    // skip files, we're only interested in directories

                if (substr($plugin,-9) == '.disabled') {
                    // the plugin was disabled by rc2009-01-26
                    // disabling mechanism was changed back very soon again
                    // to keep everything simple we just skip the plugin completely
                } elseif (@file_exists(DOKU_PLUGIN.$plugin.'/disabled')) {
                    // treat this as a default disabled plugin(over-rideable by the plugin manager)
                    // deprecated 2011-09-10 (usage of disabled files)
                    if (empty($this->plugin_cascade['local'][$plugin])) {
                        $all_plugins[$plugin] = 0;
                    } else {
                        $all_plugins[$plugin] = 1;
                    }
                    $this->plugin_cascade['default'][$plugin] = 0;

                } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 0) ||
                          ($plugin === 'plugin' && isset($conf['pluginmanager']) && !$conf['pluginmanager'])){
                    $all_plugins[$plugin] = 0;

                } elseif ((array_key_exists($plugin,$this->tmp_plugins) && $this->tmp_plugins[$plugin] == 1)) {
                    $all_plugins[$plugin] = 1;
                } else {
                    $all_plugins[$plugin] = 1;
                }
            }
            $this->tmp_plugins = $all_plugins;
            if (!file_exists($this->last_local_config_file)) {
                $this->saveList(true);
            }
        }
    }

    protected function checkRequire($files) {
        $plugins = array();
        foreach($files as $file) {
            if(file_exists($file)) {
                @include_once($file);
            }
        }
        return $plugins;
    }

    function getCascade() {
        return $this->plugin_cascade;
    }

    /**
     * Save the current list of plugins
     */
    function saveList($forceSave = false) {
        global $conf;

        if (empty($this->tmp_plugins)) return false;

        // Rebuild list of local settings
        $local_plugins = $this->rebuildLocal();
        if($local_plugins != $this->plugin_cascade['local'] || $forceSave) {
            $file = $this->last_local_config_file;
            $out = "<?php\n/*\n * Local plugin enable/disable settings\n * Auto-generated through plugin/extension manager\n *\n".
                   " * NOTE: Plugins will not be added to this file unless there is a need to override a default setting. Plugins are\n".
                   " *       enabled by default, unless having a 'disabled' file in their plugin folder.\n */\n";
            foreach ($local_plugins as $plugin => $value) {
                $out .= "\$plugins['$plugin'] = $value;\n";
            }
            // backup current file (remove any existing backup)
            if (@file_exists($file)) {
                $backup = $file.'.bak';
                if (@file_exists($backup)) @unlink($backup);
                if (!@copy($file,$backup)) return false;
                if ($conf['fperm']) chmod($backup, $conf['fperm']);
            }
            //check if can open for writing, else restore
            return io_saveFile($file,$out);
        }
        return false;
    }

    /**
     * Rebuild the set of local plugins
     * @return array array of plugins to be saved in end($config_cascade['plugins']['local'])
     */
    function rebuildLocal() {
        //assign to local variable to avoid overwriting
        $backup = $this->tmp_plugins;
        //Can't do anything about protected one so rule them out completely
        $local_default = array_diff_key($backup,$this->plugin_cascade['protected']);
        //Diff between local+default and default
        //gives us the ones we need to check and save
        $diffed_ones = array_diff_key($local_default,$this->plugin_cascade['default']);
        //The ones which we are sure of (list of 0s not in default)
        $sure_plugins = array_filter($diffed_ones,array($this,'negate'));
        //the ones in need of diff
        $conflicts = array_diff_key($local_default,$diffed_ones);
        //The final list
        return array_merge($sure_plugins,array_diff_assoc($conflicts,$this->plugin_cascade['default']));
    }

    /**
     * Build the list of plugins and cascade
     * 
     */
    function loadConfig() {
        global $config_cascade;
        foreach(array('default','protected') as $type) {
            if(array_key_exists($type,$config_cascade['plugins']))
                $this->plugin_cascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]);
        }
        $local = $config_cascade['plugins']['local'];
        $this->last_local_config_file = array_pop($local);
        $this->plugin_cascade['local'] = $this->checkRequire(array($this->last_local_config_file));
        if(is_array($local)) {
            $this->plugin_cascade['default'] = array_merge($this->plugin_cascade['default'],$this->checkRequire($local));
        }
        $this->tmp_plugins = array_merge($this->plugin_cascade['default'],$this->plugin_cascade['local'],$this->plugin_cascade['protected']);
    }

    function _getListByType($type, $enabled) {
        $master_list = $enabled ? array_keys(array_filter($this->tmp_plugins)) : array_keys(array_filter($this->tmp_plugins,array($this,'negate')));

        $plugins = array();
        foreach ($master_list as $plugin) {
            $dir = $this->get_directory($plugin);

            if (@file_exists(DOKU_PLUGIN."$dir/$type.php")){
                $plugins[] = $plugin;
            } else {
                if ($dp = @opendir(DOKU_PLUGIN."$dir/$type/")) {
                    while (false !== ($component = readdir($dp))) {
                        if (substr($component,0,1) == '.' || strtolower(substr($component, -4)) != ".php") continue;
                        if (is_file(DOKU_PLUGIN."$dir/$type/$component")) {
                            $plugins[] = $plugin.'_'.substr($component, 0, -4);
                        }
                    }
                    closedir($dp);
                }
            }
        }

        return $plugins;
    }

    function _splitName($name) {
        if (array_search($name, array_keys($this->tmp_plugins)) === false) {
            return explode('_',$name,2);
        }

        return array($name,'');
    }
    function negate($input) {
        return !(bool) $input;
    }
}