Passed
Push — master ( 9d11d1...a4b028 )
by Jeroen
10:12
created

ElggPlugin::start()   D

Complexity

Conditions 12
Paths 385

Size

Total Lines 60
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 12.2212

Importance

Changes 0
Metric Value
cc 12
eloc 25
nc 385
nop 1
dl 0
loc 60
ccs 23
cts 26
cp 0.8846
crap 12.2212
rs 4.6615
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
use Elgg\Application;
4
use Elgg\Includer;
5
6
/**
7
 * Stores site-side plugin settings as private data.
8
 *
9
 * This class is currently a stub, allowing a plugin to
10
 * save settings in an object's private settings for each site.
11
 */
12
class ElggPlugin extends ElggObject {
13
14
	/**
15
	 * @var ElggPluginPackage
16
	 */
17
	protected $package;
18
19
	/**
20
	 * @var ElggPluginManifest
21
	 */
22
	protected $manifest;
23
24
	/**
25
	 * @var string
26
	 */
27
	protected $path;
28
29
	/**
30
	 * Data from static config file. null if not yet read.
31
	 *
32
	 * @var array|null
33
	 */
34
	protected $static_config;
35
36
	/**
37
	 * @var string
38
	 */
39
	protected $errorMsg = '';
40
41
	/**
42
	 * {@inheritdoc}
43
	 */
44 309
	protected function initializeAttributes() {
45 309
		parent::initializeAttributes();
46
47 309
		$this->attributes['subtype'] = "plugin";
48 309
	}
49
50
	/**
51
	 * Load a plugin object from its ID
52
	 * Create a new plugin entity if doesn't exist
53
	 *
54
	 * @param string $plugin_id Plugin ID
55
	 * @param string $path      Path, defaults to /mod
56
	 *
57
	 * @return ElggPlugin
58
	 * @throws PluginException
59
	 */
60 291
	public static function fromId($plugin_id, $path = null) {
61 291
		if (empty($plugin_id)) {
62 1
			throw new PluginException('Plugin ID must be set');
63
		}
64
65 290
		$plugin = elgg_get_plugin_from_id($plugin_id);
66
67 290
		if (!$plugin) {
68 5
			$ia = elgg_set_ignore_access(true);
69 5
			$plugin = new ElggPlugin();
70 5
			$plugin->title = $plugin_id;
71 5
			$plugin->save();
72
73 5
			elgg_set_ignore_access($ia);
74
		}
75
76 290
		if (!$path) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type null|string is loosely compared to false; 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...
77 284
			$path = elgg_get_plugins_path();
78
		}
79
80 290
		$path = rtrim($path, '/');
81 290
		$plugin->setPath($path . '/' . $plugin_id);
82
83 290
		return $plugin;
84
	}
85
86
	/**
87
	 * {@inheritdoc}
88
	 */
89 7
	public function save() {
90
91 7
		$site = elgg_get_site_entity();
92
93 7
		$this->attributes['owner_guid'] = $site->guid;
94 7
		$this->attributes['container_guid'] = $site->guid;
95 7
		$this->attributes['access_id'] = ACCESS_PUBLIC;
96
97 7
		$new = !$this->guid;
98 7
		$priority = null;
99 7
		if ($new) {
100 7
			$name = _elgg_services()->plugins->namespacePrivateSetting('internal', 'priority');
101 7
			$priority = elgg_extract($name, $this->temp_private_settings, 'new');
102 2
		} else if (!$this->getPriority()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getPriority() of type null|integer is loosely compared to false; this is ambiguous if the integer can be 0. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
103
			$priority = 'last';
104
		}
105
106 7
		$guid = parent::save();
107 7
		if ($guid && $priority) {
108 7
			$this->setPriority($new ? 'new' : 'last');
109
		}
110
111 7
		return $guid;
112
	}
113
114
	/**
115
	 * Returns the ID (dir name) of this plugin
116
	 *
117
	 * @return string
118
	 */
119 314
	public function getID() {
120 314
		return $this->title;
121
	}
122
123
	/**
124
	 * Returns the manifest's name if available, otherwise the ID.
125
	 *
126
	 * @return string
127
	 * @since 3.0
128
	 */
129 2
	public function getDisplayName() {
130 2
		$manifest = $this->getManifest();
131 2
		if ($manifest) {
0 ignored issues
show
introduced by
The condition $manifest can never be true.
Loading history...
132 1
			return $manifest->getName();
133
		}
134
135 1
		return $this->getID();
136
	}
137
138
	/**
139
	 * Set path
140
	 *
141
	 * @param string $path Path to plugin directory
142
	 *
143
	 * @return void
144
	 * @access private
145
	 */
146 290
	public function setPath($path) {
147 290
		$this->path = $path;
148 290
	}
149
150
	/**
151
	 * Returns the plugin's full path with trailing slash.
152
	 *
153
	 * @return string
154
	 */
155 295
	public function getPath() {
156 295
		if (isset($this->path)) {
157 283
			$path = $this->path;
158
		} else {
159 14
			$path = elgg_get_plugins_path() . $this->getID();
160
		}
161
162 295
		return \Elgg\Project\Paths::sanitize($path, true);
163
	}
164
165
	/**
166
	 * Get a value from the plugins's static config file.
167
	 *
168
	 * @note     If the system cache is on, Elgg APIs should not call this on every request.
169
	 *
170
	 * @param string $key     Config key
171
	 * @param mixed  $default Value returned if missing
172
	 *
173
	 * @return mixed
174
	 * @access   private
175
	 * @internal For Elgg internal use only
176
	 */
177 99
	public function getStaticConfig($key, $default = null) {
178 99
		if ($this->static_config === null) {
179 86
			$this->static_config = [];
180
181
			try {
182 86
				if ($this->canReadFile(ElggPluginPackage::STATIC_CONFIG_FILENAME)) {
183 86
					$this->static_config = $this->includeFile(ElggPluginPackage::STATIC_CONFIG_FILENAME);
184
				}
185
			} catch (PluginException $ex) {
186
				elgg_log($ex->getMessage(), 'WARNING');
187
			}
188
		}
189
190 99
		if (isset($this->static_config[$key])) {
191 73
			return $this->static_config[$key];
192
		} else {
193 96
			return $default;
194
		}
195
	}
196
197
	/**
198
	 * Returns an array of available markdown files for this plugin
199
	 *
200
	 * @return array
201
	 */
202 1
	public function getAvailableTextFiles() {
203 1
		$filenames = $this->getPackage()->getTextFilenames();
204
205 1
		$files = [];
206 1
		foreach ($filenames as $filename) {
207 1
			if ($this->canReadFile($filename)) {
208 1
				$files[$filename] = "{$this->getPath()}{$filename}";
209
			}
210
		}
211
212 1
		return $files;
213
	}
214
215
	// Load Priority
216
217
	/**
218
	 * Gets the plugin's load priority.
219
	 *
220
	 * @return int|null
221
	 */
222 8
	public function getPriority() {
223 8
		$name = _elgg_services()->plugins->namespacePrivateSetting('internal', 'priority');
224
225 8
		$priority = $this->getSetting($name);
226 8
		if (isset($priority)) {
227 5
			return (int) $priority;
228
		}
229
230 7
		return null;
231
	}
232
233
	/**
234
	 * Sets the priority of the plugin
235
	 * Returns the new priority or false on error
236
	 *
237
	 * @param mixed $priority The priority to set
238
	 *                        One of +1, -1, first, last, or a number.
239
	 *                        If given a number, this will displace all plugins at that number
240
	 *                        and set their priorities +1
241
	 *
242
	 * @return int|false
243
	 * @throws DatabaseException
244
	 */
245 7
	public function setPriority($priority) {
246 7
		$priority = $this->normalizePriority($priority);
247
248 7
		return _elgg_services()->plugins->setPriority($this, $priority);
249
	}
250
251
	/**
252
	 * Normalize and validate new priority
253
	 *
254
	 * @param mixed $priority Priority to normalize
255
	 *
256
	 * @return int
257
	 * @access private
258
	 */
259 7
	public function normalizePriority($priority) {
260
		// if no priority assume a priority of 1
261 7
		$old_priority = $this->getPriority();
262 7
		$old_priority = $old_priority ? : 1;
263 7
		$max_priority = _elgg_get_max_plugin_priority();
264
265
		// can't use switch here because it's not strict and php evaluates +1 == 1
266 7
		if ($priority === '+1') {
267 1
			$priority = $old_priority + 1;
268 7
		} else if ($priority === '-1') {
269 1
			$priority = $old_priority - 1;
270 7
		} else if ($priority === 'first') {
271 1
			$priority = 1;
272 7
		} else if ($priority === 'last') {
273 1
			$priority = $max_priority;
274 7
		} else if ($priority === 'new') {
275 7
			$max_priority++;
276 7
			$priority = $max_priority;
277
		}
278
279 7
		return min($max_priority, max(1, (int) $priority));
280
	}
281
282
	// Plugin settings
283
284
	/**
285
	 * Returns a plugin setting
286
	 *
287
	 * @param string $name    The setting name
288
	 * @param mixed  $default The default value to return if none is set
289
	 *
290
	 * @return mixed
291
	 */
292 40
	public function getSetting($name, $default = null) {
293 40
		$values = $this->getAllSettings();
294 40
		return elgg_extract($name, $values, $default);
295
	}
296
297
	/**
298
	 * Returns an array of all settings saved for this plugin.
299
	 *
300
	 * @note Unlike user settings, plugin settings are not namespaced.
301
	 *
302
	 * @return array An array of key/value pairs.
303
	 */
304 40
	public function getAllSettings() {
305
306
		try {
307 40
			$defaults = [];
308 40
			if ($this->isActive()) {
309
				// only load settings from static config for active plugins to prevent issues
310
				// with internal plugin references ie. classes and language keys
311 15
				$defaults = $this->getStaticConfig('settings', []);
312
			}
313
314 40
			if (!$this->guid) {
315 1
				$settings = $this->temp_private_settings;
316
			} else {
317 39
				$settings = _elgg_services()->plugins->getAllSettings($this);
318
			}
319
320 40
			return array_merge($defaults, $settings);
321
		} catch (DatabaseException $ex) {
322
			return [];
323
		}
324
	}
325
326
	/**
327
	 * Set a plugin setting for the plugin
328
	 *
329
	 * @param string $name  The name to set
330
	 * @param string $value The value to set
331
	 *
332
	 * @return bool
333
	 */
334 4
	public function setSetting($name, $value) {
335
336 4
		$value = elgg_trigger_plugin_hook('setting', 'plugin', [
337 4
			'plugin_id' => $this->getID(),
338 4
			'plugin' => $this,
339 4
			'name' => $name,
340 4
			'value' => $value,
341 4
		], $value);
342
343 4
		if (is_array($value)) {
0 ignored issues
show
introduced by
The condition is_array($value) can never be true.
Loading history...
344 1
			elgg_log('Plugin settings cannot store arrays.', 'ERROR');
345
346 1
			return false;
347
		}
348
349 4
		return $this->setPrivateSetting($name, $value);
350
	}
351
352
	/**
353
	 * Removes a plugin setting name and value
354
	 *
355
	 * @param string $name The setting name to remove
356
	 *
357
	 * @return bool
358
	 */
359 1
	public function unsetSetting($name) {
360 1
		return $this->removePrivateSetting($name);
361
	}
362
363
	/**
364
	 * Removes all settings for this plugin
365
	 * @return bool
366
	 */
367 1
	public function unsetAllSettings() {
368 1
		$settings = $this->getAllSettings();
369
370 1
		foreach ($settings as $name => $value) {
371 1
			if (strpos($name, 'elgg:internal:') === 0) {
372 1
				continue;
373
			}
374 1
			$this->unsetSetting($name);
375
		}
376
377 1
		return true;
378
	}
379
380
381
	// User settings
382
383
	/**
384
	 * Returns a user's setting for this plugin
385
	 *
386
	 * @param string $name      The setting name
387
	 * @param int    $user_guid The user GUID
388
	 * @param mixed  $default   The default value to return if none is set
389
	 *
390
	 * @return mixed The setting string value, the default value or false if there is no user
391
	 * @throws DatabaseException
392
	 */
393 1
	public function getUserSetting($name, $user_guid = 0, $default = null) {
394 1
		$values = $this->getAllUserSettings($user_guid);
395 1
		if ($values === false) {
0 ignored issues
show
introduced by
The condition $values === false can never be true.
Loading history...
396
			return false;
397
		}
398
399 1
		return elgg_extract($name, $values, $default);
400
	}
401
402
	/**
403
	 * Returns an array of all user settings saved for this plugin for the user.
404
	 *
405
	 * @note Plugin settings are saved with a prefix. This removes that prefix.
406
	 *
407
	 * @param int $user_guid The user GUID. Defaults to logged in.
408
	 *
409
	 * @return array An array of key/value pairs
410
	 * @throws DatabaseException
411
	 */
412 1
	public function getAllUserSettings($user_guid = 0) {
413
414 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
415 1
		if (!$user instanceof ElggUser) {
0 ignored issues
show
introduced by
The condition ! $user instanceof ElggUser can never be true.
Loading history...
416
			return [];
417
		}
418
419 1
		$defaults = $this->getStaticConfig('user_settings', []);
420
421 1
		$settings = _elgg_services()->plugins->getAllUserSettings($this, $user);
422
423 1
		return array_merge($defaults, $settings);
424
	}
425
426
	/**
427
	 * Sets a user setting for a plugin
428
	 *
429
	 * @param string $name      The setting name
430
	 * @param string $value     The setting value
431
	 * @param int    $user_guid The user GUID
432
	 *
433
	 * @return mixed The new setting ID or false
434
	 */
435 1
	public function setUserSetting($name, $value, $user_guid = 0) {
436 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
437 1
		if (!$user instanceof ElggUser) {
0 ignored issues
show
introduced by
The condition ! $user instanceof ElggUser can never be true.
Loading history...
438
			return false;
439
		}
440
441 1
		$value = _elgg_services()->hooks->trigger('usersetting', 'plugin', [
442 1
			'user' => $user,
443 1
			'plugin' => $this,
444 1
			'plugin_id' => $this->getID(),
445 1
			'name' => $name,
446 1
			'value' => $value
447 1
		], $value);
448
449 1
		if (is_array($value)) {
0 ignored issues
show
introduced by
The condition is_array($value) can never be true.
Loading history...
450 1
			elgg_log('Plugin user settings cannot store arrays.', 'ERROR');
451
452 1
			return false;
453
		}
454
455 1
		$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
456
457 1
		return $user->setPrivateSetting($name, $value);
458
	}
459
460
	/**
461
	 * Removes a user setting name and value.
462
	 *
463
	 * @param string $name      The user setting name
464
	 * @param int    $user_guid The user GUID
465
	 *
466
	 * @return bool
467
	 */
468 1
	public function unsetUserSetting($name, $user_guid = 0) {
469 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
470
471 1
		if (!$user instanceof ElggUser) {
0 ignored issues
show
introduced by
The condition ! $user instanceof ElggUser can never be true.
Loading history...
472
			return false;
473
		}
474
475 1
		$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
476
477 1
		return $user->removePrivateSetting($name);
478
	}
479
480
	/**
481
	 * Removes all plugin settings for a given user
482
	 *
483
	 * @param int $user_guid The user GUID to remove user settings.
484
	 *
485
	 * @return bool
486
	 * @throws DatabaseException
487
	 */
488 1
	public function unsetAllUserSettings($user_guid = 0) {
489 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
490
491 1
		if (!$user instanceof ElggUser) {
0 ignored issues
show
introduced by
The condition ! $user instanceof ElggUser can never be true.
Loading history...
492
			return false;
493
		}
494
495 1
		$settings = $this->getAllUserSettings($user_guid);
496
497 1
		foreach ($settings as $name => $value) {
498 1
			$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
499 1
			$user->removePrivateSetting($name);
500
		}
501
502 1
		return true;
503
	}
504
505
	/**
506
	 * Returns if the plugin is complete, meaning has all required files
507
	 * and Elgg can read them and they make sense.
508
	 *
509
	 * @return bool
510
	 */
511 1
	public function isValid() {
512 1
		if (!$this->getID()) {
513
			$this->errorMsg = _elgg_services()->translator->translate('ElggPlugin:MissingID', [$this->guid]);
514
515
			return false;
516
		}
517
518 1
		if (!$this->getPackage() instanceof ElggPluginPackage) {
0 ignored issues
show
introduced by
The condition ! $this->getPackage() in...nceof ElggPluginPackage can never be true.
Loading history...
519
			$this->errorMsg = _elgg_services()->translator->translate('ElggPlugin:NoPluginPackagePackage', [
520
				$this->getID(),
521
				$this->guid
522
			]);
523
524
			return false;
525
		}
526
527 1
		if (!$this->getPackage()->isValid()) {
528
			$this->errorMsg = $this->getPackage()->getError();
529
530
			return false;
531
		}
532
533 1
		return true;
534
	}
535
536
	/**
537
	 * Is this plugin active?
538
	 *
539
	 * @return bool
540
	 */
541 78
	public function isActive() {
542 78
		if (!$this->guid) {
543 1
			return false;
544
		}
545
546 77
		$site = elgg_get_site_entity();
547
548 77
		if (!($site instanceof \ElggSite)) {
0 ignored issues
show
introduced by
The condition ! $site instanceof ElggSite can never be true.
Loading history...
549 13
			return false;
550
		}
551
552 77
		return check_entity_relationship($this->guid, 'active_plugin', $site->guid) instanceof ElggRelationship;
553
	}
554
555
	/**
556
	 * Checks if this plugin can be activated on the current
557
	 * Elgg installation.
558
	 *
559
	 * @return bool
560
	 */
561 3
	public function canActivate() {
562 3
		if ($this->isActive()) {
563
			return false;
564
		}
565
566 3
		if ($this->getPackage()) {
567 3
			$result = $this->getPackage()->isValid() && $this->getPackage()->checkDependencies();
568 3
			if (!$result) {
569
				$this->errorMsg = $this->getPackage()->getError();
570
			}
571
572 3
			return $result;
573
		}
574
575
		return false;
576
	}
577
578
579
	// activating and deactivating
580
581
	/**
582
	 * Actives the plugin for the current site.
583
	 *
584
	 * @return bool
585
	 * @throws PluginException
586
	 */
587 3
	public function activate() {
588 3
		if ($this->isActive()) {
589
			return false;
590
		}
591
592 3
		if (!$this->canActivate()) {
593
			return false;
594
		}
595
596
		// Check this before setting status because the file could potentially throw
597 3
		if (!$this->isStaticConfigValid()) {
598
			return false;
599
		}
600
601 3
		if (!$this->setStatus(true)) {
602
			return false;
603
		}
604
605
		// perform tasks and emit events
606
		// emit an event. returning false will make this not be activated.
607
		// we need to do this after it's been fully activated
608
		// or the deactivate will be confused.
609
		$params = [
610 3
			'plugin_id' => $this->getID(),
611 3
			'plugin_entity' => $this,
612
		];
613
614 3
		$return = _elgg_services()->hooks->getEvents()->trigger('activate', 'plugin', $params);
615
616
		// if there are any on_enable functions, start the plugin now and run them
617
		// Note: this will not run re-run the init hooks!
618 3
		if ($return) {
619 3
			$this->activateEntities();
620
621 3
			if ($this->canReadFile('activate.php')) {
622
				_elgg_services()->hooks->getEvents()->trigger('cache:flush', 'system');
623
624
				$flags = ELGG_PLUGIN_INCLUDE_START |
625
						ELGG_PLUGIN_REGISTER_CLASSES |
626
						ELGG_PLUGIN_REGISTER_LANGUAGES |
627
						ELGG_PLUGIN_REGISTER_VIEWS |
628
						ELGG_PLUGIN_REGISTER_WIDGETS |
629
						ELGG_PLUGIN_REGISTER_ACTIONS |
630
						ELGG_PLUGIN_REGISTER_ROUTES;
631
632
				$this->start($flags);
633
634
				$return = $this->includeFile('activate.php');
635
			}
636
		}
637
638 3
		if ($return === false) {
639
			$this->deactivate();
640
		}
641
642 3
		_elgg_services()->plugins->setBootPlugins(null);
643
644 3
		_elgg_services()->logger->notice("Plugin {$this->getID()} has been activated");
645
646 3
		return $return;
647
	}
648
649
	/**
650
	 * Checks if this plugin can be deactivated on the current
651
	 * Elgg installation. Validates that this plugin has no
652
	 * active dependants.
653
	 *
654
	 * @return bool
655
	 */
656 1
	public function canDeactivate() {
657 1
		if (!$this->isActive()) {
658
			return false;
659
		}
660
661 1
		$dependents = [];
662
663 1
		$active_plugins = elgg_get_plugins();
664
665 1
		foreach ($active_plugins as $plugin) {
666 1
			$manifest = $plugin->getManifest();
667 1
			if (!$manifest) {
0 ignored issues
show
introduced by
The condition ! $manifest can never be false.
Loading history...
668
				return true;
669
			}
670 1
			$requires = $manifest->getRequires();
671
672 1
			foreach ($requires as $required) {
673 1
				if ($required['type'] == 'plugin' && $required['name'] == $this->getID()) {
674
					// there are active dependents
675 1
					$dependents[$manifest->getPluginID()] = $plugin;
676
				}
677
			}
678
		}
679
680 1
		if (!empty($dependents)) {
681
			$list = array_map(function (\ElggPlugin $plugin) {
682
				$css_id = preg_replace('/[^a-z0-9-]/i', '-', $plugin->getManifest()->getID());
683
684
				return elgg_view('output/url', [
685
					'text' => $plugin->getDisplayName(),
686
					'href' => "#$css_id",
687
				]);
688
			}, $dependents);
689
			$name = $this->getDisplayName();
690
			$list = implode(', ', $list);
691
			$this->errorMsg = elgg_echo('ElggPlugin:Dependencies:ActiveDependent', [$name, $list]);
692
693
			return false;
694
		}
695
696 1
		return true;
697
	}
698
699
	/**
700
	 * Deactivates the plugin.
701
	 *
702
	 * @return bool
703
	 * @throws PluginException
704
	 */
705 1
	public function deactivate() {
706 1
		if (!$this->isActive()) {
707
			return false;
708
		}
709
710 1
		if (!$this->canDeactivate()) {
711
			return false;
712
		}
713
714
		// emit an event. returning false will cause this to not be deactivated.
715
		$params = [
716 1
			'plugin_id' => $this->getID(),
717 1
			'plugin_entity' => $this,
718
		];
719
720 1
		$return = _elgg_services()->hooks->getEvents()->trigger('deactivate', 'plugin', $params);
721 1
		if ($return === false) {
722
			return false;
723
		}
724
725
		// run any deactivate code
726 1
		if ($this->canReadFile('deactivate.php')) {
727
			// allows you to prevent disabling a plugin by returning false in a deactivate.php file
728
			if ($this->includeFile('deactivate.php') === false) {
729
				return false;
730
			}
731
		}
732
733 1
		$this->deactivateEntities();
734
735 1
		_elgg_services()->hooks->getEvents()->trigger('cache:flush', 'system');
736
737 1
		_elgg_services()->logger->notice("Plugin {$this->getID()} has been deactivated");
738
739 1
		_elgg_services()->plugins->setBootPlugins(null);
740
741 1
		return $this->setStatus(false);
742
	}
743
744
	/**
745
	 * Start the plugin.
746
	 *
747
	 * @param int $flags Start flags for the plugin. See the constants in lib/plugins.php for details.
748
	 *
749
	 * @return true
750
	 * @throws PluginException
751
	 * @throws InvalidParameterException
752
	 */
753 101
	public function start($flags) {
754
755 101
		if (!($flags & ELGG_PLUGIN_IGNORE_MANIFEST)) {
756
			// Detect plugins errors early and throw so that plugins service can disable the plugin
757 34
			if (!$this->getManifest()) {
758
				throw new PluginException($this->getError());
759
			}
760
		}
761
762
		// include classes
763 101
		if ($flags & ELGG_PLUGIN_REGISTER_CLASSES) {
764 101
			$this->registerClasses();
765
766 101
			$autoload_file = 'vendor/autoload.php';
767 101
			if ($this->canReadFile($autoload_file)) {
768
				Application::requireSetupFileOnce("{$this->getPath()}{$autoload_file}");
769
			}
770
		}
771
772
		// include languages
773 101
		// should be loaded before the first function that touches the static config (elgg-plugin.php)
774
		// so translations can be used... for example in registering widgets
775
		$this->registerLanguages($flags & ELGG_PLUGIN_REGISTER_LANGUAGES);
0 ignored issues
show
Bug introduced by
$flags & ELGG_PLUGIN_REGISTER_LANGUAGES of type integer is incompatible with the type boolean expected by parameter $path_only of ElggPlugin::registerLanguages(). ( Ignorable by Annotation )

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

775
		$this->registerLanguages(/** @scrutinizer ignore-type */ $flags & ELGG_PLUGIN_REGISTER_LANGUAGES);
Loading history...
776 24
		
777
		// include start file if it exists
778
		if ($flags & ELGG_PLUGIN_INCLUDE_START) {
779
			$this->activateEntities();
780 101
781 95
			if ($this->canReadFile('start.php')) {
782
				$result = Application::requireSetupFileOnce("{$this->getPath()}start.php");
783 95
				if ($result instanceof \Closure) {
784 95
					$result();
785 95
				}
786
			}
787
788
			$this->registerEntities();
789
		}
790 95
791
		// include views
792
		if ($flags & ELGG_PLUGIN_REGISTER_VIEWS) {
793
			$this->registerViews();
794 101
		}
795 85
796
		// include actions
797
		if ($flags & ELGG_PLUGIN_REGISTER_ACTIONS) {
798
			$this->registerActions();
799 101
		}
800 34
801
		// include routes
802
		if ($flags & ELGG_PLUGIN_REGISTER_ROUTES) {
803
			$this->registerRoutes();
804 101
		}
805 13
806
		// include widgets
807
		if ($flags & ELGG_PLUGIN_REGISTER_WIDGETS) {
808
			// should load after views because those are used during registration
809 101
			$this->registerWidgets();
810
		}
811 34
812
		return true;
813
	}
814 101
815
	/**
816
	 * Includes one of the plugins files
817
	 *
818
	 * @param string $filename The name of the file
819
	 *
820
	 * @throws PluginException
821
	 * @return mixed The return value of the included file (or 1 if there is none)
822
	 */
823
	protected function includeFile($filename) {
824
		$filepath = "{$this->getPath()}{$filename}";
825 85
826 85
		if (!$this->canReadFile($filename)) {
827
			$msg = _elgg_services()->translator->translate('ElggPlugin:Exception:CannotIncludeFile',
828 85
				[$filename, $this->getID(), $this->guid, $this->getPath()]);
829
			throw new PluginException($msg);
830
		}
831
832
		try {
833
			$ret = Includer::includeFile($filepath);
834
		} catch (Exception $e) {
835 85
			$msg = _elgg_services()->translator->translate('ElggPlugin:Exception:IncludeFileThrew',
836
				[$filename, $this->getID(), $this->guid, $this->getPath()]);
837
			throw new PluginException($msg, 0, $e);
838
		}
839
840
		return $ret;
841
	}
842 85
843
	/**
844
	 * Checks whether a plugin file with the given name exists
845
	 *
846
	 * @param string $filename The name of the file
847
	 *
848
	 * @return bool
849
	 */
850
	protected function canReadFile($filename) {
851
		$path = "{$this->getPath()}{$filename}";
852 105
853 105
		return is_file($path) && is_readable($path);
854
	}
855 105
856
	/**
857
	 * If a static config file is present, is it a serializable array?
858
	 *
859
	 * @return bool
860
	 * @throws PluginException
861
	 */
862
	private function isStaticConfigValid() {
863
		if (!$this->canReadFile(ElggPluginPackage::STATIC_CONFIG_FILENAME)) {
864 3
			return true;
865 3
		}
866
867
		ob_start();
868
		$value = $this->includeFile(ElggPluginPackage::STATIC_CONFIG_FILENAME);
869 3
		if (ob_get_clean() !== '') {
870 3
			$this->errorMsg = _elgg_services()->translator->translate('ElggPlugin:activate:ConfigSentOutput');
871 3
872
			return false;
873
		}
874
875
		// make sure can serialize
876
		$value = @unserialize(serialize($value));
877
		if (!is_array($value)) {
878 3
			$this->errorMsg = _elgg_services()->translator->translate('ElggPlugin:activate:BadConfigFormat');
879 3
880
			return false;
881
		}
882
883
		return true;
884
	}
885 3
886
	/**
887
	 * Registers the plugin's views
888
	 *
889
	 * @throws PluginException
890
	 * @return void
891
	 */
892
	protected function registerViews() {
893
		$views = _elgg_services()->views;
894 85
895 85
		// Declared views first
896
		$file = "{$this->getPath()}views.php";
897
		if (is_file($file)) {
898 85
			$spec = Includer::includeFile($file);
899 85
			if (is_array($spec)) {
900
				$views->mergeViewsSpec($spec);
901
			}
902
		}
903
904
		$spec = $this->getStaticConfig('views');
905
		if ($spec) {
906 85
			$views->mergeViewsSpec($spec);
907 85
		}
908 5
909
		// Allow /views directory files to override
910
		if (!$views->registerPluginViews($this->getPath(), $failed_dir)) {
911
			$key = 'ElggPlugin:Exception:CannotRegisterViews';
912 85
			$args = [$this->getID(), $this->guid, $failed_dir];
913
			$msg = _elgg_services()->translator->translate($key, $args);
914
			throw new PluginException($msg);
915
		}
916
	}
917
918 85
	/**
919
	 * Registers the plugin's entities
920
	 *
921
	 * @return void
922
	 */
923
	protected function registerEntities() {
924
925 95
		$spec = (array) $this->getStaticConfig('entities', []);
926
		if (empty($spec)) {
927 95
			return;
928 95
		}
929 50
930
		foreach ($spec as $entity) {
931
			if (isset($entity['type'], $entity['subtype'], $entity['searchable']) && $entity['searchable']) {
932 58
				elgg_register_entity_type($entity['type'], $entity['subtype']);
933 58
			}
934 58
		}
935
	}
936
937 58
	/**
938
	 * Registers the plugin's actions provided in the plugin config file
939
	 *
940
	 * @return void
941
	 */
942
	protected function registerActions() {
943
		self::addActionsFromStaticConfig($this->getStaticConfig('actions', []), $this->getPath());
944 34
	}
945 34
946 34
	/**
947
	 * Register a plugin's actions provided in the config file
948
	 *
949
	 * @todo   move to a static config service
950
	 *
951
	 * @param array  $spec      'actions' section of static config
952
	 * @param string $root_path Plugin path
953
	 *
954
	 * @return void
955
	 * @access private
956
	 * @internal
957
	 */
958
	public static function addActionsFromStaticConfig(array $spec, $root_path) {
959
		$actions = _elgg_services()->actions;
960 34
		$root_path = rtrim($root_path, '/\\');
961 34
962 34
		foreach ($spec as $action => $action_spec) {
963
			if (!is_array($action_spec)) {
964 34
				continue;
965 33
			}
966
967
			$options = [
968
				'access' => 'logged_in',
969
				'filename' => '', // assuming core action is registered
970 33
			];
971
972
			$options = array_merge($options, $action_spec);
973
974 33
			$filename = "$root_path/actions/{$action}.php";
975
			if (is_file($filename)) {
976 33
				$options['filename'] = $filename;
977 33
			}
978 33
979
			$actions->register($action, $options['filename'], $options['access']);
980
		}
981 33
	}
982
983 34
	/**
984
	 * Registers the plugin's routes provided in the plugin config file
985
	 *
986
	 * @throws PluginException
987
	 * @return void
988
	 */
989
	protected function registerRoutes() {
990
		$router = _elgg_services()->router;
991 13
992 13
		$spec = (array) $this->getStaticConfig('routes', []);
993
994 13
		foreach ($spec as $name => $route_spec) {
995
			if (!is_array($route_spec)) {
996 13
				continue;
997 13
			}
998
999
			$router->registerRoute($name, $route_spec);
1000
		}
1001 13
	}
1002
1003 13
	/**
1004
	 * Registers the plugin's widgets provided in the plugin config file
1005
	 *
1006
	 * @return void
1007
	 * @throws \InvalidParameterException
1008
	 */
1009
	protected function registerWidgets() {
1010
		$widgets = _elgg_services()->widgets;
1011 34
1012 34
		$spec = (array) $this->getStaticConfig('widgets', []);
1013
		foreach ($spec as $widget_id => $widget_definition) {
1014 34
			if (!is_array($widget_definition)) {
1015 34
				continue;
1016 24
			}
1017
			if (!isset($widget_definition['id'])) {
1018
				$widget_definition['id'] = $widget_id;
1019 24
			}
1020 24
1021
			$definition = \Elgg\WidgetDefinition::factory($widget_definition);
1022
1023 24
			$widgets->registerType($definition);
1024
		}
1025 24
	}
1026
1027 34
	/**
1028
	 * Registers the plugin's languages
1029
	 *
1030
	 * @param boolean $path_only we need to register the path only
1031
	 *
1032
	 * @return true
1033
	 */
1034 24
	protected function registerLanguages($path_only = false) {
1035 24
		$languages_path = $this->getPath() . 'languages';
1036
		if (!is_dir($languages_path)) {
1037
			return true;
1038
		}
1039
		
1040
		if ($path_only) {
1041
			return _elgg_services()->translator->registerLanguagePath($languages_path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return _elgg_services()-...gePath($languages_path) returns the type void which is incompatible with the documented return type true.
Loading history...
Bug introduced by
Are you sure the usage of _elgg_services()->transl...gePath($languages_path) targeting Elgg\I18n\Translator::registerLanguagePath() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1042
		}
1043 101
		
1044 101
		return _elgg_services()->translator->registerTranslations($languages_path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return _elgg_services()-...ations($languages_path) returns the type boolean which is incompatible with the documented return type true.
Loading history...
1045
	}
1046 101
1047 77
	/**
1048
	 * Registers the plugin's classes
1049
	 *
1050 101
	 * @return true
1051
	 */
1052
	protected function registerClasses() {
1053
		$classes_path = "{$this->getPath()}classes";
1054
1055
		if (is_dir($classes_path)) {
1056
			_elgg_services()->autoloadManager->addClasses($classes_path);
1057
		}
1058 97
1059 97
		return true;
1060 97
	}
1061 50
1062
	/**
1063
	 * Activates the plugin's entities
1064 60
	 *
1065 60
	 * @return void
1066 60
	 */
1067
	protected function activateEntities() {
1068
		$spec = (array) $this->getStaticConfig('entities', []);
1069 60
		if (empty($spec)) {
1070
			return;
1071
		}
1072
1073
		foreach ($spec as $entity) {
1074
			if (isset($entity['type'], $entity['subtype'], $entity['class'])) {
1075
				elgg_set_entity_class($entity['type'], $entity['subtype'], $entity['class']);
1076 1
			}
1077 1
		}
1078 1
	}
1079 1
1080
	/**
1081
	 * Deactivates the plugin's entities
1082
	 *
1083
	 * @return void
1084
	 */
1085
	protected function deactivateEntities() {
1086
		$spec = (array) $this->getStaticConfig('entities', []);
1087
		if (empty($spec)) {
1088
			return;
1089
		}
1090
1091
		foreach ($spec as $entity) {
1092
			if (isset($entity['type'], $entity['subtype'], $entity['class'])) {
1093
				elgg_set_entity_class($entity['type'], $entity['subtype']);
1094
			}
1095
		}
1096 352
	}
1097
1098 352
	/**
1099 352
	 * Get an attribute, metadata or private setting value
1100
	 *
1101
	 * @param string $name Name of the attribute or private setting
1102
	 *
1103 314
	 * @return mixed
1104 314
	 */
1105
	public function __get($name) {
1106
		// See if its in our base attribute
1107 3
		if (array_key_exists($name, $this->attributes)) {
1108 3
			return $this->attributes[$name];
1109 1
		}
1110
1111
		// object title and description are stored as metadata
1112 2
		if (in_array($name, ['title', 'description'])) {
1113
			return parent::__get($name);
1114 2
		}
1115
1116
		$result = $this->getPrivateSetting($name);
1117
		if ($result !== null) {
1118
			return $result;
1119
		}
1120
1121
		$defaults = $this->getStaticConfig('settings', []);
1122
1123
		return elgg_extract($name, $defaults, $result);
1124
	}
1125
1126
	/**
1127 288
	 * Set a value as attribute, metadata or private setting.
1128 288
	 *
1129
	 * Metadata applies to title and description.
1130 1
	 *
1131
	 * @param string $name  Name of the attribute or private_setting
1132
	 * @param mixed  $value Value to be set
1133
	 *
1134 1
	 * @return void
1135
	 */
1136 1
	public function __set($name, $value) {
1137
		if (array_key_exists($name, $this->attributes)) {
1138
			// Check that we're not trying to change the guid!
1139
			if ((array_key_exists('guid', $this->attributes)) && ($name == 'guid')) {
1140 288
				return;
1141 288
			}
1142
1143 288
			$this->attributes[$name] = $value;
1144
1145
			return;
1146
		}
1147 2
1148 2
		// object title and description are stored as metadata
1149
		if (in_array($name, ['title', 'description'])) {
1150
			parent::__set($name, $value);
1151
1152
			return;
1153
		}
1154
1155
		// to make sure we trigger the correct hooks
1156
		$this->setSetting($name, $value);
1157 3
	}
1158 3
1159
	/**
1160
	 * Sets the plugin to active or inactive.
1161
	 *
1162 3
	 * @param bool $active Set to active or inactive
1163 3
	 *
1164 3
	 * @return bool
1165
	 */
1166 1
	private function setStatus($active) {
1167
		if (!$this->guid) {
1168
			return false;
1169 3
		}
1170
1171 3
		$site = elgg_get_site_entity();
1172
		if ($active) {
1173
			$result = add_entity_relationship($this->guid, 'active_plugin', $site->guid);
1174
		} else {
1175
			$result = remove_entity_relationship($this->guid, 'active_plugin', $site->guid);
1176
		}
1177
1178
		$this->invalidateCache();
1179
1180
		return $result;
1181
	}
1182
1183
	/**
1184
	 * Returns the last error message registered.
1185
	 *
1186
	 * @return string|null
1187
	 */
1188 224
	public function getError() {
1189 224
		return $this->errorMsg;
1190 17
	}
1191
1192
	/**
1193
	 * Returns this plugin's \ElggPluginManifest object
1194 211
	 *
1195 211
	 * @return ElggPluginManifest|null
1196 1
	 */
1197
	public function getManifest() {
1198 210
		if ($this->manifest instanceof ElggPluginManifest) {
0 ignored issues
show
introduced by
The condition $this->manifest instanceof ElggPluginManifest can never be false since $this->manifest is always a sub-type of ElggPluginManifest.
Loading history...
1199
			return $this->manifest;
1200 210
		}
1201 1
1202 1
		try {
1203 1
			$package = $this->getPackage();
1204
			if (!$package) {
1205 1
				throw new PluginException('Package cannot be loaded');
1206
			}
1207
			$this->manifest = $package->getManifest();
1208
1209
			return $this->manifest;
1210
		} catch (PluginException $e) {
1211
			_elgg_services()->logger->warn("Failed to load manifest for plugin $this->guid. " . $e->getMessage());
1212 214
			$this->errorMsg = $e->getmessage();
1213 214
		}
1214 4
	}
1215
1216
	/**
1217
	 * Returns this plugin's \ElggPluginPackage object
1218 213
	 *
1219
	 * @return ElggPluginPackage|null
1220 212
	 */
1221 1
	public function getPackage() {
1222 1
		if ($this->package instanceof ElggPluginPackage) {
0 ignored issues
show
introduced by
The condition $this->package instanceof ElggPluginPackage can never be false since $this->package is always a sub-type of ElggPluginPackage.
Loading history...
1223 1
			return $this->package;
1224
		}
1225 1
1226
		try {
1227
			$this->package = new ElggPluginPackage($this->getPath(), false);
1228
1229
			return $this->package;
1230 308
		} catch (Exception $e) {
1231 308
			_elgg_services()->logger->warn("Failed to load package for $this->guid. " . $e->getMessage());
1232
			$this->errorMsg = $e->getmessage();
1233
		}
1234
	}
1235
1236
	/**
1237 308
	 * {@inheritdoc}
1238 308
	 */
1239
	public function isCacheable() {
1240 308
		return true;
1241
	}
1242
1243
	/**
1244
	 * {@inheritdoc}
1245
	 */
1246 289
	public function cache($persist = true) {
1247 289
		_elgg_services()->plugins->cache($this);
1248
1249 289
		return parent::cache($persist);
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::cache($persist) targeting ElggEntity::cache() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1250
	}
1251
1252
	/**
1253
	 * {@inheritdoc}
1254
	 */
1255
	public function invalidateCache() {
1256
		_elgg_services()->plugins->invalidateCache($this->getID());
1257
1258
		return parent::invalidateCache();
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::invalidateCache() targeting ElggEntity::invalidateCache() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1259
	}
1260
}
1261