These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace dokuwiki\Extension; |
||
4 | |||
5 | use dokuwiki\ErrorHandler; |
||
6 | |||
7 | /** |
||
8 | * Class to encapsulate access to dokuwiki plugins |
||
9 | * |
||
10 | * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) |
||
11 | * @author Christopher Smith <[email protected]> |
||
12 | */ |
||
13 | class PluginController |
||
14 | { |
||
15 | /** @var array the types of plugins DokuWiki supports */ |
||
16 | const PLUGIN_TYPES = ['auth', 'admin', 'syntax', 'action', 'renderer', 'helper', 'remote', 'cli']; |
||
17 | |||
18 | protected $listByType = []; |
||
19 | /** @var array all installed plugins and their enabled state [plugin=>enabled] */ |
||
20 | protected $masterList = []; |
||
21 | protected $pluginCascade = ['default' => [], 'local' => [], 'protected' => []]; |
||
22 | protected $lastLocalConfigFile = ''; |
||
23 | |||
24 | /** |
||
25 | * Populates the master list of plugins |
||
26 | */ |
||
27 | public function __construct() |
||
28 | { |
||
29 | $this->loadConfig(); |
||
30 | $this->populateMasterList(); |
||
31 | } |
||
32 | |||
33 | /** |
||
34 | * Returns a list of available plugins of given type |
||
35 | * |
||
36 | * @param $type string, plugin_type name; |
||
37 | * the type of plugin to return, |
||
38 | * use empty string for all types |
||
39 | * @param $all bool; |
||
40 | * false to only return enabled plugins, |
||
41 | * true to return both enabled and disabled plugins |
||
42 | * |
||
43 | * @return array of |
||
44 | * - plugin names when $type = '' |
||
45 | * - or plugin component names when a $type is given |
||
46 | * |
||
47 | * @author Andreas Gohr <[email protected]> |
||
48 | */ |
||
49 | public function getList($type = '', $all = false) |
||
50 | { |
||
51 | |||
52 | // request the complete list |
||
53 | if (!$type) { |
||
54 | return $all ? array_keys($this->masterList) : array_keys(array_filter($this->masterList)); |
||
55 | } |
||
56 | |||
57 | if (!isset($this->listByType[$type]['enabled'])) { |
||
58 | $this->listByType[$type]['enabled'] = $this->getListByType($type, true); |
||
59 | } |
||
60 | if ($all && !isset($this->listByType[$type]['disabled'])) { |
||
61 | $this->listByType[$type]['disabled'] = $this->getListByType($type, false); |
||
62 | } |
||
63 | |||
64 | return $all |
||
65 | ? array_merge($this->listByType[$type]['enabled'], $this->listByType[$type]['disabled']) |
||
66 | : $this->listByType[$type]['enabled']; |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Loads the given plugin and creates an object of it |
||
71 | * |
||
72 | * @param $type string type of plugin to load |
||
73 | * @param $name string name of the plugin to load |
||
74 | * @param $new bool true to return a new instance of the plugin, false to use an already loaded instance |
||
75 | * @param $disabled bool true to load even disabled plugins |
||
76 | * @return PluginInterface|null the plugin object or null on failure |
||
77 | * @author Andreas Gohr <[email protected]> |
||
78 | * |
||
79 | */ |
||
80 | public function load($type, $name, $new = false, $disabled = false) |
||
81 | { |
||
82 | |||
83 | //we keep all loaded plugins available in global scope for reuse |
||
84 | global $DOKU_PLUGINS; |
||
85 | |||
86 | list($plugin, /* $component */) = $this->splitName($name); |
||
87 | |||
88 | // check if disabled |
||
89 | if (!$disabled && !$this->isEnabled($plugin)) { |
||
90 | return null; |
||
91 | } |
||
92 | |||
93 | $class = $type . '_plugin_' . $name; |
||
94 | |||
95 | try { |
||
96 | //plugin already loaded? |
||
97 | if (!empty($DOKU_PLUGINS[$type][$name])) { |
||
98 | if ($new || !$DOKU_PLUGINS[$type][$name]->isSingleton()) { |
||
99 | |||
100 | return class_exists($class, true) ? new $class : null; |
||
101 | } |
||
102 | |||
103 | return $DOKU_PLUGINS[$type][$name]; |
||
104 | } |
||
105 | |||
106 | //construct class and instantiate |
||
107 | if (!class_exists($class, true)) { |
||
108 | # the plugin might be in the wrong directory |
||
109 | $inf = confToHash(DOKU_PLUGIN . "$plugin/plugin.info.txt"); |
||
110 | if ($inf['base'] && $inf['base'] != $plugin) { |
||
111 | msg( |
||
112 | sprintf( |
||
113 | "Plugin installed incorrectly. Rename plugin directory '%s' to '%s'.", |
||
114 | hsc($plugin), |
||
115 | hsc( |
||
116 | $inf['base'] |
||
117 | ) |
||
118 | ), -1 |
||
119 | ); |
||
120 | } elseif (preg_match('/^' . DOKU_PLUGIN_NAME_REGEX . '$/', $plugin) !== 1) { |
||
121 | msg( |
||
122 | sprintf( |
||
123 | "Plugin name '%s' is not a valid plugin name, only the characters a-z and 0-9 are allowed. " . |
||
124 | 'Maybe the plugin has been installed in the wrong directory?', hsc($plugin) |
||
125 | ), -1 |
||
126 | ); |
||
127 | } |
||
128 | return null; |
||
129 | } |
||
130 | $DOKU_PLUGINS[$type][$name] = new $class; |
||
131 | |||
132 | } catch (\Throwable $e) { |
||
0 ignored issues
–
show
|
|||
133 | ErrorHandler::showExceptionMsg($e, sprintf('Failed to load plugin %s', $plugin)); |
||
134 | return null; |
||
135 | } |
||
136 | |||
137 | return $DOKU_PLUGINS[$type][$name]; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Whether plugin is disabled |
||
142 | * |
||
143 | * @param string $plugin name of plugin |
||
144 | * @return bool true disabled, false enabled |
||
145 | * @deprecated in favor of the more sensible isEnabled where the return value matches the enabled state |
||
146 | */ |
||
147 | public function isDisabled($plugin) |
||
148 | { |
||
149 | dbg_deprecated('isEnabled()'); |
||
150 | return !$this->isEnabled($plugin); |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * Check whether plugin is disabled |
||
155 | * |
||
156 | * @param string $plugin name of plugin |
||
157 | * @return bool true enabled, false disabled |
||
158 | */ |
||
159 | public function isEnabled($plugin) |
||
160 | { |
||
161 | return !empty($this->masterList[$plugin]); |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Disable the plugin |
||
166 | * |
||
167 | * @param string $plugin name of plugin |
||
168 | * @return bool true saving succeed, false saving failed |
||
169 | */ |
||
170 | public function disable($plugin) |
||
171 | { |
||
172 | if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false; |
||
173 | $this->masterList[$plugin] = 0; |
||
174 | return $this->saveList(); |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Enable the plugin |
||
179 | * |
||
180 | * @param string $plugin name of plugin |
||
181 | * @return bool true saving succeed, false saving failed |
||
182 | */ |
||
183 | public function enable($plugin) |
||
184 | { |
||
185 | if (array_key_exists($plugin, $this->pluginCascade['protected'])) return false; |
||
186 | $this->masterList[$plugin] = 1; |
||
187 | return $this->saveList(); |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * Returns cascade of the config files |
||
192 | * |
||
193 | * @return array with arrays of plugin configs |
||
194 | */ |
||
195 | public function getCascade() |
||
196 | { |
||
197 | return $this->pluginCascade; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Read all installed plugins and their current enabled state |
||
202 | */ |
||
203 | protected function populateMasterList() |
||
204 | { |
||
205 | if ($dh = @opendir(DOKU_PLUGIN)) { |
||
206 | $all_plugins = array(); |
||
207 | while (false !== ($plugin = readdir($dh))) { |
||
208 | if ($plugin[0] === '.') continue; // skip hidden entries |
||
209 | if (is_file(DOKU_PLUGIN . $plugin)) continue; // skip files, we're only interested in directories |
||
210 | |||
211 | if (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 0) { |
||
212 | $all_plugins[$plugin] = 0; |
||
213 | |||
214 | } elseif (array_key_exists($plugin, $this->masterList) && $this->masterList[$plugin] == 1) { |
||
215 | $all_plugins[$plugin] = 1; |
||
216 | } else { |
||
217 | $all_plugins[$plugin] = 1; |
||
218 | } |
||
219 | } |
||
220 | $this->masterList = $all_plugins; |
||
221 | if (!file_exists($this->lastLocalConfigFile)) { |
||
222 | $this->saveList(true); |
||
223 | } |
||
224 | } |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Includes the plugin config $files |
||
229 | * and returns the entries of the $plugins array set in these files |
||
230 | * |
||
231 | * @param array $files list of files to include, latter overrides previous |
||
232 | * @return array with entries of the $plugins arrays of the included files |
||
233 | */ |
||
234 | protected function checkRequire($files) |
||
235 | { |
||
236 | $plugins = array(); |
||
237 | foreach ($files as $file) { |
||
238 | if (file_exists($file)) { |
||
239 | include_once($file); |
||
240 | } |
||
241 | } |
||
242 | return $plugins; |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Save the current list of plugins |
||
247 | * |
||
248 | * @param bool $forceSave ; |
||
249 | * false to save only when config changed |
||
250 | * true to always save |
||
251 | * @return bool true saving succeed, false saving failed |
||
252 | */ |
||
253 | protected function saveList($forceSave = false) |
||
254 | { |
||
255 | global $conf; |
||
256 | |||
257 | if (empty($this->masterList)) return false; |
||
258 | |||
259 | // Rebuild list of local settings |
||
260 | $local_plugins = $this->rebuildLocal(); |
||
261 | if ($local_plugins != $this->pluginCascade['local'] || $forceSave) { |
||
262 | $file = $this->lastLocalConfigFile; |
||
263 | $out = "<?php\n/*\n * Local plugin enable/disable settings\n" . |
||
264 | " * Auto-generated through plugin/extension manager\n *\n" . |
||
265 | " * NOTE: Plugins will not be added to this file unless there " . |
||
266 | "is a need to override a default setting. Plugins are\n" . |
||
267 | " * enabled by default.\n */\n"; |
||
268 | foreach ($local_plugins as $plugin => $value) { |
||
269 | $out .= "\$plugins['$plugin'] = $value;\n"; |
||
270 | } |
||
271 | // backup current file (remove any existing backup) |
||
272 | if (file_exists($file)) { |
||
273 | $backup = $file . '.bak'; |
||
274 | if (file_exists($backup)) @unlink($backup); |
||
275 | if (!@copy($file, $backup)) return false; |
||
276 | if (!empty($conf['fperm'])) chmod($backup, $conf['fperm']); |
||
277 | } |
||
278 | //check if can open for writing, else restore |
||
279 | return io_saveFile($file, $out); |
||
280 | } |
||
281 | return false; |
||
282 | } |
||
283 | |||
284 | /** |
||
285 | * Rebuild the set of local plugins |
||
286 | * |
||
287 | * @return array array of plugins to be saved in end($config_cascade['plugins']['local']) |
||
288 | */ |
||
289 | protected function rebuildLocal() |
||
290 | { |
||
291 | //assign to local variable to avoid overwriting |
||
292 | $backup = $this->masterList; |
||
293 | //Can't do anything about protected one so rule them out completely |
||
294 | $local_default = array_diff_key($backup, $this->pluginCascade['protected']); |
||
295 | //Diff between local+default and default |
||
296 | //gives us the ones we need to check and save |
||
297 | $diffed_ones = array_diff_key($local_default, $this->pluginCascade['default']); |
||
298 | //The ones which we are sure of (list of 0s not in default) |
||
299 | $sure_plugins = array_filter($diffed_ones, array($this, 'negate')); |
||
300 | //the ones in need of diff |
||
301 | $conflicts = array_diff_key($local_default, $diffed_ones); |
||
302 | //The final list |
||
303 | return array_merge($sure_plugins, array_diff_assoc($conflicts, $this->pluginCascade['default'])); |
||
304 | } |
||
305 | |||
306 | /** |
||
307 | * Build the list of plugins and cascade |
||
308 | * |
||
309 | */ |
||
310 | protected function loadConfig() |
||
311 | { |
||
312 | global $config_cascade; |
||
313 | foreach (array('default', 'protected') as $type) { |
||
314 | if (array_key_exists($type, $config_cascade['plugins'])) { |
||
315 | $this->pluginCascade[$type] = $this->checkRequire($config_cascade['plugins'][$type]); |
||
316 | } |
||
317 | } |
||
318 | $local = $config_cascade['plugins']['local']; |
||
319 | $this->lastLocalConfigFile = array_pop($local); |
||
320 | $this->pluginCascade['local'] = $this->checkRequire(array($this->lastLocalConfigFile)); |
||
321 | if (is_array($local)) { |
||
322 | $this->pluginCascade['default'] = array_merge( |
||
323 | $this->pluginCascade['default'], |
||
324 | $this->checkRequire($local) |
||
325 | ); |
||
326 | } |
||
327 | $this->masterList = array_merge( |
||
328 | $this->pluginCascade['default'], |
||
329 | $this->pluginCascade['local'], |
||
330 | $this->pluginCascade['protected'] |
||
331 | ); |
||
332 | } |
||
333 | |||
334 | /** |
||
335 | * Returns a list of available plugin components of given type |
||
336 | * |
||
337 | * @param string $type plugin_type name; the type of plugin to return, |
||
338 | * @param bool $enabled true to return enabled plugins, |
||
339 | * false to return disabled plugins |
||
340 | * @return array of plugin components of requested type |
||
341 | */ |
||
342 | protected function getListByType($type, $enabled) |
||
343 | { |
||
344 | $master_list = $enabled |
||
345 | ? array_keys(array_filter($this->masterList)) |
||
346 | : array_keys(array_filter($this->masterList, array($this, 'negate'))); |
||
347 | $plugins = array(); |
||
348 | |||
349 | foreach ($master_list as $plugin) { |
||
350 | |||
351 | if (file_exists(DOKU_PLUGIN . "$plugin/$type.php")) { |
||
352 | $plugins[] = $plugin; |
||
353 | continue; |
||
354 | } |
||
355 | |||
356 | $typedir = DOKU_PLUGIN . "$plugin/$type/"; |
||
357 | if (is_dir($typedir)) { |
||
358 | if ($dp = opendir($typedir)) { |
||
359 | while (false !== ($component = readdir($dp))) { |
||
360 | if (strpos($component, '.') === 0 || strtolower(substr($component, -4)) !== '.php') continue; |
||
361 | if (is_file($typedir . $component)) { |
||
362 | $plugins[] = $plugin . '_' . substr($component, 0, -4); |
||
363 | } |
||
364 | } |
||
365 | closedir($dp); |
||
366 | } |
||
367 | } |
||
368 | |||
369 | }//foreach |
||
370 | |||
371 | return $plugins; |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Split name in a plugin name and a component name |
||
376 | * |
||
377 | * @param string $name |
||
378 | * @return array with |
||
379 | * - plugin name |
||
380 | * - and component name when available, otherwise empty string |
||
381 | */ |
||
382 | protected function splitName($name) |
||
383 | { |
||
384 | if (!isset($this->masterList[$name])) { |
||
385 | return explode('_', $name, 2); |
||
386 | } |
||
387 | |||
388 | return array($name, ''); |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * Returns inverse boolean value of the input |
||
393 | * |
||
394 | * @param mixed $input |
||
395 | * @return bool inversed boolean value of input |
||
396 | */ |
||
397 | protected function negate($input) |
||
398 | { |
||
399 | return !(bool)$input; |
||
400 | } |
||
401 | } |
||
402 |
Scrutinizer analyzes your
composer.json
/composer.lock
file if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.