Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/classes/ElggPluginManifest.php (1 issue)

loose comparison of strings.

Best Practice Bug Major
1
<?php
2
/**
3
 * Parses Elgg manifest.xml files.
4
 *
5
 * Normalizes the values from the \ElggManifestParser object.
6
 *
7
 * This requires an \ElggPluginManifestParser class implementation
8
 * as $this->parser.
9
 *
10
 * To add new parser versions, name them \ElggPluginManifestParserXX
11
 * where XX is the version specified in the top-level <plugin_manifest>
12
 * tag's XML namespace.
13
 *
14
 * @package    Elgg.Core
15
 * @subpackage Plugins
16
 * @since      1.8
17
 */
18
class ElggPluginManifest {
19
20
	/**
21
	 * The parser object
22
	 *
23
	 * @var \ElggPluginManifestParser18
24
	 */
25
	protected $parser;
26
27
	/**
28
	 * The root for plugin manifest namespaces.
29
	 * This is in the format http://www.elgg.org/plugin_manifest/<version>
30
	 */
31
	protected $namespace_root = 'http://www.elgg.org/plugin_manifest/';
32
33
	/**
34
	 * The expected structure of a plugins requires element
35
	 */
36
	private $depsStructPlugin = [
37
		'type' => '',
38
		'name' => '',
39
		'version' => '',
40
		'comparison' => 'ge'
41
	];
42
43
	/**
44
	 * The expected structure of a priority element
45
	 */
46
	private $depsStructPriority = [
47
		'type' => '',
48
		'priority' => '',
49
		'plugin' => ''
50
	];
51
52
	/*
53
	 * The expected structure of elgg_release requires element
54
	 */
55
	private $depsStructElgg = [
56
		'type' => '',
57
		'version' => '',
58
		'comparison' => 'ge'
59
	];
60
61
	/**
62
	 * The expected structure of a requires php_version dependency element
63
	 */
64
	private $depsStructPhpVersion = [
65
		'type' => '',
66
		'version' => '',
67
		'comparison' => 'ge'
68
	];
69
70
	/**
71
	 * The expected structure of a requires php_ini dependency element
72
	 */
73
	private $depsStructPhpIni = [
74
		'type' => '',
75
		'name' => '',
76
		'value' => '',
77
		'comparison' => '='
78
	];
79
80
	/**
81
	 * The expected structure of a requires php_extension dependency element
82
	 */
83
	private $depsStructPhpExtension = [
84
		'type' => '',
85
		'name' => '',
86
		'version' => '',
87
		'comparison' => '='
88
	];
89
90
	/**
91
	 * The expected structure of a conflicts depedency element
92
	 */
93
	private $depsConflictsStruct = [
94
		'type' => '',
95
		'name' => '',
96
		'version' => '',
97
		'comparison' => '='
98
	];
99
100
	/**
101
	 * The expected structure of a provides dependency element.
102
	 */
103
	private $depsProvidesStruct = [
104
		'type' => '',
105
		'name' => '',
106
		'version' => ''
107
	];
108
109
	/**
110
	 * The expected structure of a screenshot element
111
	 */
112
	private $screenshotStruct = [
113
		'description' => '',
114
		'path' => ''
115
	];
116
117
	/**
118
	 * The expected structure of a contributor element
119
	 */
120
	private $contributorStruct = [
121
		'name' => '',
122
		'email' => '',
123
		'website' => '',
124
		'username' => '',
125
		'description' => '',
126
	];
127
128
	/**
129
	 * The API version of the manifest.
130
	 *
131
	 * @var int
132
	 */
133
	protected $apiVersion;
134
135
	/**
136
	 * The optional plugin id this manifest belongs to.
137
	 *
138
	 * @var string
139
	 */
140
	protected $pluginID;
141
142
	/**
143
	 * Load a manifest file, XmlElement or path to manifest.xml file
144
	 *
145
	 * @param mixed  $manifest  A string, XmlElement, or path of a manifest file.
146
	 * @param string $plugin_id Optional ID of the owning plugin. Used to
147
	 *                          fill in some values automatically.
148
	 *
149
	 * @throws PluginException
150
	 */
151 241
	public function __construct($manifest, $plugin_id = null) {
152 241
		if ($plugin_id) {
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression $plugin_id of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
153 241
			$this->pluginID = $plugin_id;
154
		}
155
156
		// see if we need to construct the xml object.
157 241
		if ($manifest instanceof \ElggXMLElement) {
158 1
			$manifest_obj = $manifest;
159
		} else {
160 241
			$raw_xml = '';
161 241
			if (substr(trim($manifest), 0, 1) == '<') {
162
				// this is a string
163 1
				$raw_xml = $manifest;
164 241
			} elseif (is_file($manifest)) {
165
				// this is a file
166 241
				$raw_xml = file_get_contents($manifest);
167
			}
168 241
			if ($raw_xml) {
169 241
				$manifest_obj = new \ElggXMLElement($raw_xml);
170
			} else {
171
				$manifest_obj = null;
172
			}
173
		}
174
175 241
		if (!$manifest_obj) {
176
			throw new \PluginException(_elgg_services()->translator->translate('PluginException:InvalidManifest',
177
						[$this->getPluginID()]));
178
		}
179
180
		// set manifest api version
181 241
		if (isset($manifest_obj->attributes['xmlns'])) {
182 241
			$namespace = $manifest_obj->attributes['xmlns'];
183 241
			$version = str_replace($this->namespace_root, '', $namespace);
184
		} else {
185
			$version = 1.7;
186
		}
187
188 241
		$this->apiVersion = $version;
189
190 241
		$parser_class_name = '\ElggPluginManifestParser' . str_replace('.', '', $this->apiVersion);
191
192
		// @todo currently the autoloader freaks out if a class doesn't exist.
193
		try {
194 241
			$class_exists = class_exists($parser_class_name);
195
		} catch (Exception $e) {
196
			$class_exists = false;
197
		}
198
199 241
		if ($class_exists) {
200 241
			$this->parser = new $parser_class_name($manifest_obj, $this);
201
		} else {
202
			throw new \PluginException(_elgg_services()->translator->translate('PluginException:NoAvailableParser',
203
							[$this->apiVersion, $this->getPluginID()]));
204
		}
205
206 241
		if (!$this->parser->parse()) {
207
			throw new \PluginException(_elgg_services()->translator->translate('PluginException:ParserError',
208
						[$this->apiVersion, $this->getPluginID()]));
209
		}
210 241
	}
211
212
	/**
213
	 * Returns the API version in use.
214
	 *
215
	 * @return int
216
	 */
217 33
	public function getApiVersion() {
218 33
		return $this->apiVersion;
219
	}
220
221
	/**
222
	 * Returns the plugin ID.
223
	 *
224
	 * @return string
225
	 */
226 63
	public function getPluginID() {
227 63
		if ($this->pluginID) {
228 63
			return $this->pluginID;
229
		} else {
230
			return _elgg_services()->translator->translate('unknown');
231
		}
232
	}
233
234
	/**
235
	 * Returns the manifest array.
236
	 *
237
	 * Used for backward compatibility.  Specific
238
	 * methods should be called instead.
239
	 *
240
	 * @return array
241
	 */
242 1
	public function getManifest() {
243 1
		return $this->parser->getManifest();
244
	}
245
246
	/***************************************
247
	 * Parsed and Normalized Manifest Data *
248
	 ***************************************/
249
250
	/**
251
	 * Returns the plugin name
252
	 *
253
	 * @return string
254
	 */
255 33
	public function getName() {
256 33
		$name = $this->parser->getAttribute('name');
257
258 33
		if (!$name && $this->pluginID) {
259
			$name = ucwords(str_replace('_', ' ', $this->pluginID));
260
		}
261
262 33
		return $name;
263
	}
264
265
	/**
266
	 * Return the plugin ID required by the author. If getPluginID() does
267
	 * not match this, the plugin should not be started.
268
	 *
269
	 * @return string empty string if not empty/not defined
270
	 */
271 64
	public function getID() {
272 64
		return trim((string) $this->parser->getAttribute('id'));
273
	}
274
275
276
	/**
277
	 * Return the description
278
	 *
279
	 * @return string
280
	 */
281 32
	public function getDescription() {
282 32
		return $this->parser->getAttribute('description');
283
	}
284
285
	/**
286
	 * Return the short description
287
	 *
288
	 * @return string
289
	 */
290 1
	public function getBlurb() {
291 1
		$blurb = $this->parser->getAttribute('blurb');
292
293 1
		if (!$blurb) {
294
			$blurb = elgg_get_excerpt($this->getDescription());
295
		}
296
297 1
		return $blurb;
298
	}
299
300
	/**
301
	 * Returns the license
302
	 *
303
	 * @return string
304
	 */
305 32
	public function getLicense() {
306
		// license vs licence.  Use license.
307 32
		$en_us = $this->parser->getAttribute('license');
308 32
		if ($en_us) {
309 32
			return $en_us;
310
		} else {
311
			return $this->parser->getAttribute('licence');
312
		}
313
	}
314
315
	/**
316
	 * Returns the repository url
317
	 *
318
	 * @return string
319
	 */
320 1
	public function getRepositoryURL() {
321 1
		return $this->parser->getAttribute('repository');
322
	}
323
324
	/**
325
	 * Returns the bug tracker page
326
	 *
327
	 * @return string
328
	 */
329 1
	public function getBugTrackerURL() {
330 1
		return $this->parser->getAttribute('bugtracker');
331
	}
332
333
	/**
334
	 * Returns the donations page
335
	 *
336
	 * @return string
337
	 */
338 1
	public function getDonationsPageURL() {
339 1
		return $this->parser->getAttribute('donations');
340
	}
341
342
	/**
343
	 * Returns the version of the plugin.
344
	 *
345
	 * @return float
346
	 */
347 33
	public function getVersion() {
348 33
		return $this->parser->getAttribute('version');
349
	}
350
351
	/**
352
	 * Returns the plugin author.
353
	 *
354
	 * @return string
355
	 */
356 32
	public function getAuthor() {
357 32
		return $this->parser->getAttribute('author');
358
	}
359
360
	/**
361
	 * Return the copyright
362
	 *
363
	 * @return string
364
	 */
365 1
	public function getCopyright() {
366 1
		return $this->parser->getAttribute('copyright');
367
	}
368
369
	/**
370
	 * Return the website
371
	 *
372
	 * @return string
373
	 */
374 1
	public function getWebsite() {
375 1
		return $this->parser->getAttribute('website');
376
	}
377
378
	/**
379
	 * Return the categories listed for this plugin
380
	 *
381
	 * @return array
382
	 */
383 32
	public function getCategories() {
384
		$bundled_plugins = [
385 32
			'activity',
386
			'blog',
387
			'bookmarks',
388
			'ckeditor',
389
			'custom_index',
390
			'dashboard',
391
			'developers',
392
			'diagnostics',
393
			'discussions',
394
			'embed',
395
			'externalpages',
396
			'file',
397
			'friends',
398
			'friends_collections',
399
			'garbagecollector',
400
			'groups',
401
			'invitefriends',
402
			'legacy_urls',
403
			'likes',
404
			'login_as',
405
			'members',
406
			'messageboard',
407
			'messages',
408
			'notifications',
409
			'pages',
410
			'profile',
411
			'reportedcontent',
412
			'search',
413
			'site_notifications',
414
			'system_log',
415
			'tagcloud',
416
			'thewire',
417
			'uservalidationbyemail',
418
			'web_services',
419
		];
420
421 32
		$cats = $this->parser->getAttribute('category');
422
423 32
		if (!$cats) {
424
			$cats = [];
425
		}
426
427 32
		if (in_array('bundled', $cats) && !in_array($this->getPluginID(), $bundled_plugins)) {
428
			unset($cats[array_search('bundled', $cats)]);
429
		}
430
431 32
		return $cats;
432
	}
433
434
	/**
435
	 * Return the screenshots listed.
436
	 *
437
	 * @return array
438
	 */
439 1
	public function getScreenshots() {
440 1
		$ss = $this->parser->getAttribute('screenshot');
441
442 1
		if (!$ss) {
443
			$ss = [];
444
		}
445
446 1
		$normalized = [];
447 1
		foreach ($ss as $s) {
448 1
			$normalized[] = $this->buildStruct($this->screenshotStruct, $s);
449
		}
450
451 1
		return $normalized;
452
	}
453
454
	/**
455
	 * Return the contributors listed.
456
	 *
457
	 * @return array
458
	 */
459 1
	public function getContributors() {
460 1
		$ss = $this->parser->getAttribute('contributor');
461
462 1
		if (!$ss) {
463
			$ss = [];
464
		}
465
466 1
		$normalized = [];
467 1
		foreach ($ss as $s) {
468 1
			$normalized[] = $this->buildStruct($this->contributorStruct, $s);
469
		}
470
471 1
		return $normalized;
472
	}
473
474
	/**
475
	 * Return the list of provides by this plugin.
476
	 *
477
	 * @return array
478
	 */
479 33
	public function getProvides() {
480
		// normalize for 1.7
481 33
		if ($this->getApiVersion() < 1.8) {
482
			$provides = [];
483
		} else {
484 33
			$provides = $this->parser->getAttribute('provides');
485
		}
486
487 33
		if (!$provides) {
488 5
			$provides = [];
489
		}
490
491
		// always provide ourself if we can
492 33
		if ($this->pluginID) {
493 33
			$provides[] = [
494 33
				'type' => 'plugin',
495 33
				'name' => $this->getPluginID(),
496 33
				'version' => $this->getVersion()
497
			];
498
		}
499
500 33
		$normalized = [];
501 33
		foreach ($provides as $provide) {
502 33
			$normalized[] = $this->buildStruct($this->depsProvidesStruct, $provide);
503
		}
504
505 33
		return $normalized;
506
	}
507
508
	/**
509
	 * Returns the dependencies listed.
510
	 *
511
	 * @return array
512
	 */
513 33
	public function getRequires() {
514 33
		$reqs = $this->parser->getAttribute('requires');
515
516 33
		if (!$reqs) {
517
			$reqs = [];
518
		}
519
520 33
		$normalized = [];
521 33
		foreach ($reqs as $req) {
522 33
			$normalized[] = $this->normalizeDep($req);
523
		}
524
525 33
		return $normalized;
526
	}
527
528
	/**
529
	 * Returns the suggests elements.
530
	 *
531
	 * @return array
532
	 */
533 1
	public function getSuggests() {
534 1
		$suggests = $this->parser->getAttribute('suggests');
535
536 1
		if (!$suggests) {
537
			$suggests = [];
538
		}
539
540 1
		$normalized = [];
541 1
		foreach ($suggests as $suggest) {
542 1
			$normalized[] = $this->normalizeDep($suggest);
543
		}
544
545 1
		return $normalized;
546
	}
547
548
	/**
549
	 * Normalizes a dependency array using the defined structs.
550
	 * Can be used with either requires or suggests.
551
	 *
552
	 * @param array $dep A dependency array.
553
	 * @return array The normalized deps array.
554
	 */
555 33
	private function normalizeDep($dep) {
556
		
557 33
		$struct = [];
558
		
559 33
		switch ($dep['type']) {
560
			case 'elgg_release':
561 33
				$struct = $this->depsStructElgg;
562 33
				break;
563
564
			case 'plugin':
565 31
				$struct = $this->depsStructPlugin;
566 31
				break;
567
568
			case 'priority':
569 31
				$struct = $this->depsStructPriority;
570 31
				break;
571
572
			case 'php_version':
573 30
				$struct = $this->depsStructPhpVersion;
574 30
				break;
575
576
			case 'php_extension':
577 30
				$struct = $this->depsStructPhpExtension;
578 30
				break;
579
580
			case 'php_ini':
581 30
				$struct = $this->depsStructPhpIni;
582
583
				// also normalize boolean values
584 30
				if (isset($dep['value'])) {
585 30
					switch (strtolower($dep['value'])) {
586 30
						case 'yes':
587 30
						case 'true':
588 30
						case 'on':
589 30
						case 1:
590
							$dep['value'] = 1;
591
							break;
592
593 30
						case 'no':
594 30
						case 'false':
595 30
						case 'off':
596
						case 0:
597
						case '':
598 30
							$dep['value'] = 0;
599 30
							break;
600
					}
601
				}
602 30
				break;
603
			default:
604
				// unrecognized so we just return the raw dependency
605
				return $dep;
606
		}
607
		
608 33
		$normalized_dep = $this->buildStruct($struct, $dep);
609
610
		// normalize comparison operators
611 33
		if (isset($normalized_dep['comparison'])) {
612 33
			switch ($normalized_dep['comparison']) {
613
				case '<':
614
					$normalized_dep['comparison'] = 'lt';
615
					break;
616
617
				case '<=':
618
					$normalized_dep['comparison'] = 'le';
619
					break;
620
621
				case '>':
622
					$normalized_dep['comparison'] = 'gt';
623
					break;
624
625
				case '>=':
626
					$normalized_dep['comparison'] = 'ge';
627
					break;
628
629
				case '==':
630
				case 'eq':
631
					$normalized_dep['comparison'] = '=';
632
					break;
633
634
				case '<>':
635
				case 'ne':
636
					$normalized_dep['comparison'] = '!=';
637
					break;
638
			}
639
		}
640
641 33
		return $normalized_dep;
642
	}
643
644
	/**
645
	 * Returns the conflicts listed
646
	 *
647
	 * @return array
648
	 */
649 33
	public function getConflicts() {
650
		// normalize for 1.7
651 33
		if ($this->getApiVersion() < 1.8) {
652
			$conflicts = [];
653
		} else {
654 33
			$conflicts = $this->parser->getAttribute('conflicts');
655
		}
656
657 33
		if (!$conflicts) {
658 5
			$conflicts = [];
659
		}
660
661 33
		$normalized = [];
662
663 33
		foreach ($conflicts as $conflict) {
664 33
			$normalized[] = $this->buildStruct($this->depsConflictsStruct, $conflict);
665
		}
666
667 33
		return $normalized;
668
	}
669
670
	/**
671
	 * Should this plugin be activated when Elgg is installed
672
	 *
673
	 *  @return bool
674
	 */
675 1
	public function getActivateOnInstall() {
676 1
		$activate = $this->parser->getAttribute('activate_on_install');
677 1
		switch (strtolower($activate)) {
678 1
			case 'yes':
679 1
			case 'true':
680
			case 'on':
681
			case 1:
682 1
				return true;
683
684
			case 'no':
685
			case 'false':
686
			case 'off':
687
			case 0:
688
			case '':
689
				return false;
690
		}
691
	}
692
693
	/**
694
	 * Normalizes an array into the structure specified
695
	 *
696
	 * @param array $struct The struct to normalize $element to.
697
	 * @param array $array  The array
698
	 *
699
	 * @return array
700
	 */
701 33
	protected function buildStruct(array $struct, array $array) {
702 33
		$return = [];
703
704 33
		foreach ($struct as $index => $default) {
705 33
			$return[$index] = elgg_extract($index, $array, $default);
706
		}
707
708 33
		return $return;
709
	}
710
711
	/**
712
	 * Returns a category's friendly name. This can be localized by
713
	 * defining the string 'admin:plugins:category:<category>'. If no
714
	 * localization is found, returns the category with _ and - converted to ' '
715
	 * and then ucwords()'d.
716
	 *
717
	 * @param string $category The category as defined in the manifest.
718
	 * @return string A human-readable category
719
	 */
720
	static public function getFriendlyCategory($category) {
721
		$cat_raw_string = "admin:plugins:category:$category";
722
		if (_elgg_services()->translator->languageKeyExists($cat_raw_string)) {
723
			return _elgg_services()->translator->translate($cat_raw_string);
724
		}
725
		
726
		$category = str_replace(['-', '_'], ' ', $category);
727
		return ucwords($category);
728
	}
729
}
730