ElggPluginManifest::normalizeDep()   F
last analyzed

Complexity

Conditions 26
Paths 161

Size

Total Lines 87
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 40.6338

Importance

Changes 0
Metric Value
cc 26
eloc 61
nc 161
nop 1
dl 0
loc 87
ccs 44
cts 61
cp 0.7213
crap 40.6338
rs 3.6583
c 0
b 0
f 0

How to fix   Long Method    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
 * 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 368
	public function __construct($manifest, $plugin_id = null) {
152 368
		if ($plugin_id) {
153 368
			$this->pluginID = $plugin_id;
154
		}
155
156
		// see if we need to construct the xml object.
157 368
		if ($manifest instanceof \ElggXMLElement) {
158 1
			$manifest_obj = $manifest;
159
		} else {
160 368
			$raw_xml = '';
161 368
			if (substr(trim($manifest), 0, 1) == '<') {
162
				// this is a string
163 1
				$raw_xml = $manifest;
164 368
			} elseif (is_file($manifest)) {
165
				// this is a file
166 368
				$raw_xml = file_get_contents($manifest);
167
			}
168 368
			if ($raw_xml) {
169 368
				$manifest_obj = new \ElggXMLElement($raw_xml);
170
			} else {
171
				$manifest_obj = null;
172
			}
173
		}
174
175 368
		if (!$manifest_obj) {
176
			$msg = elgg_echo('PluginException:InvalidManifest', [$this->getPluginID()]);
177
			throw PluginException::factory('InvalidManifest', null, $msg);
178
		}
179
180
		// set manifest api version
181 368
		if (isset($manifest_obj->attributes['xmlns'])) {
182 368
			$namespace = $manifest_obj->attributes['xmlns'];
183 368
			$version = str_replace($this->namespace_root, '', $namespace);
184
		} else {
185
			$version = 1.7;
186
		}
187
188 368
		$this->apiVersion = $version;
0 ignored issues
show
Documentation Bug introduced by Brett Profitt
It seems like $version can also be of type string. However, the property $apiVersion is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
189
190 368
		$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 368
			$class_exists = class_exists($parser_class_name);
195
		} catch (Exception $e) {
196
			$class_exists = false;
197
		}
198
199 368
		if ($class_exists) {
200 368
			$this->parser = new $parser_class_name($manifest_obj, $this);
201
		} else {
202
			$msg = elgg_echo('PluginException:NoAvailableParser', [$this->apiVersion, $this->getPluginID()]);
203
			throw PluginException::factory('NoAvailableParser', null, $msg);
204
		}
205
206 368
		if (!$this->parser->parse()) {
207
			$msg = elgg_echo('PluginException:ParserError', [$this->apiVersion, $this->getPluginID()]);
208
			throw PluginException::factory('ParseError', null, $msg);
209
		}
210 368
	}
211
212
	/**
213
	 * Returns the API version in use.
214
	 *
215
	 * @return int
216
	 */
217 44
	public function getApiVersion() {
218 44
		return $this->apiVersion;
219
	}
220
221
	/**
222
	 * Returns the plugin ID.
223
	 *
224
	 * @return string
225
	 */
226 73
	public function getPluginID() {
227 73
		if ($this->pluginID) {
228 73
			return $this->pluginID;
229
		} else {
230
			return elgg_echo('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 32
	public function getName() {
256 32
		$name = $this->parser->getAttribute('name');
257
258 32
		if (!$name && $this->pluginID) {
259
			$name = ucwords(str_replace('_', ' ', $this->pluginID));
260
		}
261
262 32
		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 74
	public function getID() {
272 74
		return trim((string) $this->parser->getAttribute('id'));
273
	}
274
275
276
	/**
277
	 * Return the description
278
	 *
279
	 * @return string
280
	 */
281 31
	public function getDescription() {
282 31
		return $this->parser->getAttribute('description');
0 ignored issues
show
Bug Best Practice introduced by Cash Costello
The expression return $this->parser->getAttribute('description') 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...
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 31
	public function getLicense() {
306
		// license vs licence.  Use license.
307 31
		$en_us = $this->parser->getAttribute('license');
308 31
		if ($en_us) {
309 31
			return $en_us;
310
		} else {
311
			return $this->parser->getAttribute('licence');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->parser->getAttribute('licence') 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...
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');
0 ignored issues
show
Bug Best Practice introduced by Sem
The expression return $this->parser->getAttribute('repository') 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...
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');
0 ignored issues
show
Bug Best Practice introduced by Sem
The expression return $this->parser->getAttribute('bugtracker') 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...
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');
0 ignored issues
show
Bug Best Practice introduced by Sem
The expression return $this->parser->getAttribute('donations') 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...
340
	}
341
342
	/**
343
	 * Returns the version of the plugin.
344
	 *
345
	 * @return float
346
	 */
347 44
	public function getVersion() {
348 44
		return $this->parser->getAttribute('version');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->parser->getAttribute('version') could also return false which is incompatible with the documented return type double. 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...
349
	}
350
351
	/**
352
	 * Returns the plugin author.
353
	 *
354
	 * @return string
355
	 */
356 31
	public function getAuthor() {
357 31
		return $this->parser->getAttribute('author');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->parser->getAttribute('author') 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...
358
	}
359
360
	/**
361
	 * Return the copyright
362
	 *
363
	 * @return string
364
	 */
365 1
	public function getCopyright() {
366 1
		return $this->parser->getAttribute('copyright');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->parser->getAttribute('copyright') 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...
367
	}
368
369
	/**
370
	 * Return the website
371
	 *
372
	 * @return string
373
	 */
374 1
	public function getWebsite() {
375 1
		return $this->parser->getAttribute('website');
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression return $this->parser->getAttribute('website') 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...
376
	}
377
378
	/**
379
	 * Return the categories listed for this plugin
380
	 *
381
	 * @return array
382
	 */
383 31
	public function getCategories() {
384
		$bundled_plugins = [
385 31
			'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
			'likes',
403
			'login_as',
404
			'members',
405
			'messageboard',
406
			'messages',
407
			'notifications',
408
			'pages',
409
			'profile',
410
			'reportedcontent',
411
			'search',
412
			'site_notifications',
413
			'system_log',
414
			'tagcloud',
415
			'thewire',
416
			'uservalidationbyemail',
417
			'web_services',
418
		];
419
420 31
		$cats = $this->parser->getAttribute('category');
421
422 31
		if (!$cats) {
423
			$cats = [];
424
		}
425
426 31
		if (in_array('bundled', $cats) && !in_array($this->getPluginID(), $bundled_plugins)) {
427
			unset($cats[array_search('bundled', $cats)]);
428
		}
429
430 31
		return $cats;
431
	}
432
433
	/**
434
	 * Return the screenshots listed.
435
	 *
436
	 * @return array
437
	 */
438 1
	public function getScreenshots() {
439 1
		$ss = $this->parser->getAttribute('screenshot');
440
441 1
		if (!$ss) {
442
			$ss = [];
443
		}
444
445 1
		$normalized = [];
446 1
		foreach ($ss as $s) {
447 1
			$normalized[] = $this->buildStruct($this->screenshotStruct, $s);
448
		}
449
450 1
		return $normalized;
451
	}
452
453
	/**
454
	 * Return the contributors listed.
455
	 *
456
	 * @return array
457
	 */
458 1
	public function getContributors() {
459 1
		$ss = $this->parser->getAttribute('contributor');
460
461 1
		if (!$ss) {
462
			$ss = [];
463
		}
464
465 1
		$normalized = [];
466 1
		foreach ($ss as $s) {
467 1
			$normalized[] = $this->buildStruct($this->contributorStruct, $s);
468
		}
469
470 1
		return $normalized;
471
	}
472
473
	/**
474
	 * Return the list of provides by this plugin.
475
	 *
476
	 * @return array
477
	 */
478 44
	public function getProvides() {
479
		// normalize for 1.7
480 44
		if ($this->getApiVersion() < 1.8) {
481
			$provides = [];
482
		} else {
483 44
			$provides = $this->parser->getAttribute('provides');
484
		}
485
486 44
		if (!$provides) {
487 16
			$provides = [];
488
		}
489
490
		// always provide ourself if we can
491 44
		if ($this->pluginID) {
492 44
			$provides[] = [
493 44
				'type' => 'plugin',
494 44
				'name' => $this->getPluginID(),
495 44
				'version' => $this->getVersion()
496
			];
497
		}
498
499 44
		$normalized = [];
500 44
		foreach ($provides as $provide) {
501 44
			$normalized[] = $this->buildStruct($this->depsProvidesStruct, $provide);
502
		}
503
504 44
		return $normalized;
505
	}
506
507
	/**
508
	 * Returns the dependencies listed.
509
	 *
510
	 * @return array
511
	 */
512 192
	public function getRequires() {
513 192
		$reqs = $this->parser->getAttribute('requires');
514
515 192
		if (!$reqs) {
516
			$reqs = [];
517
		}
518
519 192
		$normalized = [];
520 192
		foreach ($reqs as $req) {
521 192
			$normalized[] = $this->normalizeDep($req);
522
		}
523
524 192
		return $normalized;
525
	}
526
527
	/**
528
	 * Returns the suggests elements.
529
	 *
530
	 * @return array
531
	 */
532 1
	public function getSuggests() {
533 1
		$suggests = $this->parser->getAttribute('suggests');
534
535 1
		if (!$suggests) {
536
			$suggests = [];
537
		}
538
539 1
		$normalized = [];
540 1
		foreach ($suggests as $suggest) {
541 1
			$normalized[] = $this->normalizeDep($suggest);
542
		}
543
544 1
		return $normalized;
545
	}
546
547
	/**
548
	 * Normalizes a dependency array using the defined structs.
549
	 * Can be used with either requires or suggests.
550
	 *
551
	 * @param array $dep A dependency array.
552
	 * @return array The normalized deps array.
553
	 */
554 192
	private function normalizeDep($dep) {
555
		
556 192
		$struct = [];
557
		
558 192
		switch ($dep['type']) {
559 192
			case 'elgg_release':
560 192
				$struct = $this->depsStructElgg;
561 192
				break;
562
563 38
			case 'plugin':
564 38
				$struct = $this->depsStructPlugin;
565 38
				break;
566
567 33
			case 'priority':
568 33
				$struct = $this->depsStructPriority;
569 33
				break;
570
571 30
			case 'php_version':
572 30
				$struct = $this->depsStructPhpVersion;
573 30
				break;
574
575 30
			case 'php_extension':
576 30
				$struct = $this->depsStructPhpExtension;
577 30
				break;
578
579 30
			case 'php_ini':
580 30
				$struct = $this->depsStructPhpIni;
581
582
				// also normalize boolean values
583 30
				if (isset($dep['value'])) {
584 30
					switch (strtolower($dep['value'])) {
585 30
						case 'yes':
586 30
						case 'true':
587 30
						case 'on':
588 30
						case 1:
589
							$dep['value'] = 1;
590
							break;
591
592 30
						case 'no':
593 30
						case 'false':
594 30
						case 'off':
595
						case 0:
596
						case '':
597 30
							$dep['value'] = 0;
598 30
							break;
599
					}
600
				}
601 30
				break;
602
			default:
603
				// unrecognized so we just return the raw dependency
604
				return $dep;
605
		}
606
		
607 192
		$normalized_dep = $this->buildStruct($struct, $dep);
608
609
		// normalize comparison operators
610 192
		if (isset($normalized_dep['comparison'])) {
611 192
			switch ($normalized_dep['comparison']) {
612 192
				case '<':
613
					$normalized_dep['comparison'] = 'lt';
614
					break;
615
616 192
				case '<=':
617
					$normalized_dep['comparison'] = 'le';
618
					break;
619
620 192
				case '>':
621
					$normalized_dep['comparison'] = 'gt';
622
					break;
623
624 192
				case '>=':
625
					$normalized_dep['comparison'] = 'ge';
626
					break;
627
628 192
				case '==':
629 192
				case 'eq':
630
					$normalized_dep['comparison'] = '=';
631
					break;
632
633 192
				case '<>':
634 192
				case 'ne':
635
					$normalized_dep['comparison'] = '!=';
636
					break;
637
			}
638
		}
639
640 192
		return $normalized_dep;
641
	}
642
643
	/**
644
	 * Returns the conflicts listed
645
	 *
646
	 * @return array
647
	 */
648 44
	public function getConflicts() {
649
		// normalize for 1.7
650 44
		if ($this->getApiVersion() < 1.8) {
651
			$conflicts = [];
652
		} else {
653 44
			$conflicts = $this->parser->getAttribute('conflicts');
654
		}
655
656 44
		if (!$conflicts) {
657 16
			$conflicts = [];
658
		}
659
660 44
		$normalized = [];
661
662 44
		foreach ($conflicts as $conflict) {
663 35
			$normalized[] = $this->buildStruct($this->depsConflictsStruct, $conflict);
664
		}
665
666 44
		return $normalized;
667
	}
668
669
	/**
670
	 * Should this plugin be activated when Elgg is installed
671
	 *
672
	 *  @return bool
673
	 */
674 1
	public function getActivateOnInstall() {
675 1
		$activate = $this->parser->getAttribute('activate_on_install');
676 1
		switch (strtolower($activate)) {
0 ignored issues
show
Bug introduced by Brett Profitt
It seems like $activate can also be of type false; however, parameter $str of strtolower() does only seem to accept string, 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

676
		switch (strtolower(/** @scrutinizer ignore-type */ $activate)) {
Loading history...
677 1
			case 'yes':
678 1
			case 'true':
679
			case 'on':
680
			case 1:
681 1
				return true;
682
683
			case 'no':
684
			case 'false':
685
			case 'off':
686
			case 0:
687
			case '':
688
				return false;
689
		}
690
	}
691
692
	/**
693
	 * Normalizes an array into the structure specified
694
	 *
695
	 * @param array $struct The struct to normalize $element to.
696
	 * @param array $array  The array
697
	 *
698
	 * @return array
699
	 */
700 192
	protected function buildStruct(array $struct, array $array) {
701 192
		$return = [];
702
703 192
		foreach ($struct as $index => $default) {
704 192
			$return[$index] = elgg_extract($index, $array, $default);
705
		}
706
707 192
		return $return;
708
	}
709
710
	/**
711
	 * Returns a category's friendly name. This can be localized by
712
	 * defining the string 'admin:plugins:category:<category>'. If no
713
	 * localization is found, returns the category with _ and - converted to ' '
714
	 * and then ucwords()'d.
715
	 *
716
	 * @param string $category The category as defined in the manifest.
717
	 * @return string A human-readable category
718
	 */
719
	static public function getFriendlyCategory($category) {
720
		$cat_raw_string = "admin:plugins:category:$category";
721
		if (_elgg_services()->translator->languageKeyExists($cat_raw_string)) {
722
			return elgg_echo($cat_raw_string);
723
		}
724
		
725
		$category = str_replace(['-', '_'], ' ', $category);
726
		return ucwords($category);
727
	}
728
}
729