Failed Conditions
Push — psr2-pluginredux ( 89614c )
by Andreas
03:02
created

inc/Extension/PluginController.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 <[email protected]>
7
 */
8
9
namespace dokuwiki\Extension;
10
11
class PluginController
12
{
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
    {
24
        $this->loadConfig();
25
        $this->_populateMasterList();
26
    }
27
28
    /**
29
     * Returns a list of available plugins of given type
30
     *
31
     * @param $type  string, plugin_type name;
32
     *               the type of plugin to return,
33
     *               use empty string for all types
34
     * @param $all   bool;
35
     *               false to only return enabled plugins,
36
     *               true to return both enabled and disabled plugins
37
     *
38
     * @return       array of
39
     *                  - plugin names when $type = ''
40
     *                  - or plugin component names when a $type is given
41
     *
42
     * @author Andreas Gohr <[email protected]>
43
     */
44
    public function getList($type = '', $all = false)
45
    {
46
47
        // request the complete list
48
        if (!$type) {
49
            return $all ? array_keys($this->tmp_plugins) : array_keys(array_filter($this->tmp_plugins));
50
        }
51
52
        if (!isset($this->list_bytype[$type]['enabled'])) {
53
            $this->list_bytype[$type]['enabled'] = $this->_getListByType($type, true);
54
        }
55
        if ($all && !isset($this->list_bytype[$type]['disabled'])) {
56
            $this->list_bytype[$type]['disabled'] = $this->_getListByType($type, false);
57
        }
58
59
        return $all
60
            ? array_merge($this->list_bytype[$type]['enabled'], $this->list_bytype[$type]['disabled'])
61
            : $this->list_bytype[$type]['enabled'];
62
    }
63
64
    /**
65
     * Loads the given plugin and creates an object of it
66
     *
67
     * @author Andreas Gohr <[email protected]>
68
     *
69
     * @param  $type     string type of plugin to load
70
     * @param  $name     string name of the plugin to load
71
     * @param  $new      bool   true to return a new instance of the plugin, false to use an already loaded instance
72
     * @param  $disabled bool   true to load even disabled plugins
73
     * @return PluginInterface|null  the plugin object or null on failure
74
     */
75
    public function load($type, $name, $new = false, $disabled = false)
76
    {
77
78
        //we keep all loaded plugins available in global scope for reuse
79
        global $DOKU_PLUGINS;
80
81
        list($plugin, /* $component */) = $this->_splitName($name);
82
83
        // check if disabled
84
        if (!$disabled && $this->isdisabled($plugin)) {
0 ignored issues
show
Deprecated Code introduced by
The method dokuwiki\Extension\PluginController::isDisabled() has been deprecated with message: in favor of the more sensible isEnabled where the return value matches the enabled state

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
85
            return null;
86
        }
87
88
        $class = $type . '_plugin_' . $name;
89
90
        //plugin already loaded?
91
        if (!empty($DOKU_PLUGINS[$type][$name])) {
92
            if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) {
93
                return class_exists($class, true) ? new $class : null;
94
            } else {
95
                return $DOKU_PLUGINS[$type][$name];
96
            }
97
        }
98
99
        //construct class and instantiate
100
        if (!class_exists($class, true)) {
101
102
            # the plugin might be in the wrong directory
103
            $inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt");
104
            if ($inf['base'] && $inf['base'] != $plugin) {
105
                msg(
106
                    sprintf(
107
                        "Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.",
108
                        hsc($plugin),
109
                        hsc(
110
                            $inf['base']
111
                        )
112
                    ), -1
113
                );
114
            } elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) {
115
                msg(
116
                    sprintf(
117
                        "Plugin name '%s' is not a valid plugin name, only the characters a-z and 0-9 are allowed. " .
118
                        'Maybe the plugin has been installed in the wrong directory?', hsc($plugin)
119
                    ), -1
120
                );
121
            }
122
            return null;
123
        }
124
125
        $DOKU_PLUGINS[$type][$name] = new $class;
126
        return $DOKU_PLUGINS[$type][$name];
127
    }
128
129
    /**
130
     * Whether plugin is disabled
131
     *
132
     * @param string $plugin name of plugin
133
     * @return bool  true disabled, false enabled
134
     * @deprecated in favor of the more sensible isEnabled where the return value matches the enabled state
135
     */
136
    public function isDisabled($plugin)
137
    {
138
        dbg_deprecated('isEnabled()');
139
        return !$this->isEnabled($plugin);
140
    }
141
142
    /**
143
     * Check whether plugin is disabled
144
     *
145
     * @param string $plugin name of plugin
146
     * @return bool  true enabled, false disabled
147
     */
148
    public function isEnabled($plugin)
149
    {
150
        return !empty($this->tmp_plugins[$plugin]);
151
    }
152
153
    /**
154
     * Disable the plugin
155
     *
156
     * @param string $plugin name of plugin
157
     * @return bool  true saving succeed, false saving failed
158
     */
159
    public function disable($plugin)
160
    {
161
        if (array_key_exists($plugin, $this->plugin_cascade['protected'])) return false;
162
        $this->tmp_plugins[$plugin] = 0;
163
        return $this->saveList();
164
    }
165
166
    /**
167
     * Enable the plugin
168
     *
169
     * @param string $plugin name of plugin
170
     * @return bool  true saving succeed, false saving failed
171
     */
172
    public function enable($plugin)
173
    {
174
        if (array_key_exists($plugin, $this->plugin_cascade['protected'])) return false;
175
        $this->tmp_plugins[$plugin] = 1;
176
        return $this->saveList();
177
    }
178
179
    /**
180
     * Returns cascade of the config files
181
     *
182
     * @return array with arrays of plugin configs
183
     */
184
    public function getCascade()
185
    {
186
        return $this->plugin_cascade;
187
    }
188
189
    protected function _populateMasterList()
190
    {
191
        global $conf;
192
193
        if ($dh = @opendir(DOKU_PLUGIN)) {
194
            $all_plugins = array();
195
            while (false !== ($plugin = readdir($dh))) {
196
                if ($plugin[0] == '.') continue;               // skip hidden entries
197
                if (is_file(DOKU_PLUGIN . $plugin)) continue;    // skip files, we're only interested in directories
198
199
                if (array_key_exists($plugin, $this->tmp_plugins) && $this->tmp_plugins[$plugin] == 0) {
200
                    $all_plugins[$plugin] = 0;
201
202
                } elseif ((array_key_exists($plugin, $this->tmp_plugins) && $this->tmp_plugins[$plugin] == 1)) {
203
                    $all_plugins[$plugin] = 1;
204
                } else {
205
                    $all_plugins[$plugin] = 1;
206
                }
207
            }
208
            $this->tmp_plugins = $all_plugins;
209
            if (!file_exists($this->last_local_config_file)) {
210
                $this->saveList(true);
211
            }
212
        }
213
    }
214
215
    /**
216
     * Includes the plugin config $files
217
     * and returns the entries of the $plugins array set in these files
218
     *
219
     * @param array $files list of files to include, latter overrides previous
220
     * @return array with entries of the $plugins arrays of the included files
221
     */
222
    protected function checkRequire($files)
223
    {
224
        $plugins = array();
225
        foreach ($files as $file) {
226
            if (file_exists($file)) {
227
                include_once($file);
228
            }
229
        }
230
        return $plugins;
231
    }
232
233
    /**
234
     * Save the current list of plugins
235
     *
236
     * @param bool $forceSave ;
237
     *              false to save only when config changed
238
     *              true to always save
239
     * @return bool  true saving succeed, false saving failed
240
     */
241
    protected function saveList($forceSave = false)
242
    {
243
        global $conf;
244
245
        if (empty($this->tmp_plugins)) return false;
246
247
        // Rebuild list of local settings
248
        $local_plugins = $this->rebuildLocal();
249
        if ($local_plugins != $this->plugin_cascade['local'] || $forceSave) {
250
            $file = $this->last_local_config_file;
251
            $out = "<?php\n/*\n * Local plugin enable/disable settings\n" .
252
                " * Auto-generated through plugin/extension manager\n *\n" .
253
                " * NOTE: Plugins will not be added to this file unless there " .
254
                "is a need to override a default setting. Plugins are\n" .
255
                " *       enabled by default.\n */\n";
256
            foreach ($local_plugins as $plugin => $value) {
257
                $out .= "\$plugins['$plugin'] = $value;\n";
258
            }
259
            // backup current file (remove any existing backup)
260
            if (file_exists($file)) {
261
                $backup = $file . '.bak';
262
                if (file_exists($backup)) @unlink($backup);
263
                if (!@copy($file, $backup)) return false;
264
                if (!empty($conf['fperm'])) chmod($backup, $conf['fperm']);
265
            }
266
            //check if can open for writing, else restore
267
            return io_saveFile($file, $out);
268
        }
269
        return false;
270
    }
271
272
    /**
273
     * Rebuild the set of local plugins
274
     *
275
     * @return array array of plugins to be saved in end($config_cascade['plugins']['local'])
276
     */
277
    protected function rebuildLocal()
278
    {
279
        //assign to local variable to avoid overwriting
280
        $backup = $this->tmp_plugins;
281
        //Can't do anything about protected one so rule them out completely
282
        $local_default = array_diff_key($backup, $this->plugin_cascade['protected']);
283
        //Diff between local+default and default
284
        //gives us the ones we need to check and save
285
        $diffed_ones = array_diff_key($local_default, $this->plugin_cascade['default']);
286
        //The ones which we are sure of (list of 0s not in default)
287
        $sure_plugins = array_filter($diffed_ones, array($this, 'negate'));
288
        //the ones in need of diff
289
        $conflicts = array_diff_key($local_default, $diffed_ones);
290
        //The final list
291
        return array_merge($sure_plugins, array_diff_assoc($conflicts, $this->plugin_cascade['default']));
292
    }
293
294
    /**
295
     * Build the list of plugins and cascade
296
     *
297
     */
298
    protected function loadConfig()
299
    {
300
        global $config_cascade;
301
        foreach (array('default', 'protected') as $type) {
302
            if (array_key_exists($type, $config_cascade['plugins'])) {
303
                $this->plugin_cascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]);
304
            }
305
        }
306
        $local = $config_cascade['plugins']['local'];
307
        $this->last_local_config_file = array_pop($local);
308
        $this->plugin_cascade['local'] = $this->checkRequire(array($this->last_local_config_file));
309
        if (is_array($local)) {
310
            $this->plugin_cascade['default'] = array_merge(
311
                $this->plugin_cascade['default'],
312
                $this->checkRequire($local)
313
            );
314
        }
315
        $this->tmp_plugins = array_merge(
316
            $this->plugin_cascade['default'],
317
            $this->plugin_cascade['local'],
318
            $this->plugin_cascade['protected']
319
        );
320
    }
321
322
    /**
323
     * Returns a list of available plugin components of given type
324
     *
325
     * @param string $type plugin_type name; the type of plugin to return,
326
     * @param bool $enabled true to return enabled plugins,
327
     *                          false to return disabled plugins
328
     * @return array of plugin components of requested type
329
     */
330
    protected function _getListByType($type, $enabled)
331
    {
332
        $master_list = $enabled
333
            ? array_keys(array_filter($this->tmp_plugins))
334
            : array_keys(array_filter($this->tmp_plugins, array($this, 'negate')));
335
        $plugins = array();
336
337
        foreach ($master_list as $plugin) {
338
339
            if (file_exists(DOKU_PLUGIN . "$plugin/$type.php")) {
340
                $plugins[] = $plugin;
341
                continue;
342
            }
343
344
            $typedir = DOKU_PLUGIN . "$plugin/$type/";
345
            if (is_dir($typedir)) {
346
                if ($dp = opendir($typedir)) {
347
                    while (false !== ($component = readdir($dp))) {
348
                        if (substr($component, 0, 1) == '.' || strtolower(substr($component, -4)) != ".php") continue;
349
                        if (is_file($typedir . $component)) {
350
                            $plugins[] = $plugin . '_' . substr($component, 0, -4);
351
                        }
352
                    }
353
                    closedir($dp);
354
                }
355
            }
356
357
        }//foreach
358
359
        return $plugins;
360
    }
361
362
    /**
363
     * Split name in a plugin name and a component name
364
     *
365
     * @param string $name
366
     * @return array with
367
     *              - plugin name
368
     *              - and component name when available, otherwise empty string
369
     */
370
    protected function _splitName($name)
371
    {
372
        if (array_search($name, array_keys($this->tmp_plugins)) === false) {
373
            return explode('_', $name, 2);
374
        }
375
376
        return array($name, '');
377
    }
378
379
    /**
380
     * Returns inverse boolean value of the input
381
     *
382
     * @param mixed $input
383
     * @return bool inversed boolean value of input
384
     */
385
    protected function negate($input)
386
    {
387
        return !(bool)$input;
388
    }
389
}
390