Passed
Push — master ( 37cafd...a8b392 )
by
unknown
07:18
created

PluginManager::migrateLegacyFileReferences()   B

Complexity

Conditions 11
Paths 5

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 16
c 0
b 0
f 0
nc 5
nop 3
dl 0
loc 27
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
define('TYPE_PLUGIN', 1);
4
define('TYPE_MODULE', 2);
5
define('TYPE_CONFIG', 3);
6
define('TYPE_NOTIFIER', 4);
7
8
define('DEPEND_DEPENDS', 1);
9
define('DEPEND_REQUIRES', 2);
10
define('DEPEND_RECOMMENDS', 3);
11
define('DEPEND_SUGGESTS', 4);
12
13
/**
14
 * Managing component for all plugins.
15
 *
16
 * This class handles all the plugin interaction with the webaccess on the server side.
17
 */
18
class PluginManager {
19
	// True if the Plugin framework is enabled
20
	public $enabled;
21
22
	// The path to the folder which contains the plugins
23
	public $pluginpath;
24
25
	// The path to the folder which holds the configuration for the plugins
26
	// This folder has same structure as $this->pluginpath
27
	public $pluginconfigpath;
28
29
	// List of all plugins and their data
30
	public $plugindata;
31
32
	// List of the plugins in the order in which
33
	// they should be loaded
34
	public $pluginorder;
35
36
	/**
37
	 * List of all hooks registered by plugins.
38
	 * [eventID][] = plugin.
39
	 */
40
	public $hooks;
41
42
	/**
43
	 * List of all plugin objects
44
	 * [pluginname] = pluginObj.
45
	 */
46
	public $plugins;
47
48
	/**
49
	 * List of all provided modules
50
	 * [modulename] = moduleFile.
51
	 */
52
	public $modules;
53
54
	/**
55
	 * List of all provided notifiers
56
	 * [notifiername] = notifierFile.
57
	 */
58
	public $notifiers;
59
60
	/**
61
	 * Mapping of legacy plugin identifiers to their canonical replacements.
62
	 *
63
	 * @var array<string, string>
64
	 */
65
	private $pluginAliases = [
66
		'filesbackendOwncloud' => 'filesbackendDefault',
67
	];
68
69
	/**
70
	 * List of sessiondata from plugins.
71
	 * [pluginname] = sessiondata.
72
	 */
73
	public $sessionData;
74
75
	/**
76
	 * Mapping for the XML 'load' attribute values
77
	 * on the <serverfile>, <clientfile> or <resourcefile> element
78
	 * to the corresponding define.
79
	 */
80
	public $loadMap = [
81
		'release' => LOAD_RELEASE,
82
		'debug' => LOAD_DEBUG,
83
		'source' => LOAD_SOURCE,
84
	];
85
86
	/**
87
	 * Mapping for the XML 'type' attribute values
88
	 * on the <serverfile> element to the corresponding define.
89
	 */
90
	public $typeMap = [
91
		'plugin' => TYPE_PLUGIN,
92
		'module' => TYPE_MODULE,
93
		'notifier' => TYPE_NOTIFIER,
94
	];
95
96
	/**
97
	 * Mapping for the XML 'type' attribute values
98
	 * on the <depends> element to the corresponding define.
99
	 */
100
	public $dependMap = [
101
		'depends' => DEPEND_DEPENDS,
102
		'requires' => DEPEND_REQUIRES,
103
		'recommends' => DEPEND_RECOMMENDS,
104
		'suggests' => DEPEND_SUGGESTS,
105
	];
106
107
	/**
108
	 * Constructor.
109
	 *
110
	 * @param mixed $enable
111
	 */
112
	public function __construct($enable = ENABLE_PLUGINS) {
113
		$this->enabled = $enable && defined('PATH_PLUGIN_DIR');
114
		$this->plugindata = [];
115
		$this->pluginorder = [];
116
		$this->hooks = [];
117
		$this->plugins = [];
118
		$this->modules = [];
119
		$this->notifiers = [];
120
		$this->sessionData = false;
121
		if ($this->enabled) {
122
			$this->pluginpath = PATH_PLUGIN_DIR;
123
			$this->pluginconfigpath = PATH_PLUGIN_CONFIG_DIR;
124
		}
125
	}
126
127
	/**
128
	 * pluginsEnabled.
129
	 *
130
	 * Checks whether the plugins have been enabled by checking if the proper
131
	 * configuration keys are set.
132
	 *
133
	 * @return bool returns true when plugins enabled, false when not
134
	 */
135
	public function pluginsEnabled() {
136
		return $this->enabled;
137
	}
138
139
	/**
140
	 * detectPlugins.
141
	 *
142
	 * Detecting the installed plugins either by using the already ready data
143
	 * from the state object or otherwise read in all the data and write it into
144
	 * the state.
145
	 *
146
	 * @param string $disabled the list of plugins to disable, this list is separated
147
	 *                         by the ';' character
148
	 */
149
	public function detectPlugins($disabled = '') {
150
		if (!$this->pluginsEnabled()) {
151
			return false;
152
		}
153
154
		// Get the plugindata from the state.
155
		$pluginState = new State('plugin');
156
		$pluginState->open();
157
158
		if (!DEBUG_PLUGINS_DISABLE_CACHE) {
159
			$this->plugindata = $pluginState->read("plugindata");
160
			$pluginOrder = $pluginState->read("pluginorder");
161
			$this->plugindata = $this->normalizePluginData($this->plugindata ?? []);
0 ignored issues
show
Bug introduced by
It seems like $this->plugindata ?? array() can also be of type string; however, parameter $plugindata of PluginManager::normalizePluginData() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

161
			$this->plugindata = $this->normalizePluginData(/** @scrutinizer ignore-type */ $this->plugindata ?? []);
Loading history...
162
			$this->pluginorder = $this->normalizePluginOrder(empty($pluginOrder) ? [] : $pluginOrder);
0 ignored issues
show
Bug introduced by
It seems like empty($pluginOrder) ? array() : $pluginOrder can also be of type string; however, parameter $pluginorder of PluginManager::normalizePluginOrder() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
			$this->pluginorder = $this->normalizePluginOrder(/** @scrutinizer ignore-type */ empty($pluginOrder) ? [] : $pluginOrder);
Loading history...
163
		}
164
165
		// If no plugindata has been stored yet, get it from the plugins dir.
166
		if (!$this->plugindata || !$this->pluginorder) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->plugindata of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->pluginorder of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
167
			$disabledPlugins = [];
168
			if (!empty($disabled)) {
169
				$disabledPlugins = array_map([$this, 'normalizePluginName'], explode(';', $disabled));
170
			}
171
172
			// Read all plugins from the plugins folders.
173
			$this->plugindata = $this->readPluginFolder($disabledPlugins);
174
			$this->plugindata = $this->normalizePluginData($this->plugindata);
175
176
			// Check if any plugin directories found or not
177
			if (!empty($this->plugindata)) {
178
				// Not we update plugindata and pluginorder based on the configured dependencies.
179
				// Note that each change to plugindata requires the requirements and dependencies
180
				// to be recalculated.
181
				while (!$this->pluginorder || !$this->validatePluginRequirements()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->pluginorder of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
182
					// Generate the order in which the plugins should be loaded,
183
					// this uses the $this->plugindata as base.
184
					$pluginOrder = $this->buildPluginDependencyOrder();
185
					$this->pluginorder = $this->normalizePluginOrder(empty($pluginOrder) ? [] : $pluginOrder);
186
				}
187
			}
188
		}
189
190
		// Decide whether to show password plugin in settings:
191
		// - show if the users are in a db
192
		// - don't show if the users are in ldap
193
		if (isset($this->plugindata['passwd'], $GLOBALS['usersinldap']) && $GLOBALS['usersinldap']) {
194
			unset($this->plugindata['passwd']);
195
			if (($passwdKey = array_search('passwd', $this->pluginorder)) !== false) {
196
				unset($this->pluginorder[$passwdKey]);
197
			}
198
		}
199
200
		// Write the plugindata back to the state
201
		if (!DEBUG_PLUGINS_DISABLE_CACHE) {
202
			$pluginState->write("plugindata", $this->plugindata);
203
			$pluginState->write("pluginorder", $this->pluginorder);
204
		}
205
206
		// Free the state again.
207
		$pluginState->close();
208
	}
209
210
	/**
211
	 * Convert legacy plugin identifiers to their canonical replacements.
212
	 *
213
	 * @param string $pluginname
214
	 * @return string
215
	 */
216
	private function normalizePluginName($pluginname) {
217
		return $this->pluginAliases[$pluginname] ?? $pluginname;
218
	}
219
220
	/**
221
	 * Normalize the plugindata array by applying legacy aliases.
222
	 *
223
	 * @param array $plugindata
224
	 * @return array
225
	 */
226
	private function normalizePluginData(array $plugindata) {
227
		foreach ($this->pluginAliases as $legacy => $canonical) {
228
			$legacyData = $plugindata[$legacy] ?? null;
229
			$canonicalData = $plugindata[$canonical] ?? null;
230
			$freshData = $this->processPlugin($canonical);
231
			if ($freshData !== null) {
232
				$canonicalData = $freshData;
233
			}
234
			elseif ($canonicalData === null && $legacyData !== null) {
235
				$canonicalData = $legacyData;
236
			}
237
238
			if ($canonicalData !== null) {
239
				$canonicalData['pluginname'] = $canonical;
240
				$canonicalData = $this->migrateLegacyFileReferences($canonicalData, $legacy, $canonical);
241
				$plugindata[$canonical] = $canonicalData;
242
			}
243
244
			if ($legacyData !== null) {
245
				unset($plugindata[$legacy]);
246
			}
247
		}
248
249
		return $plugindata;
250
	}
251
252
	/**
253
	 * Replace legacy filenames inside plugin metadata so cached state stays in sync with the new canonical plugin.
254
	 *
255
	 * @param array  $pluginData
256
	 * @param string $legacy
257
	 * @param string $canonical
258
	 * @return array
259
	 */
260
	private function migrateLegacyFileReferences(array $pluginData, $legacy, $canonical) {
261
		$search = $legacy;
262
		$replace = $canonical;
263
		if (!isset($pluginData['components'])) {
264
			return $pluginData;
265
		}
266
267
		foreach ($pluginData['components'] as $componentIndex => $component) {
268
			foreach (['clientfiles', 'resourcefiles', 'serverfiles'] as $group) {
269
				if (empty($component[$group])) {
270
					continue;
271
				}
272
				foreach ($component[$group] as $load => $files) {
273
					if (empty($files) || !is_array($files)) {
274
						continue;
275
					}
276
					foreach ($files as $fileIndex => $file) {
277
						if (!is_array($file) || !isset($file['file'])) {
278
							continue;
279
						}
280
						$pluginData['components'][$componentIndex][$group][$load][$fileIndex]['file'] = str_replace($search, $replace, $file['file']);
281
					}
282
				}
283
			}
284
		}
285
286
		return $pluginData;
287
	}
288
289
	/**
290
	 * Normalize a list of plugin names by applying legacy aliases.
291
	 *
292
	 * @param array $pluginorder
293
	 * @return array
294
	 */
295
	private function normalizePluginOrder(array $pluginorder) {
296
		$normalized = [];
297
		foreach ($pluginorder as $pluginname) {
298
			$canonical = $this->normalizePluginName($pluginname);
299
			if (!in_array($canonical, $normalized, true)) {
300
				$normalized[] = $canonical;
301
			}
302
		}
303
304
		return $normalized;
305
	}
306
307
	/**
308
	 * readPluginFolder.
309
	 *
310
	 * Read all subfolders of the directory referenced to by $this->pluginpath,
311
	 * for each subdir, we $this->processPlugin it as a plugin.
312
	 *
313
	 * @param $disabledPlugins Array The list of disabled plugins, the subfolders
314
	 *                         named as any of the strings inside this list will not be processed
315
	 *
316
	 * @returns Array The object containing all the processed plugins. The object is a key-value'
317
	 * object where the key is the unique name of the plugin, and the value the parsed data.
318
	 */
319
	public function readPluginFolder($disabledPlugins) {
320
		$data = [];
321
322
		$pluginsdir = opendir($this->pluginpath);
323
		if ($pluginsdir) {
0 ignored issues
show
introduced by
$pluginsdir is of type resource, thus it always evaluated to false.
Loading history...
324
			while (($plugin = readdir($pluginsdir)) !== false) {
325
				if ($plugin != '.' && $plugin != '..' && !in_array($plugin, $disabledPlugins)) {
326
					if (is_dir($this->pluginpath . DIRECTORY_SEPARATOR . $plugin)) {
327
						if (is_file($this->pluginpath . DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR . 'manifest.xml')) {
328
							$processed = $this->processPlugin($plugin);
329
							$data[$processed['pluginname']] = $processed;
330
						}
331
					}
332
				}
333
			}
334
335
			closedir($pluginsdir);
336
		}
337
338
		return $data;
339
	}
340
341
	/**
342
	 * validatePluginRequirements.
343
	 *
344
	 * Go over the parsed $this->plugindata and check if all requirements are met.
345
	 * This means that for each plugin which defined a "depends" or "requires" plugin
346
	 * we check if those plugins are present on the system. If some dependencies are
347
	 * not met, the plugin is removed from $this->plugindata.
348
	 *
349
	 * @return bool False if the $this->plugindata was modified by this function
350
	 */
351
	public function validatePluginRequirements() {
352
		$modified = false;
353
354
		do {
355
			$success = true;
356
357
			foreach ($this->plugindata as $pluginname => &$plugin) {
358
				// Check if the plugin had any dependencies
359
				// declared in the manifest. If not, they are obviously
360
				// met. Otherwise we have to check the type of dependencies
361
				// which were declared.
362
				if ($plugin['dependencies']) {
363
					// We only care about the 'depends' and 'requires'
364
					// dependency types. All others are not blocking.
365
					foreach ($plugin['dependencies'][DEPEND_DEPENDS] as &$depends) {
366
						if (!$this->pluginExists($depends['plugin'])) {
367
							if (DEBUG_PLUGINS) {
368
								dump('[PLUGIN ERROR] Plugin "' . $pluginname . '" requires "' . $depends['plugin'] . '" which could not be found');
369
							}
370
							unset($this->plugindata[$pluginname]);
371
							// Indicate failure, as we have removed a plugin, and the requirements
372
							// must be rechecked.
373
							$success = false;
374
							// Indicate that the plugindata was modified.
375
							$modified = true;
376
						}
377
					}
378
379
					foreach ($plugin['dependencies'][DEPEND_REQUIRES] as &$depends) {
380
						if (!$this->pluginExists($depends['plugin'])) {
381
							if (DEBUG_PLUGINS) {
382
								dump('[PLUGIN ERROR] Plugin "' . $pluginname . '" requires "' . $depends['plugin'] . '" which could not be found');
383
							}
384
							unset($this->plugindata[$pluginname]);
385
							// Indicate failure, as we have removed a plugin, and the requirements
386
							// must be rechecked.
387
							$success = false;
388
							// Indicate that the plugindata was modified.
389
							$modified = true;
390
						}
391
					}
392
				}
393
			}
394
395
			// If a plugin was removed because of a failed dependency or requirement,
396
			// then we have to redo the cycle, because another plugin might have depended
397
			// on the removed plugin.
398
		}
399
		while (!$success);
400
401
		return !$modified;
402
	}
403
404
	/**
405
	 * buildPluginDependencyOrder.
406
	 *
407
	 * Go over the parsed $this->plugindata and create a ordered list of the plugins, resembling
408
	 * the order in which those plugins should be loaded. This goes over all plugins to read
409
	 * the 'dependencies' data and ordering those plugins based on the DEPEND_DEPENDS dependency type.
410
	 *
411
	 * In case of circular dependencies, the $this->plugindata object might be altered to remove
412
	 * the plugin which the broken dependencies.
413
	 *
414
	 * @return array The array of plugins in the order of which they should be loaded
415
	 */
416
	public function buildPluginDependencyOrder() {
417
		$plugins = array_keys($this->plugindata);
418
		$ordered = [];
419
		$failedCount = 0;
420
421
		// We are going to keep it quite simple, we keep looping over the $plugins
422
		// array until it is empty. Each time we find a plugin for which all dependencies
423
		// are met, we can put it on the $ordered list. If we have looped over the list twice,
424
		// without updated the $ordered list in any way, then we have found a circular dependency
425
		// and we cannot resolve the plugins correctly.
426
		while (!empty($plugins)) {
427
			$pluginname = array_shift($plugins);
428
			$plugin = $this->plugindata[$pluginname];
429
			$accepted = true;
430
431
			// Go over all dependencies to see if they have been met.
432
			if ($plugin['dependencies']) {
433
				for ($i = 0, $len = count($plugin['dependencies'][DEPEND_DEPENDS]); $i < $len; ++$i) {
434
					$dependency = $plugin['dependencies'][DEPEND_DEPENDS][$i];
435
					if (array_search($dependency['plugin'], $ordered) === false) {
436
						$accepted = false;
437
						break;
438
					}
439
				}
440
			}
441
442
			if ($accepted) {
443
				// The dependencies for this plugin have been met, we can push
444
				// the plugin into the tree.
445
				$ordered[] = $pluginname;
446
447
				// Reset the $failedCount property, this ensures that we can keep
448
				// looping because other plugins with previously unresolved dependencies
449
				// could possible be resolved.
450
				$failedCount = 0;
451
			}
452
			else {
453
				// The dependencies for this plugin have not been met, we push
454
				// the plugin back to the list and we will retry later when the
455
				// $ordered list contains more items.
456
				$plugins[] = $pluginname;
457
458
				// Increase the $failedCount property, this prevents that we could go into
459
				// an infinite loop when a circular dependency was defined.
460
				++$failedCount;
461
			}
462
463
			// If the $failedCount matches the the number of items in the $plugins array,
464
			// it means that all unordered plugins have unmet dependencies. This could only
465
			// happen for circular dependencies. In that case we will refuse to load those plugins.
466
			if ($failedCount === count($plugins)) {
467
				foreach ($plugins as $plugin) {
468
					if (DEBUG_PLUGINS) {
469
						dump('[PLUGIN ERROR] Circular dependency detected for plugin "' . $plugin . '"');
470
					}
471
					unset($this->plugindata[$plugin]);
472
				}
473
				break;
474
			}
475
		}
476
477
		return $ordered;
478
	}
479
480
	/**
481
	 * initPlugins.
482
	 *
483
	 * This function includes the server plugin classes, instantiate and
484
	 * initialize them.
485
	 *
486
	 * @param number $load One of LOAD_RELEASE, LOAD_DEBUG, LOAD_SOURCE. This will filter
487
	 *                     the files based on the 'load' attribute.
488
	 */
489
	public function initPlugins($load = LOAD_RELEASE) {
490
		if (!$this->pluginsEnabled()) {
491
			return false;
492
		}
493
494
		$files = $this->getServerFiles($load);
495
		foreach ($files['server'] as $file) {
496
			include_once $file;
497
		}
498
499
		// Include the root files of all the plugins and instantiate the plugin
500
		foreach ($this->pluginorder as $plugName) {
501
			$pluginClassName = 'Plugin' . $plugName;
502
			if (class_exists($pluginClassName)) {
503
				$this->plugins[$plugName] = new $pluginClassName();
504
				$this->plugins[$plugName]->setPluginName($plugName);
505
				$this->plugins[$plugName]->init();
506
			}
507
		}
508
509
		$this->modules = $files['modules'];
510
		$this->notifiers = $files['notifiers'];
511
	}
512
513
	/**
514
	 * processPlugin.
515
	 *
516
	 * Read in the manifest and get the files that need to be included
517
	 * for placing hooks, defining modules, etc.
518
	 *
519
	 * @param $dirname string name of the directory of the plugin
520
	 *
521
	 * @return array The plugin data read from the given directory
522
	 */
523
	public function processPlugin($dirname) {
524
		// Read XML manifest file of plugin
525
		$handle = fopen($this->pluginpath . DIRECTORY_SEPARATOR . $dirname . DIRECTORY_SEPARATOR . 'manifest.xml', 'rb');
526
		$xml = '';
527
		if ($handle) {
0 ignored issues
show
introduced by
$handle is of type resource, thus it always evaluated to false.
Loading history...
528
			while (!feof($handle)) {
529
				$xml .= fread($handle, 4096);
530
			}
531
			fclose($handle);
532
		}
533
534
		$plugindata = $this->extractPluginDataFromXML($xml, $dirname);
535
		if ($plugindata) {
536
			// Apply the name to the object
537
			$plugindata['pluginname'] = $dirname;
538
		}
539
		else {
540
			if (DEBUG_PLUGINS) {
541
				dump('[PLUGIN ERROR] Plugin "' . $dirname . '" has an invalid manifest.');
542
			}
543
		}
544
545
		return $plugindata;
546
	}
547
548
	/**
549
	 * loadSessionData.
550
	 *
551
	 * Loads sessiondata of the plugins from disk.
552
	 * To improve performance the data is only loaded if a
553
	 * plugin requests (reads or saves) the data.
554
	 *
555
	 * @param $pluginname string Identifier of the plugin
556
	 */
557
	public function loadSessionData($pluginname) {
558
		$canonicalName = $this->normalizePluginName($pluginname);
559
560
		// lazy reading of sessionData
561
		if (!$this->sessionData) {
562
			$sessState = new State('plugin_sessiondata');
563
			$sessState->open();
564
			$this->sessionData = $sessState->read("sessionData");
565
			if (!isset($this->sessionData) || $this->sessionData == "") {
566
				$this->sessionData = [];
567
			}
568
			$sessState->close();
569
		}
570
571
		if ($pluginname !== $canonicalName && isset($this->sessionData[$pluginname])) {
572
			// migrate legacy session data key to canonical name
573
			$this->sessionData[$canonicalName] = $this->sessionData[$pluginname];
574
			unset($this->sessionData[$pluginname]);
575
		}
576
577
		if ($this->pluginExists($canonicalName)) {
578
			if (!isset($this->sessionData[$canonicalName])) {
579
				$this->sessionData[$canonicalName] = [];
580
			}
581
			$this->plugins[$canonicalName]->setSessionData($this->sessionData[$canonicalName]);
582
		}
583
	}
584
585
	/**
586
	 * saveSessionData.
587
	 *
588
	 * Saves sessiondata of the plugins to the disk.
589
	 *
590
	 * @param $pluginname string Identifier of the plugin
591
	 */
592
	public function saveSessionData($pluginname) {
593
		$canonicalName = $this->normalizePluginName($pluginname);
594
		if ($this->pluginExists($canonicalName)) {
595
			$this->sessionData[$canonicalName] = $this->plugins[$canonicalName]->getSessionData();
596
		}
597
		if ($this->sessionData) {
598
			$sessState = new State('plugin_sessiondata');
599
			$sessState->open();
600
			$sessState->write("sessionData", $this->sessionData);
601
			$sessState->close();
602
		}
603
	}
604
605
	/**
606
	 * pluginExists.
607
	 *
608
	 * Checks if plugin exists.
609
	 *
610
	 * @param $pluginname string Identifier of the plugin
611
	 *
612
	 * @return bool true when plugin exists, false when it does not
613
	 */
614
	public function pluginExists($pluginname) {
615
		$canonicalName = $this->normalizePluginName($pluginname);
616
		if (isset($this->plugindata[$canonicalName])) {
617
			return true;
618
		}
619
620
		return false;
621
	}
622
623
	/**
624
	 * getModuleFilePath.
625
	 *
626
	 * Obtain the filepath of the given modulename
627
	 *
628
	 * @param $modulename string Identifier of the modulename
629
	 *
630
	 * @return string The path to the file for the module
631
	 */
632
	public function getModuleFilePath($modulename) {
633
		return $this->modules[$modulename] ?? false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->modules[$modulename] ?? false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
634
	}
635
636
	/**
637
	 * getNotifierFilePath.
638
	 *
639
	 * Obtain the filepath of the given notifiername
640
	 *
641
	 * @param $notifiername string Identifier of the notifiername
642
	 *
643
	 * @return string The path to the file for the notifier
644
	 */
645
	public function getNotifierFilePath($notifiername) {
646
		return $this->notifiers[$notifiername] ?? false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->notifiers[$notifiername] ?? false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
647
	}
648
649
	/**
650
	 * registerHook.
651
	 *
652
	 * This function allows the plugin to register their hooks.
653
	 *
654
	 * @param $eventID    string Identifier of the event where this hook must be triggered
655
	 * @param $pluginName string Name of the plugin that is registering this hook
656
	 */
657
	public function registerHook($eventID, $pluginName) {
658
		$canonicalName = $this->normalizePluginName($pluginName);
659
		$this->hooks[$eventID][$canonicalName] = $canonicalName;
660
	}
661
662
	/**
663
	 * triggerHook.
664
	 *
665
	 * This function will call all the registered hooks when their event is triggered.
666
	 *
667
	 * @param $eventID string Identifier of the event that has just been triggered
668
	 * @param $data    mixed (Optional) Usually an array of data that the callback function can modify
669
	 *
670
	 * @return mixed data that has been changed by plugins
671
	 */
672
	public function triggerHook($eventID, $data = []) {
673
		if (isset($this->hooks[$eventID]) && is_array($this->hooks[$eventID])) {
674
			foreach ($this->hooks[$eventID] as $key => $pluginname) {
675
				$this->plugins[$pluginname]->execute($eventID, $data);
676
			}
677
		}
678
679
		return $data;
680
	}
681
682
	/**
683
	 * getPluginVersion.
684
	 *
685
	 * Function is used to prepare version information array from plugindata.
686
	 *
687
	 * @return array the array of plugins version information
688
	 */
689
	public function getPluginsVersion() {
690
		$versionInfo = [];
691
		foreach ($this->plugindata as $pluginName => $data) {
692
			$versionInfo[$pluginName] = $data["version"];
693
		}
694
695
		return $versionInfo;
696
	}
697
698
	/**
699
	 * getServerFilesForComponent.
700
	 *
701
	 * Called by getServerFiles() to return the list of files which are provided
702
	 * for the given component in a particular plugin.
703
	 * The paths which are returned start at the root of the webapp.
704
	 *
705
	 * This function might call itself recursively if it couldn't find any files for
706
	 * the given $load type. If no 'source' files are found, it will obtain the 'debug'
707
	 * files, if that too files it will fallback to 'release' files. If the latter is
708
	 * not found either, no files are returned.
709
	 *
710
	 * @param string $pluginname The name of the plugin (this is used in the pathname)
711
	 * @param array  $component  The component to read the serverfiles from
712
	 * @param number $load       One of LOAD_RELEASE, LOAD_DEBUG, LOAD_SOURCE. This will filter
713
	 *                           the files based on the 'load' attribute.
714
	 *
715
	 * @return array list of paths to the files in this component
716
	 */
717
	public function getServerFilesForComponent($pluginname, $component, $load) {
718
		$pluginname = $this->normalizePluginName($pluginname);
719
		$componentfiles = [
720
			'server' => [],
721
			'modules' => [],
722
			'notifiers' => [],
723
		];
724
725
		foreach ($component['serverfiles'][$load] as &$file) {
726
			switch ($file['type']) {
727
				case TYPE_CONFIG:
728
					$componentfiles['server'][] = $this->pluginconfigpath . DIRECTORY_SEPARATOR . $pluginname . DIRECTORY_SEPARATOR . $file['file'];
729
					break;
730
731
				case TYPE_PLUGIN:
732
					$componentfiles['server'][] = $this->pluginpath . DIRECTORY_SEPARATOR . $pluginname . DIRECTORY_SEPARATOR . $file['file'];
733
					break;
734
735
				case TYPE_MODULE:
736
					$componentfiles['modules'][$file['module']] = $this->pluginpath . DIRECTORY_SEPARATOR . $pluginname . DIRECTORY_SEPARATOR . $file['file'];
737
					break;
738
739
				case TYPE_NOTIFIER:
740
					$componentfiles['notifiers'][$file['notifier']] = $this->pluginpath . DIRECTORY_SEPARATOR . $pluginname . DIRECTORY_SEPARATOR . $file['file'];
741
					break;
742
			}
743
		}
744
		unset($file);
745
746
		return $componentfiles;
747
	}
748
749
	/**
750
	 * getServerFiles.
751
	 *
752
	 * Returning an array of paths to files that need to be included.
753
	 * The paths which are returned start at the root of the webapp.
754
	 *
755
	 * This calls getServerFilesForComponent() to obtain the files
756
	 * for each component inside the requested plugin
757
	 *
758
	 * @param number $load One of LOAD_RELEASE, LOAD_DEBUG, LOAD_SOURCE. This will filter
759
	 *                     the files based on the 'load' attribute.
760
	 *
761
	 * @return array list of paths to files
762
	 */
763
	public function getServerFiles($load = LOAD_RELEASE) {
764
		$files = [
765
			'server' => [],
766
			'modules' => [],
767
			'notifiers' => [],
768
		];
769
770
		foreach ($this->pluginorder as $pluginname) {
771
			$plugin = &$this->plugindata[$pluginname];
772
			foreach ($plugin['components'] as &$component) {
773
				if (!empty($component['serverfiles'][$load])) {
774
					$componentfiles = $this->getServerFilesForComponent($pluginname, $component, $load);
775
				}
776
				elseif ($load === LOAD_SOURCE && !empty($component['serverfiles'][LOAD_DEBUG])) {
777
					$componentfiles = $this->getServerFilesForComponent($pluginname, $component, LOAD_DEBUG);
778
				}
779
				elseif ($load !== LOAD_RELEASE && !empty($component['serverfiles'][LOAD_RELEASE])) {
780
					$componentfiles = $this->getServerFilesForComponent($pluginname, $component, LOAD_RELEASE);
781
				} // else tough luck, at least release should be present
782
783
				if (isset($componentfiles)) {
784
					$files['server'] = array_merge($files['server'], $componentfiles['server']);
785
					$files['modules'] = array_merge($files['modules'], $componentfiles['modules']);
786
					$files['notifiers'] = array_merge($files['notifiers'], $componentfiles['notifiers']);
787
					unset($componentfiles);
788
				}
789
			}
790
			unset($component);
791
		}
792
		unset($plugin);
793
794
		return $files;
795
	}
796
797
	/**
798
	 * getClientFilesForComponent.
799
	 *
800
	 * Called by getClientFiles() to return the list of files which are provided
801
	 * for the given component in a particular plugin.
802
	 * The paths which are returned start at the root of the webapp.
803
	 *
804
	 * This function might call itself recursively if it couldn't find any files for
805
	 * the given $load type. If no 'source' files are found, it will obtain the 'debug'
806
	 * files, if that too files it will fallback to 'release' files. If the latter is
807
	 * not found either, no files are returned.
808
	 *
809
	 * @param string $pluginname The name of the plugin (this is used in the pathname)
810
	 * @param array  $component  The component to read the clientfiles from
811
	 * @param number $load       One of LOAD_RELEASE, LOAD_DEBUG, LOAD_SOURCE. This will filter
812
	 *                           the files based on the 'load' attribute.
813
	 *
814
	 * @return array list of paths to the files in this component
815
	 */
816
	public function getClientFilesForComponent($pluginname, $component, $load) {
817
		$pluginname = $this->normalizePluginName($pluginname);
818
		$componentfiles = [];
819
820
		foreach ($component['clientfiles'][$load] as &$file) {
821
			$componentfiles[] = $this->pluginpath . DIRECTORY_SEPARATOR . $pluginname . DIRECTORY_SEPARATOR . $file['file'];
822
		}
823
		unset($file);
824
825
		return $componentfiles;
826
	}
827
828
	/**
829
	 * getClientFiles.
830
	 *
831
	 * Returning an array of paths to files that need to be included.
832
	 * The paths which are returned start at the root of the webapp.
833
	 *
834
	 * This calls getClientFilesForComponent() to obtain the files
835
	 * for each component inside each plugin.
836
	 *
837
	 * @param number $load One of LOAD_RELEASE, LOAD_DEBUG, LOAD_SOURCE. This will filter
838
	 *                     the files based on the 'load' attribute.
839
	 *
840
	 * @return array list of paths to files
841
	 */
842
	public function getClientFiles($load = LOAD_RELEASE) {
843
		$files = [];
844
845
		foreach ($this->pluginorder as $pluginname) {
846
			$plugin = &$this->plugindata[$pluginname];
847
			foreach ($plugin['components'] as &$component) {
848
				if (!empty($component['clientfiles'][$load])) {
849
					$componentfiles = $this->getClientFilesForComponent($pluginname, $component, $load);
850
				}
851
				elseif ($load === LOAD_SOURCE && !empty($component['clientfiles'][LOAD_DEBUG])) {
852
					$componentfiles = $this->getClientFilesForComponent($pluginname, $component, LOAD_DEBUG);
853
				}
854
				elseif ($load !== LOAD_RELEASE && !empty($component['clientfiles'][LOAD_RELEASE])) {
855
					$componentfiles = $this->getClientFilesForComponent($pluginname, $component, LOAD_RELEASE);
856
				} // else tough luck, at least release should be present
857
858
				if (isset($componentfiles)) {
859
					$files = array_merge($files, $componentfiles);
860
					unset($componentfiles);
861
				}
862
			}
863
			unset($component);
864
		}
865
		unset($plugin);
866
867
		return $files;
868
	}
869
870
	/**
871
	 * getResourceFilesForComponent.
872
	 *
873
	 * Called by getResourceFiles() to return the list of files which are provided
874
	 * for the given component in a particular plugin.
875
	 * The paths which are returned start at the root of the webapp.
876
	 *
877
	 * This function might call itself recursively if it couldn't find any files for
878
	 * the given $load type. If no 'source' files are found, it will obtain the 'debug'
879
	 * files, if that too files it will fallback to 'release' files. If the latter is
880
	 * not found either, no files are returned.
881
	 *
882
	 * @param string $pluginname The name of the plugin (this is used in the pathname)
883
	 * @param array  $component  The component to read the resourcefiles from
884
	 * @param number $load       One of LOAD_RELEASE, LOAD_DEBUG, LOAD_SOURCE. This will filter
885
	 *                           the files based on the 'load' attribute.
886
	 *
887
	 * @return array list of paths to the files in this component
888
	 */
889
	public function getResourceFilesForComponent($pluginname, $component, $load) {
890
		$pluginname = $this->normalizePluginName($pluginname);
891
		$componentfiles = [];
892
893
		foreach ($component['resourcefiles'][$load] as &$file) {
894
			$componentfiles[] = $this->pluginpath . DIRECTORY_SEPARATOR . $pluginname . DIRECTORY_SEPARATOR . $file['file'];
895
		}
896
		unset($file);
897
898
		return $componentfiles;
899
	}
900
901
	/**
902
	 * getResourceFiles.
903
	 *
904
	 * Returning an array of paths to files that need to be included.
905
	 * The paths which are returned start at the root of the webapp.
906
	 *
907
	 * This calls getResourceFilesForComponent() to obtain the files
908
	 * for each component inside each plugin.
909
	 *
910
	 * @param number $load One of LOAD_RELEASE, LOAD_DEBUG, LOAD_SOURCE. This will filter
911
	 *                     the files based on the 'load' attribute.
912
	 *
913
	 * @return array list of paths to files
914
	 */
915
	public function getResourceFiles($load = LOAD_RELEASE) {
916
		$files = [];
917
918
		foreach ($this->pluginorder as $pluginname) {
919
			$plugin = &$this->plugindata[$pluginname];
920
			foreach ($plugin['components'] as &$component) {
921
				if (!empty($component['resourcefiles'][$load])) {
922
					$componentfiles = $this->getResourceFilesForComponent($pluginname, $component, $load);
923
				}
924
				elseif ($load === LOAD_SOURCE && !empty($component['resourcefiles'][LOAD_DEBUG])) {
925
					$componentfiles = $this->getResourceFilesForComponent($pluginname, $component, LOAD_DEBUG);
926
				}
927
				elseif ($load !== LOAD_RELEASE && !empty($component['resourcefiles'][LOAD_RELEASE])) {
928
					$componentfiles = $this->getResourceFilesForComponent($pluginname, $component, LOAD_RELEASE);
929
				} // else tough luck, at least release should be present
930
931
				if (isset($componentfiles)) {
932
					$files = array_merge($files, $componentfiles);
933
					unset($componentfiles);
934
				}
935
			}
936
			unset($component);
937
		}
938
		unset($plugin);
939
940
		return $files;
941
	}
942
943
	/**
944
	 * getTranslationFilePaths.
945
	 *
946
	 * Returning an array of paths to to the translations files. This will be
947
	 * used by the gettext functionality.
948
	 *
949
	 * @return array list of paths to translations
950
	 */
951
	public function getTranslationFilePaths() {
952
		$paths = [];
953
954
		foreach ($this->pluginorder as $pluginname) {
955
			$canonicalName = $this->normalizePluginName($pluginname);
956
			$plugin = &$this->plugindata[$pluginname];
957
			if ($plugin['translationsdir']) {
958
				$translationPath = $this->pluginpath . DIRECTORY_SEPARATOR . $canonicalName . DIRECTORY_SEPARATOR . $plugin['translationsdir']['dir'];
959
				if (is_dir($translationPath)) {
960
					$paths[$canonicalName] = $translationPath;
961
				}
962
			}
963
		}
964
		unset($plugin);
965
966
		return $paths;
967
	}
968
969
	/**
970
	 * extractPluginDataFromXML.
971
	 *
972
	 * Extracts all the data from the Plugin XML manifest.
973
	 *
974
	 * @param $xml     string XML manifest of plugin
975
	 * @param $dirname string name of the directory of the plugin
976
	 *
977
	 * @return array data from XML converted into array that the PluginManager can use
978
	 */
979
	public function extractPluginDataFromXML($xml, $dirname) {
980
		$plugindata = [
981
			'components' => [],
982
			'dependencies' => null,
983
			'translationsdir' => null,
984
			'version' => null,
985
		];
986
987
		// Parse all XML data
988
		$data = new SimpleXMLElement($xml);
989
990
		// Parse the <plugin> attributes
991
		if (isset($data['version']) && (int) $data['version'] !== 2) {
992
			if (DEBUG_PLUGINS) {
993
				dump("[PLUGIN ERROR] Plugin {$dirname} manifest uses version " . $data['version'] . " while only version 2 is supported");
994
			}
995
996
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
997
		}
998
999
		// Parse the <info> element
1000
		if (isset($data->info->version)) {
1001
			$plugindata['version'] = (string) $data->info->version;
1002
		}
1003
		else {
1004
			dump("[PLUGIN WARNING] Plugin {$dirname} has not specified version information in manifest.xml");
1005
		}
1006
1007
		// Parse the <config> element
1008
		if (isset($data->config)) {
1009
			if (isset($data->config->configfile)) {
1010
				if (empty($data->config->configfile)) {
1011
					dump("[PLUGIN ERROR] Plugin {$dirname} manifest contains empty configfile declaration");
1012
				}
1013
				if (!file_exists($data->config->configfile)) {
1014
					dump("[PLUGIN ERROR] Plugin {$dirname} manifest config file does not exists");
1015
				}
1016
			}
1017
			else {
1018
				dump("[PLUGIN ERROR] Plugin {$dirname} manifest configfile entry is missing");
1019
			}
1020
1021
			$files = [
1022
				LOAD_SOURCE => [],
1023
				LOAD_DEBUG => [],
1024
				LOAD_RELEASE => [],
1025
			];
1026
			foreach ($data->config->configfile as $filename) {
1027
				$files[LOAD_RELEASE][] = [
1028
					'file' => (string) $filename,
1029
					'type' => TYPE_CONFIG,
1030
					'load' => LOAD_RELEASE,
1031
					'module' => null,
1032
					'notifier' => null,
1033
				];
1034
			}
1035
			$plugindata['components'][] = [
1036
				'serverfiles' => $files,
1037
				'clientfiles' => [],
1038
				'resourcefiles' => [],
1039
			];
1040
		}
1041
1042
		// Parse the <dependencies> element
1043
		if (isset($data->dependencies, $data->dependencies->depends)) {
1044
			$dependencies = [
1045
				DEPEND_DEPENDS => [],
1046
				DEPEND_REQUIRES => [],
1047
				DEPEND_RECOMMENDS => [],
1048
				DEPEND_SUGGESTS => [],
1049
			];
1050
			foreach ($data->dependencies->depends as $depends) {
1051
				$type = $this->dependMap[(string) $depends->attributes()->type];
1052
				$plugin = (string) $depends->dependsname;
1053
				$dependencies[$type][] = [
1054
					'plugin' => $plugin,
1055
				];
1056
			}
1057
			$plugindata['dependencies'] = $dependencies;
1058
		}
1059
1060
		// Parse the <translations> element
1061
		if (isset($data->translations, $data->translations->translationsdir)) {
1062
			$plugindata['translationsdir'] = [
1063
				'dir' => (string) $data->translations->translationsdir,
1064
			];
1065
		}
1066
1067
		// Parse the <components> element
1068
		if (isset($data->components, $data->components->component)) {
1069
			foreach ($data->components->component as $component) {
1070
				$componentdata = [
1071
					'serverfiles' => [
1072
						LOAD_SOURCE => [],
1073
						LOAD_DEBUG => [],
1074
						LOAD_RELEASE => [],
1075
					],
1076
					'clientfiles' => [
1077
						LOAD_SOURCE => [],
1078
						LOAD_DEBUG => [],
1079
						LOAD_RELEASE => [],
1080
					],
1081
					'resourcefiles' => [
1082
						LOAD_SOURCE => [],
1083
						LOAD_DEBUG => [],
1084
						LOAD_RELEASE => [],
1085
					],
1086
				];
1087
				if (isset($component->files)) {
1088
					if (isset($component->files->server, $component->files->server->serverfile)) {
1089
						$files = [
1090
							LOAD_SOURCE => [],
1091
							LOAD_DEBUG => [],
1092
							LOAD_RELEASE => [],
1093
						];
1094
						foreach ($component->files->server->serverfile as $serverfile) {
1095
							$load = LOAD_RELEASE;
1096
							$type = TYPE_PLUGIN;
1097
							$module = null;
1098
							$notifier = null;
1099
1100
							$filename = (string) $serverfile;
1101
							if (empty($filename)) {
1102
								dump("[PLUGIN ERROR] Plugin {$dirname} manifest contains empty serverfile declaration");
1103
							}
1104
							if (isset($serverfile['type'])) {
1105
								$type = $this->typeMap[(string) $serverfile['type']];
1106
							}
1107
							if (isset($serverfile['load'])) {
1108
								$load = $this->loadMap[(string) $serverfile['load']];
1109
							}
1110
							if (isset($serverfile['module'])) {
1111
								$module = (string) $serverfile['module'];
1112
							}
1113
							if (isset($serverfile['notifier'])) {
1114
								$notifier = (string) $serverfile['notifier'];
1115
							}
1116
							if ($filename) {
1117
								$files[$load][] = [
1118
									'file' => $filename,
1119
									'type' => $type,
1120
									'load' => $load,
1121
									'module' => $module,
1122
									'notifier' => $notifier,
1123
								];
1124
							}
1125
						}
1126
						$componentdata['serverfiles'][LOAD_SOURCE] = array_merge($componentdata['serverfiles'][LOAD_SOURCE], $files[LOAD_SOURCE]);
1127
						$componentdata['serverfiles'][LOAD_DEBUG] = array_merge($componentdata['serverfiles'][LOAD_DEBUG], $files[LOAD_DEBUG]);
1128
						$componentdata['serverfiles'][LOAD_RELEASE] = array_merge($componentdata['serverfiles'][LOAD_RELEASE], $files[LOAD_RELEASE]);
1129
					}
1130
					if (isset($component->files->client, $component->files->client->clientfile)) {
1131
						$files = [
1132
							LOAD_SOURCE => [],
1133
							LOAD_DEBUG => [],
1134
							LOAD_RELEASE => [],
1135
						];
1136
						foreach ($component->files->client->clientfile as $clientfile) {
1137
							$filename = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $filename is dead and can be removed.
Loading history...
1138
							$load = LOAD_RELEASE;
1139
							$filename = (string) $clientfile;
1140
							if (isset($clientfile['load'])) {
1141
								$load = $this->loadMap[(string) $clientfile['load']];
1142
							}
1143
							if (empty($filename)) {
1144
								if (DEBUG_PLUGINS) {
1145
									dump("[PLUGIN ERROR] Plugin {$dirname} manifest contains empty resourcefile declaration");
1146
								}
1147
							}
1148
							else {
1149
								$files[$load][] = [
1150
									'file' => $filename,
1151
									'load' => $load,
1152
								];
1153
							}
1154
						}
1155
						$componentdata['clientfiles'][LOAD_SOURCE] = array_merge($componentdata['clientfiles'][LOAD_SOURCE], $files[LOAD_SOURCE]);
1156
						$componentdata['clientfiles'][LOAD_DEBUG] = array_merge($componentdata['clientfiles'][LOAD_DEBUG], $files[LOAD_DEBUG]);
1157
						$componentdata['clientfiles'][LOAD_RELEASE] = array_merge($componentdata['clientfiles'][LOAD_RELEASE], $files[LOAD_RELEASE]);
1158
					}
1159
					if (isset($component->files->resources, $component->files->resources->resourcefile)) {
1160
						$files = [
1161
							LOAD_SOURCE => [],
1162
							LOAD_DEBUG => [],
1163
							LOAD_RELEASE => [],
1164
						];
1165
						foreach ($component->files->resources->resourcefile as $resourcefile) {
1166
							$filename = false;
1167
							$load = LOAD_RELEASE;
1168
							$filename = (string) $resourcefile;
1169
							if (isset($resourcefile['load'])) {
1170
								$load = $this->loadMap[(string) $resourcefile['load']];
1171
							}
1172
							if (empty($filename)) {
1173
								if (DEBUG_PLUGINS) {
1174
									dump("[PLUGIN ERROR] Plugin {$dirname} manifest contains empty resourcefile declaration");
1175
								}
1176
							}
1177
							else {
1178
								$files[$load][] = [
1179
									'file' => $filename,
1180
									'load' => $load,
1181
								];
1182
							}
1183
						}
1184
						$componentdata['resourcefiles'][LOAD_SOURCE] = array_merge($componentdata['resourcefiles'][LOAD_SOURCE], $files[LOAD_SOURCE]);
1185
						$componentdata['resourcefiles'][LOAD_DEBUG] = array_merge($componentdata['resourcefiles'][LOAD_DEBUG], $files[LOAD_DEBUG]);
1186
						$componentdata['resourcefiles'][LOAD_RELEASE] = array_merge($componentdata['resourcefiles'][LOAD_RELEASE], $files[LOAD_RELEASE]);
1187
					}
1188
					$plugindata['components'][] = $componentdata;
1189
				}
1190
			}
1191
		}
1192
		else {
1193
			if (DEBUG_PLUGINS) {
1194
				dump("[PLUGIN ERROR] Plugin {$dirname} manifest didn't provide any components");
1195
			}
1196
1197
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
1198
		}
1199
1200
		return $plugindata;
1201
	}
1202
1203
	/**
1204
	 * Expands a string that contains a semicolon separated list of plugins.
1205
	 * All wildcards (*) will be resolved.
1206
	 *
1207
	 * @param mixed $pluginList
1208
	 */
1209
	public function expandPluginList($pluginList) {
1210
		$pluginNames = explode(';', (string) $pluginList);
1211
		$pluginList = [];
1212
		foreach ($pluginNames as $pluginName) {
1213
			$pluginName = trim($pluginName);
1214
			if ($pluginName === '') {
1215
				continue;
1216
			}
1217
			$canonicalName = $this->normalizePluginName($pluginName);
1218
			if (array_key_exists($canonicalName, $this->plugindata)) {
1219
				$pluginList[] = $canonicalName;
1220
			}
1221
			else {
1222
				// Check if it contains a wildcard
1223
				if (str_contains($pluginName, '*')) {
1224
					$expandedPluginList = $this->_expandPluginNameWithWildcard($pluginName);
1225
					$pluginList = array_merge($pluginList, $expandedPluginList);
1226
				}
1227
			}
1228
		}
1229
1230
		// Remove duplicates
1231
		$pluginList = array_unique($pluginList);
1232
1233
		// Decide whether to show password plugin in settings:
1234
		// - show if the users are in a db
1235
		// - don't show if the users are in ldap
1236
		if (($key = array_search('passwd', $pluginList)) !== false && isset($GLOBALS['usersinldap']) && $GLOBALS['usersinldap']) {
1237
			unset($pluginList[$key]);
1238
		}
1239
1240
		return implode(';', $pluginList);
1241
	}
1242
1243
	/**
1244
	 * Finds all plugins that match the given string name (that contains one or
1245
	 * more wildcards).
1246
	 *
1247
	 * @param string $pluginNameWithWildcard A plugin identifying string that
1248
	 *                                       contains a wildcard character (*)
1249
	 *
1250
	 * @return array An array with the names of the plugins that are identified by
1251
	 *               $pluginNameWithWildcard
1252
	 */
1253
	private function _expandPluginNameWithWildcard($pluginNameWithWildcard) {
1254
		$retVal = [];
1255
		$pluginNames = array_keys($this->plugindata);
1256
		$regExp = '/^' . str_replace('*', '.*?', $pluginNameWithWildcard) . '$/';
1257
		dump('rexexp = ' . $regExp);
1258
		foreach ($pluginNames as $pluginName) {
1259
			dump('checking plugin: ' . $pluginName);
1260
			if (preg_match($regExp, $pluginName)) {
1261
				$retVal[] = $pluginName;
1262
			}
1263
		}
1264
1265
		return $retVal;
1266
	}
1267
}
1268