ElggPluginManifest   F
last analyzed

Complexity

Total Complexity 99

Size/Duplication

Total Lines 729
Duplicated Lines 4.94 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 36
loc 729
ccs 0
cts 353
cp 0
rs 1.871
c 0
b 0
f 0
wmc 99
lcom 1
cbo 5

27 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 8 60 11
A getApiVersion() 0 3 1
A getPluginID() 0 7 2
A getManifest() 0 3 1
A getName() 0 9 3
A getID() 0 3 1
A getDescription() 0 3 1
A getBlurb() 0 9 2
A getLicense() 0 9 2
A getRepositoryURL() 0 3 1
A getBugTrackerURL() 0 3 1
A getDonationsPageURL() 0 3 1
A getVersion() 0 3 1
A getAuthor() 0 3 1
A getCopyright() 0 3 1
A getWebsite() 0 3 1
A getCategories() 0 52 4
A getScreenshots() 14 14 3
A getContributors() 14 14 3
A getProvides() 0 28 5
A getRequires() 0 30 5
A getSuggests() 0 14 3
F normalizeDep() 0 87 27
A getConflicts() 0 20 4
B getActivateOnInstall() 0 17 10
A buildStruct() 0 9 2
A getFriendlyCategory() 0 9 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ElggPluginManifest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ElggPluginManifest, and based on these observations, apply Extract Interface, too.

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 = array(
37
		'type' => '',
38
		'name' => '',
39
		'version' => '',
40
		'comparison' => 'ge'
41
	);
42
43
	/**
44
	 * The expected structure of a priority element
45
	 */
46
	private $depsStructPriority = array(
47
		'type' => '',
48
		'priority' => '',
49
		'plugin' => ''
50
	);
51
52
	/*
53
	 * The expected structure of elgg_version and elgg_release requires element
54
	 */
55
	private $depsStructElgg = array(
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 = array(
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 = array(
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 = array(
84
		'type' => '',
85
		'name' => '',
86
		'version' => '',
87
		'comparison' => '='
88
	);
89
90
	/**
91
	 * The expected structure of a conflicts depedency element
92
	 */
93
	private $depsConflictsStruct = array(
94
		'type' => '',
95
		'name' => '',
96
		'version' => '',
97
		'comparison' => '='
98
	);
99
100
	/**
101
	 * The expected structure of a provides dependency element.
102
	 */
103
	private $depsProvidesStruct = array(
104
		'type' => '',
105
		'name' => '',
106
		'version' => ''
107
	);
108
109
	/**
110
	 * The expected structure of a screenshot element
111
	 */
112
	private $screenshotStruct = array(
113
		'description' => '',
114
		'path' => ''
115
	);
116
	
117
	/**
118
	 * The expected structure of a contributor element
119
	 */
120
	private $contributorStruct = array(
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
	public function __construct($manifest, $plugin_id = null) {
152
		if ($plugin_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $plugin_id of type string|null 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
			$this->pluginID = $plugin_id;
154
		}
155
156
		// see if we need to construct the xml object.
157
		if ($manifest instanceof \ElggXMLElement) {
158
			$manifest_obj = $manifest;
159
		} else {
160
			$raw_xml = '';
161
			if (substr(trim($manifest), 0, 1) == '<') {
162
				// this is a string
163
				$raw_xml = $manifest;
164
			} elseif (is_file($manifest)) {
165
				// this is a file
166
				$raw_xml = file_get_contents($manifest);
167
			}
168
			if ($raw_xml) {
169
				$manifest_obj = new \ElggXMLElement($raw_xml);
170
			} else {
171
				$manifest_obj = null;
172
			}
173
		}
174
175
		if (!$manifest_obj) {
176
			throw new \PluginException(_elgg_services()->translator->translate('PluginException:InvalidManifest',
177
						array($this->getPluginID())));
178
		}
179
180
		// set manifest api version
181
		if (isset($manifest_obj->attributes['xmlns'])) {
182
			$namespace = $manifest_obj->attributes['xmlns'];
183
			$version = str_replace($this->namespace_root, '', $namespace);
184
		} else {
185
			$version = 1.7;
186
		}
187
188
		$this->apiVersion = $version;
189
190
		$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
			$class_exists = class_exists($parser_class_name);
195
		} catch (Exception $e) {
196
			$class_exists = false;
197
		}
198
199
		if ($class_exists) {
200
			$this->parser = new $parser_class_name($manifest_obj, $this);
201 View Code Duplication
		} else {
202
			throw new \PluginException(_elgg_services()->translator->translate('PluginException:NoAvailableParser',
203
							array($this->apiVersion, $this->getPluginID())));
204
		}
205
206 View Code Duplication
		if (!$this->parser->parse()) {
207
			throw new \PluginException(_elgg_services()->translator->translate('PluginException:ParserError',
208
						array($this->apiVersion, $this->getPluginID())));
209
		}
210
	}
211
212
	/**
213
	 * Returns the API version in use.
214
	 *
215
	 * @return int
216
	 */
217
	public function getApiVersion() {
218
		return $this->apiVersion;
219
	}
220
221
	/**
222
	 * Returns the plugin ID.
223
	 *
224
	 * @return string
225
	 */
226
	public function getPluginID() {
227
		if ($this->pluginID) {
228
			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
	public function getManifest() {
243
		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
	public function getName() {
256
		$name = $this->parser->getAttribute('name');
257
258
		if (!$name && $this->pluginID) {
259
			$name = ucwords(str_replace('_', ' ', $this->pluginID));
260
		}
261
262
		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
	public function getID() {
272
		return trim((string) $this->parser->getAttribute('id'));
273
	}
274
275
276
	/**
277
	 * Return the description
278
	 *
279
	 * @return string
280
	 */
281
	public function getDescription() {
282
		return $this->parser->getAttribute('description');
283
	}
284
285
	/**
286
	 * Return the short description
287
	 *
288
	 * @return string
289
	 */
290
	public function getBlurb() {
291
		$blurb = $this->parser->getAttribute('blurb');
292
293
		if (!$blurb) {
294
			$blurb = elgg_get_excerpt($this->getDescription());
295
		}
296
297
		return $blurb;
298
	}
299
300
	/**
301
	 * Returns the license
302
	 *
303
	 * @return string
304
	 */
305
	public function getLicense() {
306
		// license vs licence.  Use license.
307
		$en_us = $this->parser->getAttribute('license');
308
		if ($en_us) {
309
			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
	public function getRepositoryURL() {
321
		return $this->parser->getAttribute('repository');
322
	}
323
324
	/**
325
	 * Returns the bug tracker page
326
	 *
327
	 * @return string
328
	 */
329
	public function getBugTrackerURL() {
330
		return $this->parser->getAttribute('bugtracker');
331
	}
332
333
	/**
334
	 * Returns the donations page
335
	 *
336
	 * @return string
337
	 */
338
	public function getDonationsPageURL() {
339
		return $this->parser->getAttribute('donations');
340
	}
341
342
	/**
343
	 * Returns the version of the plugin.
344
	 *
345
	 * @return float
346
	 */
347
	public function getVersion() {
348
		return $this->parser->getAttribute('version');
349
	}
350
351
	/**
352
	 * Returns the plugin author.
353
	 *
354
	 * @return string
355
	 */
356
	public function getAuthor() {
357
		return $this->parser->getAttribute('author');
358
	}
359
360
	/**
361
	 * Return the copyright
362
	 *
363
	 * @return string
364
	 */
365
	public function getCopyright() {
366
		return $this->parser->getAttribute('copyright');
367
	}
368
369
	/**
370
	 * Return the website
371
	 *
372
	 * @return string
373
	 */
374
	public function getWebsite() {
375
		return $this->parser->getAttribute('website');
376
	}
377
378
	/**
379
	 * Return the categories listed for this plugin
380
	 *
381
	 * @return array
382
	 */
383
	public function getCategories() {
384
		$bundled_plugins = array(
385
			'aalborg_theme',
386
			'blog',
387
			'bookmarks',
388
			'categories',
389
			'ckeditor',
390
			'custom_index',
391
			'dashboard',
392
			'developers',
393
			'diagnostics',
394
			'embed',
395
			'externalpages',
396
			'file',
397
			'garbagecollector',
398
			'groups',
399
			'htmlawed',
400
			'invitefriends',
401
			'legacy_urls',
402
			'likes',
403
			'logbrowser',
404
			'login_as',
405
			'logrotate',
406
			'members',
407
			'messageboard',
408
			'messages',
409
			'notifications',
410
			'pages',
411
			'profile',
412
			'reportedcontent',
413
			'search',
414
			'site_notifications',
415
			'tagcloud',
416
			'thewire',
417
			'twitter_api',
418
			'uservalidationbyemail',
419
			'web_services',
420
			'zaudio',
421
		);
422
423
		$cats = $this->parser->getAttribute('category');
424
425
		if (!$cats) {
426
			$cats = array();
427
		}
428
429
		if (in_array('bundled', $cats) && !in_array($this->getPluginID(), $bundled_plugins)) {
430
			unset($cats[array_search('bundled', $cats)]);
431
		}
432
433
		return $cats;
434
	}
435
436
	/**
437
	 * Return the screenshots listed.
438
	 *
439
	 * @return array
440
	 */
441 View Code Duplication
	public function getScreenshots() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
442
		$ss = $this->parser->getAttribute('screenshot');
443
444
		if (!$ss) {
445
			$ss = array();
446
		}
447
448
		$normalized = array();
449
		foreach ($ss as $s) {
450
			$normalized[] = $this->buildStruct($this->screenshotStruct, $s);
451
		}
452
453
		return $normalized;
454
	}
455
	
456
	/**
457
	 * Return the contributors listed.
458
	 *
459
	 * @return array
460
	 */
461 View Code Duplication
	public function getContributors() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
462
		$ss = $this->parser->getAttribute('contributor');
463
464
		if (!$ss) {
465
			$ss = array();
466
		}
467
468
		$normalized = array();
469
		foreach ($ss as $s) {
470
			$normalized[] = $this->buildStruct($this->contributorStruct, $s);
471
		}
472
473
		return $normalized;
474
	}
475
476
	/**
477
	 * Return the list of provides by this plugin.
478
	 *
479
	 * @return array
480
	 */
481
	public function getProvides() {
482
		// normalize for 1.7
483
		if ($this->getApiVersion() < 1.8) {
484
			$provides = array();
485
		} else {
486
			$provides = $this->parser->getAttribute('provides');
487
		}
488
489
		if (!$provides) {
490
			$provides = array();
491
		}
492
493
		// always provide ourself if we can
494
		if ($this->pluginID) {
495
			$provides[] = array(
496
				'type' => 'plugin',
497
				'name' => $this->getPluginID(),
498
				'version' => $this->getVersion()
499
			);
500
		}
501
502
		$normalized = array();
503
		foreach ($provides as $provide) {
504
			$normalized[] = $this->buildStruct($this->depsProvidesStruct, $provide);
505
		}
506
507
		return $normalized;
508
	}
509
510
	/**
511
	 * Returns the dependencies listed.
512
	 *
513
	 * @return array
514
	 */
515
	public function getRequires() {
516
		// rewrite the 1.7 style elgg_version as a real requires.
517
		if ($this->apiVersion < 1.8) {
518
			$elgg_version = $this->parser->getAttribute('elgg_version');
519
			if ($elgg_version) {
520
				$reqs = array(
521
					array(
522
						'type' => 'elgg_version',
523
						'version' => $elgg_version,
524
						'comparison' => 'ge'
525
					)
526
				);
527
			} else {
528
				$reqs = array();
529
			}
530
		} else {
531
			$reqs = $this->parser->getAttribute('requires');
532
		}
533
534
		if (!$reqs) {
535
			$reqs = array();
536
		}
537
538
		$normalized = array();
539
		foreach ($reqs as $req) {
540
			$normalized[] = $this->normalizeDep($req);
541
		}
542
543
		return $normalized;
544
	}
545
546
	/**
547
	 * Returns the suggests elements.
548
	 *
549
	 * @return array
550
	 */
551
	public function getSuggests() {
552
		$suggests = $this->parser->getAttribute('suggests');
553
554
		if (!$suggests) {
555
			$suggests = array();
556
		}
557
558
		$normalized = array();
559
		foreach ($suggests as $suggest) {
560
			$normalized[] = $this->normalizeDep($suggest);
561
		}
562
563
		return $normalized;
564
	}
565
566
	/**
567
	 * Normalizes a dependency array using the defined structs.
568
	 * Can be used with either requires or suggests.
569
	 *
570
	 * @param array $dep A dependency array.
571
	 * @return array The normalized deps array.
572
	 */
573
	private function normalizeDep($dep) {
574
		switch ($dep['type']) {
575
			case 'elgg_version':
576
			case 'elgg_release':
577
				$struct = $this->depsStructElgg;
578
				break;
579
580
			case 'plugin':
581
				$struct = $this->depsStructPlugin;
582
				break;
583
584
			case 'priority':
585
				$struct = $this->depsStructPriority;
586
				break;
587
588
			case 'php_version':
589
				$struct = $this->depsStructPhpVersion;
590
				break;
591
			
592
			case 'php_extension':
593
				$struct = $this->depsStructPhpExtension;
594
				break;
595
596
			case 'php_ini':
597
				$struct = $this->depsStructPhpIni;
598
599
				// also normalize boolean values
600
				if (isset($dep['value'])) {
601
					switch (strtolower($dep['value'])) {
602
						case 'yes':
603
						case 'true':
604
						case 'on':
605
						case 1:
606
							$dep['value'] = 1;
607
							break;
608
609
						case 'no':
610
						case 'false':
611
						case 'off':
612
						case 0:
613
						case '':
614
							$dep['value'] = 0;
615
							break;
616
					}
617
				}
618
				break;
619
			default:
620
				// unrecognized so we just return the raw dependency
621
				return $dep;
622
		}
623
624
		// @todo $struct may not have been defined...
625
		$normalized_dep = $this->buildStruct($struct, $dep);
626
627
		// normalize comparison operators
628
		if (isset($normalized_dep['comparison'])) {
629
			switch ($normalized_dep['comparison']) {
630
				case '<':
631
					$normalized_dep['comparison'] = 'lt';
632
					break;
633
634
				case '<=':
635
					$normalized_dep['comparison'] = 'le';
636
					break;
637
638
				case '>':
639
					$normalized_dep['comparison'] = 'gt';
640
					break;
641
642
				case '>=':
643
					$normalized_dep['comparison'] = 'ge';
644
					break;
645
646
				case '==':
647
				case 'eq':
648
					$normalized_dep['comparison'] = '=';
649
					break;
650
651
				case '<>':
652
				case 'ne':
653
					$normalized_dep['comparison'] = '!=';
654
					break;
655
			}
656
		}
657
658
		return $normalized_dep;
659
	}
660
661
	/**
662
	 * Returns the conflicts listed
663
	 *
664
	 * @return array
665
	 */
666
	public function getConflicts() {
667
		// normalize for 1.7
668
		if ($this->getApiVersion() < 1.8) {
669
			$conflicts = array();
670
		} else {
671
			$conflicts = $this->parser->getAttribute('conflicts');
672
		}
673
674
		if (!$conflicts) {
675
			$conflicts = array();
676
		}
677
678
		$normalized = array();
679
680
		foreach ($conflicts as $conflict) {
681
			$normalized[] = $this->buildStruct($this->depsConflictsStruct, $conflict);
682
		}
683
684
		return $normalized;
685
	}
686
687
	/**
688
	 * Should this plugin be activated when Elgg is installed
689
	 *
690
	 *  @return bool
691
	 */
692
	public function getActivateOnInstall() {
693
		$activate = $this->parser->getAttribute('activate_on_install');
694
		switch (strtolower($activate)) {
695
			case 'yes':
696
			case 'true':
697
			case 'on':
698
			case 1:
699
				return true;
700
701
			case 'no':
702
			case 'false':
703
			case 'off':
704
			case 0:
705
			case '':
706
				return false;
707
		}
708
	}
709
710
	/**
711
	 * Normalizes an array into the structure specified
712
	 *
713
	 * @param array $struct The struct to normalize $element to.
714
	 * @param array $array  The array
715
	 *
716
	 * @return array
717
	 */
718
	protected function buildStruct(array $struct, array $array) {
719
		$return = array();
720
721
		foreach ($struct as $index => $default) {
722
			$return[$index] = elgg_extract($index, $array, $default);
723
		}
724
725
		return $return;
726
	}
727
728
	/**
729
	 * Returns a category's friendly name. This can be localized by
730
	 * defining the string 'admin:plugins:category:<category>'. If no
731
	 * localization is found, returns the category with _ and - converted to ' '
732
	 * and then ucwords()'d.
733
	 *
734
	 * @param string $category The category as defined in the manifest.
735
	 * @return string A human-readable category
736
	 */
737
	static public function getFriendlyCategory($category) {
738
		$cat_raw_string = "admin:plugins:category:$category";
739
		$cat_display_string = _elgg_services()->translator->translate($cat_raw_string);
740
		if ($cat_display_string == $cat_raw_string) {
741
			$category = str_replace(array('-', '_'), ' ', $category);
742
			$cat_display_string = ucwords($category);
743
		}
744
		return $cat_display_string;
745
	}
746
}
747