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

engine/classes/ElggPlugin.php (1 issue)

loose comparison of strings.

Best Practice Bug Major
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()) {
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) {
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)) {
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) {
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) {
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) {
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)) {
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) {
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) {
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) {
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)) {
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) {
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
		if ($flags & ELGG_PLUGIN_REGISTER_LANGUAGES) {
774
			// should be loaded before the first function that touches the static config (elgg-plugin.php)
775
			// so translations can be used... for example in registering widgets
776 24
			$this->registerLanguages();
777
		}
778
779
		// include start file if it exists
780 101
		if ($flags & ELGG_PLUGIN_INCLUDE_START) {
781 95
			$this->activateEntities();
782
783 95
			if ($this->canReadFile('start.php')) {
784 95
				$result = Application::requireSetupFileOnce("{$this->getPath()}start.php");
785 95
				if ($result instanceof \Closure) {
786
					$result();
787
				}
788
			}
789
790 95
			$this->registerEntities();
791
		}
792
793
		// include views
794 101
		if ($flags & ELGG_PLUGIN_REGISTER_VIEWS) {
795 85
			$this->registerViews();
796
		}
797
798
		// include actions
799 101
		if ($flags & ELGG_PLUGIN_REGISTER_ACTIONS) {
800 34
			$this->registerActions();
801
		}
802
803
		// include routes
804 101
		if ($flags & ELGG_PLUGIN_REGISTER_ROUTES) {
805 13
			$this->registerRoutes();
806
		}
807
808
		// include widgets
809 101
		if ($flags & ELGG_PLUGIN_REGISTER_WIDGETS) {
810
			// should load after views because those are used during registration
811 34
			$this->registerWidgets();
812
		}
813
814 101
		return true;
815
	}
816
817
	/**
818
	 * Includes one of the plugins files
819
	 *
820
	 * @param string $filename The name of the file
821
	 *
822
	 * @throws PluginException
823
	 * @return mixed The return value of the included file (or 1 if there is none)
824
	 */
825 85
	protected function includeFile($filename) {
826 85
		$filepath = "{$this->getPath()}{$filename}";
827
828 85
		if (!$this->canReadFile($filename)) {
829
			$msg = _elgg_services()->translator->translate('ElggPlugin:Exception:CannotIncludeFile',
830
				[$filename, $this->getID(), $this->guid, $this->getPath()]);
831
			throw new PluginException($msg);
832
		}
833
834
		try {
835 85
			$ret = Includer::includeFile($filepath);
836
		} catch (Exception $e) {
837
			$msg = _elgg_services()->translator->translate('ElggPlugin:Exception:IncludeFileThrew',
838
				[$filename, $this->getID(), $this->guid, $this->getPath()]);
839
			throw new PluginException($msg, 0, $e);
840
		}
841
842 85
		return $ret;
843
	}
844
845
	/**
846
	 * Checks whether a plugin file with the given name exists
847
	 *
848
	 * @param string $filename The name of the file
849
	 *
850
	 * @return bool
851
	 */
852 105
	protected function canReadFile($filename) {
853 105
		$path = "{$this->getPath()}{$filename}";
854
855 105
		return is_file($path) && is_readable($path);
856
	}
857
858
	/**
859
	 * If a static config file is present, is it a serializable array?
860
	 *
861
	 * @return bool
862
	 * @throws PluginException
863
	 */
864 3
	private function isStaticConfigValid() {
865 3
		if (!$this->canReadFile(ElggPluginPackage::STATIC_CONFIG_FILENAME)) {
866
			return true;
867
		}
868
869 3
		ob_start();
870 3
		$value = $this->includeFile(ElggPluginPackage::STATIC_CONFIG_FILENAME);
871 3
		if (ob_get_clean() !== '') {
872
			$this->errorMsg = _elgg_services()->translator->translate('ElggPlugin:activate:ConfigSentOutput');
873
874
			return false;
875
		}
876
877
		// make sure can serialize
878 3
		$value = @unserialize(serialize($value));
879 3
		if (!is_array($value)) {
880
			$this->errorMsg = _elgg_services()->translator->translate('ElggPlugin:activate:BadConfigFormat');
881
882
			return false;
883
		}
884
885 3
		return true;
886
	}
887
888
	/**
889
	 * Registers the plugin's views
890
	 *
891
	 * @throws PluginException
892
	 * @return void
893
	 */
894 85
	protected function registerViews() {
895 85
		$views = _elgg_services()->views;
896
897
		// Declared views first
898 85
		$file = "{$this->getPath()}views.php";
899 85
		if (is_file($file)) {
900
			$spec = Includer::includeFile($file);
901
			if (is_array($spec)) {
902
				$views->mergeViewsSpec($spec);
903
			}
904
		}
905
906 85
		$spec = $this->getStaticConfig('views');
907 85
		if ($spec) {
908 5
			$views->mergeViewsSpec($spec);
909
		}
910
911
		// Allow /views directory files to override
912 85
		if (!$views->registerPluginViews($this->getPath(), $failed_dir)) {
913
			$key = 'ElggPlugin:Exception:CannotRegisterViews';
914
			$args = [$this->getID(), $this->guid, $failed_dir];
915
			$msg = _elgg_services()->translator->translate($key, $args);
916
			throw new PluginException($msg);
917
		}
918 85
	}
919
920
	/**
921
	 * Registers the plugin's entities
922
	 *
923
	 * @return void
924
	 */
925 95
	protected function registerEntities() {
926
927 95
		$spec = (array) $this->getStaticConfig('entities', []);
928 95
		if (empty($spec)) {
929 50
			return;
930
		}
931
932 58
		foreach ($spec as $entity) {
933 58
			if (isset($entity['type'], $entity['subtype'], $entity['searchable']) && $entity['searchable']) {
934 58
				elgg_register_entity_type($entity['type'], $entity['subtype']);
935
			}
936
		}
937 58
	}
938
939
	/**
940
	 * Registers the plugin's actions provided in the plugin config file
941
	 *
942
	 * @return void
943
	 */
944 34
	protected function registerActions() {
945 34
		self::addActionsFromStaticConfig($this->getStaticConfig('actions', []), $this->getPath());
946 34
	}
947
948
	/**
949
	 * Register a plugin's actions provided in the config file
950
	 *
951
	 * @todo   move to a static config service
952
	 *
953
	 * @param array  $spec      'actions' section of static config
954
	 * @param string $root_path Plugin path
955
	 *
956
	 * @return void
957
	 * @access private
958
	 * @internal
959
	 */
960 34
	public static function addActionsFromStaticConfig(array $spec, $root_path) {
961 34
		$actions = _elgg_services()->actions;
962 34
		$root_path = rtrim($root_path, '/\\');
963
964 34
		foreach ($spec as $action => $action_spec) {
965 33
			if (!is_array($action_spec)) {
966
				continue;
967
			}
968
969
			$options = [
970 33
				'access' => 'logged_in',
971
				'filename' => '', // assuming core action is registered
972
			];
973
974 33
			$options = array_merge($options, $action_spec);
975
976 33
			$filename = "$root_path/actions/{$action}.php";
977 33
			if (is_file($filename)) {
978 33
				$options['filename'] = $filename;
979
			}
980
981 33
			$actions->register($action, $options['filename'], $options['access']);
982
		}
983 34
	}
984
985
	/**
986
	 * Registers the plugin's routes provided in the plugin config file
987
	 *
988
	 * @throws PluginException
989
	 * @return void
990
	 */
991 13
	protected function registerRoutes() {
992 13
		$router = _elgg_services()->router;
993
994 13
		$spec = (array) $this->getStaticConfig('routes', []);
995
996 13
		foreach ($spec as $name => $route_spec) {
997 13
			if (!is_array($route_spec)) {
998
				continue;
999
			}
1000
1001 13
			$router->registerRoute($name, $route_spec);
1002
		}
1003 13
	}
1004
1005
	/**
1006
	 * Registers the plugin's widgets provided in the plugin config file
1007
	 *
1008
	 * @return void
1009
	 * @throws \InvalidParameterException
1010
	 */
1011 34
	protected function registerWidgets() {
1012 34
		$widgets = _elgg_services()->widgets;
1013
1014 34
		$spec = (array) $this->getStaticConfig('widgets', []);
1015 34
		foreach ($spec as $widget_id => $widget_definition) {
1016 24
			if (!is_array($widget_definition)) {
1017
				continue;
1018
			}
1019 24
			if (!isset($widget_definition['id'])) {
1020 24
				$widget_definition['id'] = $widget_id;
1021
			}
1022
1023 24
			$definition = \Elgg\WidgetDefinition::factory($widget_definition);
1024
1025 24
			$widgets->registerType($definition);
1026
		}
1027 34
	}
1028
1029
	/**
1030
	 * Registers the plugin's languages
1031
	 *
1032
	 * @return true
1033
	 */
1034 24
	protected function registerLanguages() {
1035 24
		return _elgg_services()->translator->registerPluginTranslations($this->getPath());
1036
	}
1037
1038
	/**
1039
	 * Registers the plugin's classes
1040
	 *
1041
	 * @return true
1042
	 */
1043 101
	protected function registerClasses() {
1044 101
		$classes_path = "{$this->getPath()}classes";
1045
1046 101
		if (is_dir($classes_path)) {
1047 77
			_elgg_services()->autoloadManager->addClasses($classes_path);
1048
		}
1049
1050 101
		return true;
1051
	}
1052
1053
	/**
1054
	 * Activates the plugin's entities
1055
	 *
1056
	 * @return void
1057
	 */
1058 97
	protected function activateEntities() {
1059 97
		$spec = (array) $this->getStaticConfig('entities', []);
1060 97
		if (empty($spec)) {
1061 50
			return;
1062
		}
1063
1064 60
		foreach ($spec as $entity) {
1065 60
			if (isset($entity['type'], $entity['subtype'], $entity['class'])) {
1066 60
				elgg_set_entity_class($entity['type'], $entity['subtype'], $entity['class']);
1067
			}
1068
		}
1069 60
	}
1070
1071
	/**
1072
	 * Deactivates the plugin's entities
1073
	 *
1074
	 * @return void
1075
	 */
1076 1
	protected function deactivateEntities() {
1077 1
		$spec = (array) $this->getStaticConfig('entities', []);
1078 1
		if (empty($spec)) {
1079 1
			return;
1080
		}
1081
1082
		foreach ($spec as $entity) {
1083
			if (isset($entity['type'], $entity['subtype'], $entity['class'])) {
1084
				elgg_set_entity_class($entity['type'], $entity['subtype']);
1085
			}
1086
		}
1087
	}
1088
1089
	/**
1090
	 * Get an attribute, metadata or private setting value
1091
	 *
1092
	 * @param string $name Name of the attribute or private setting
1093
	 *
1094
	 * @return mixed
1095
	 */
1096 352
	public function __get($name) {
1097
		// See if its in our base attribute
1098 352
		if (array_key_exists($name, $this->attributes)) {
1099 352
			return $this->attributes[$name];
1100
		}
1101
1102
		// object title and description are stored as metadata
1103 314
		if (in_array($name, ['title', 'description'])) {
1104 314
			return parent::__get($name);
1105
		}
1106
1107 3
		$result = $this->getPrivateSetting($name);
1108 3
		if ($result !== null) {
1109 1
			return $result;
1110
		}
1111
1112 2
		$defaults = $this->getStaticConfig('settings', []);
1113
1114 2
		return elgg_extract($name, $defaults, $result);
1115
	}
1116
1117
	/**
1118
	 * Set a value as attribute, metadata or private setting.
1119
	 *
1120
	 * Metadata applies to title and description.
1121
	 *
1122
	 * @param string $name  Name of the attribute or private_setting
1123
	 * @param mixed  $value Value to be set
1124
	 *
1125
	 * @return void
1126
	 */
1127 288
	public function __set($name, $value) {
1128 288
		if (array_key_exists($name, $this->attributes)) {
1129
			// Check that we're not trying to change the guid!
1130 1
			if ((array_key_exists('guid', $this->attributes)) && ($name == 'guid')) {
1131
				return;
1132
			}
1133
1134 1
			$this->attributes[$name] = $value;
1135
1136 1
			return;
1137
		}
1138
1139
		// object title and description are stored as metadata
1140 288
		if (in_array($name, ['title', 'description'])) {
1141 288
			parent::__set($name, $value);
1142
1143 288
			return;
1144
		}
1145
1146
		// to make sure we trigger the correct hooks
1147 2
		$this->setSetting($name, $value);
1148 2
	}
1149
1150
	/**
1151
	 * Sets the plugin to active or inactive.
1152
	 *
1153
	 * @param bool $active Set to active or inactive
1154
	 *
1155
	 * @return bool
1156
	 */
1157 3
	private function setStatus($active) {
1158 3
		if (!$this->guid) {
1159
			return false;
1160
		}
1161
1162 3
		$site = elgg_get_site_entity();
1163 3
		if ($active) {
1164 3
			$result = add_entity_relationship($this->guid, 'active_plugin', $site->guid);
1165
		} else {
1166 1
			$result = remove_entity_relationship($this->guid, 'active_plugin', $site->guid);
1167
		}
1168
1169 3
		$this->invalidateCache();
1170
1171 3
		return $result;
1172
	}
1173
1174
	/**
1175
	 * Returns the last error message registered.
1176
	 *
1177
	 * @return string|null
1178
	 */
1179
	public function getError() {
1180
		return $this->errorMsg;
1181
	}
1182
1183
	/**