Completed
Push — 3.1 ( d59679...4b8741 )
by Jeroen
62:38 queued 13s
created

engine/classes/ElggPlugin.php (1 issue)

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
	 * @var bool
43
	 */
44
	protected $activated;
45
46
	/**
47
	 * {@inheritdoc}
48
	 */
49 386
	protected function initializeAttributes() {
50 386
		parent::initializeAttributes();
51
52 386
		$this->attributes['subtype'] = "plugin";
53 386
	}
54
55
	/**
56
	 * Load a plugin object from its ID
57
	 * Create a new plugin entity if doesn't exist
58
	 *
59
	 * @param string $plugin_id Plugin ID
60
	 * @param string $path      Path, defaults to /mod
61
	 *
62
	 * @return ElggPlugin
63
	 * @throws PluginException
64
	 */
65 362
	public static function fromId($plugin_id, $path = null) {
66 362
		if (empty($plugin_id)) {
67 1
			throw new InvalidArgumentException('Plugin ID must be set');
68
		}
69
70 361
		$plugin = elgg_get_plugin_from_id($plugin_id);
71
72 361
		if (!$plugin) {
73 8
			$ia = _elgg_services()->session->setIgnoreAccess(true);
74 8
			$plugin = new ElggPlugin();
75 8
			$plugin->title = $plugin_id;
76 8
			$plugin->save();
77
78 8
			_elgg_services()->session->setIgnoreAccess($ia);
79
		}
80
81 361
		if (!$path) {
82 343
			$path = elgg_get_plugins_path();
83
		}
84
85 361
		$path = rtrim($path, '/');
86 361
		$plugin->setPath($path . '/' . $plugin_id);
87
88 361
		return $plugin;
89
	}
90
91
	/**
92
	 * {@inheritdoc}
93
	 */
94 10
	public function save() {
95
96 10
		$site = elgg_get_site_entity();
97
98 10
		$this->attributes['owner_guid'] = $site->guid;
99 10
		$this->attributes['container_guid'] = $site->guid;
100 10
		$this->attributes['access_id'] = ACCESS_PUBLIC;
101
102 10
		$new = !$this->guid;
103 10
		$priority = null;
104 10
		if ($new) {
105 10
			$name = _elgg_services()->plugins->namespacePrivateSetting('internal', 'priority');
106 10
			$priority = elgg_extract($name, $this->temp_private_settings, 'new');
107 2
		} elseif ($this->getPriority() === null) {
108
			$priority = 'last';
109
		}
110
111 10
		if ($priority) {
112 10
			$this->setPriority($priority);
113
		}
114
		
115 10
		return parent::save();
116
	}
117
118
	/**
119
	 * Returns the ID (dir name) of this plugin
120
	 *
121
	 * @return string
122
	 */
123 455
	public function getID() {
124 455
		return $this->title;
125
	}
126
127
	/**
128
	 * Returns the manifest's name if available, otherwise the ID.
129
	 *
130
	 * @return string
131
	 * @since 3.0
132
	 */
133 2
	public function getDisplayName() {
134 2
		$manifest = $this->getManifest();
135 2
		if ($manifest instanceof ElggPluginManifest) {
136 1
			return $manifest->getName();
137
		}
138
139 1
		return $this->getID();
140
	}
141
142
	/**
143
	 * Set path
144
	 *
145
	 * @param string $path Path to plugin directory
146
	 *
147
	 * @return void
148
	 * @internal
149
	 */
150 439
	public function setPath($path) {
151 439
		$this->path = \Elgg\Project\Paths::sanitize($path, true);
152 439
	}
153
154
	/**
155
	 * Returns the plugin's full path with trailing slash.
156
	 *
157
	 * @return string
158
	 */
159 437
	public function getPath() {
160 437
		if (isset($this->path)) {
161 435
			return $this->path;
162
		}
163
		
164 99
		$this->setPath(elgg_get_plugins_path() . $this->getID());
165 99
		return $this->path;
166
	}
167
168
	/**
169
	 * Returns the plugin's languages directory full path with trailing slash.
170
	 * Returns false if directory does not exist
171
	 *
172
	 * @return string|false
173
	 */
174 249
	public function getLanguagesPath() {
175 249
		$languages_path = $this->getPath() . 'languages/';
176 249
		if (!is_dir($languages_path)) {
177 100
			return false;
178
		}
179
		
180 239
		return $languages_path;
181
	}
182
183
	/**
184
	 * Get a value from the plugins's static config file.
185
	 *
186
	 * @note     If the system cache is on, Elgg APIs should not call this on every request.
187
	 *
188
	 * @param string $key     Config key
189
	 * @param mixed  $default Value returned if missing
190
	 *
191
	 * @return mixed
192
	 * @internal
193
	 */
194 243
	public function getStaticConfig($key, $default = null) {
195 243
		if ($this->static_config === null) {
196 241
			$this->static_config = [];
197
198
			try {
199 241
				if ($this->canReadFile(ElggPluginPackage::STATIC_CONFIG_FILENAME)) {
200 241
					$this->static_config = $this->includeFile(ElggPluginPackage::STATIC_CONFIG_FILENAME);
201
				}
202
			} catch (PluginException $ex) {
203
				elgg_log($ex, \Psr\Log\LogLevel::ERROR);
204
			}
205
		}
206
207 243
		if (isset($this->static_config[$key])) {
208 233
			return $this->static_config[$key];
209
		} else {
210 242
			return $default;
211
		}
212
	}
213
214
	/**
215
	 * Returns an array of available markdown files for this plugin
216
	 *
217
	 * @return array
218
	 */
219 1
	public function getAvailableTextFiles() {
220 1
		$filenames = $this->getPackage()->getTextFilenames();
221
222 1
		$files = [];
223 1
		foreach ($filenames as $filename) {
224 1
			if ($this->canReadFile($filename)) {
225 1
				$files[$filename] = "{$this->getPath()}{$filename}";
226
			}
227
		}
228
229 1
		return $files;
230
	}
231
232
	// Load Priority
233
234
	/**
235
	 * Gets the plugin's load priority.
236
	 *
237
	 * @return int|null
238
	 */
239 16
	public function getPriority() {
240 16
		$name = _elgg_services()->plugins->namespacePrivateSetting('internal', 'priority');
241
242 16
		$priority = $this->getPrivateSetting($name);
243 16
		if (isset($priority)) {
244 9
			return (int) $priority;
245
		}
246
247 11
		return null;
248
	}
249
250
	/**
251
	 * Sets the priority of the plugin
252
	 * Returns the new priority or false on error
253
	 *
254
	 * @param mixed $priority The priority to set
255
	 *                        One of +1, -1, first, last, or a number.
256
	 *                        If given a number, this will displace all plugins at that number
257
	 *                        and set their priorities +1
258
	 *
259
	 * @return int|false
260
	 * @throws DatabaseException
261
	 */
262 10
	public function setPriority($priority) {
263 10
		$priority = $this->normalizePriority($priority);
264
265 10
		return _elgg_services()->plugins->setPriority($this, $priority);
266
	}
267
268
	/**
269
	 * Normalize and validate new priority
270
	 *
271
	 * @param mixed $priority Priority to normalize
272
	 *
273
	 * @return int
274
	 * @internal
275
	 */
276 10
	public function normalizePriority($priority) {
277
		// if no priority assume a priority of 1
278 10
		$old_priority = $this->getPriority();
279 10
		$old_priority = $old_priority ? : 1;
280 10
		$max_priority = _elgg_get_max_plugin_priority() ? : 1;
281
282
		// can't use switch here because it's not strict and php evaluates +1 == 1
283 10
		if ($priority === '+1') {
284 1
			$priority = $old_priority + 1;
285 10
		} else if ($priority === '-1') {
286 1
			$priority = $old_priority - 1;
287 10
		} else if ($priority === 'first') {
288 1
			$priority = 1;
289 10
		} else if ($priority === 'last') {
290 1
			$priority = $max_priority;
291 10
		} else if ($priority === 'new') {
292 10
			$max_priority++;
293 10
			$priority = $max_priority;
294
		}
295
296 10
		return min($max_priority, max(1, (int) $priority));
297
	}
298
299
	// Plugin settings
300
301
	/**
302
	 * Returns a plugin setting
303
	 *
304
	 * @param string $name    The setting name
305
	 * @param mixed  $default The default value to return if none is set
306
	 *
307
	 * @return mixed
308
	 */
309 4
	public function getSetting($name, $default = null) {
310 4
		$values = $this->getAllSettings();
311 4
		return elgg_extract($name, $values, $default);
312
	}
313
314
	/**
315
	 * Returns an array of all settings saved for this plugin.
316
	 *
317
	 * @note Unlike user settings, plugin settings are not namespaced.
318
	 *
319
	 * @return array An array of key/value pairs.
320
	 */
321 84
	public function getAllSettings() {
322
323
		try {
324 84
			$defaults = [];
325 84
			if ($this->isActive()) {
326
				// only load settings from static config for active plugins to prevent issues
327
				// with internal plugin references ie. classes and language keys
328 82
				$defaults = $this->getStaticConfig('settings', []);
329
			}
330
331 84
			if (!$this->guid) {
332 1
				$settings = $this->temp_private_settings;
333
			} else {
334 83
				$settings = _elgg_services()->plugins->getAllSettings($this);
335
			}
336
337 84
			return array_merge($defaults, $settings);
338
		} catch (DatabaseException $ex) {
339
			return [];
340
		}
341
	}
342
343
	/**
344
	 * Set a plugin setting for the plugin
345
	 *
346
	 * @param string $name  The name to set
347
	 * @param string $value The value to set
348
	 *
349
	 * @return bool
350
	 */
351 8
	public function setSetting($name, $value) {
352
353 8
		$value = _elgg_services()->hooks->trigger('setting', 'plugin', [
354 8
			'plugin_id' => $this->getID(),
355 8
			'plugin' => $this,
356 8
			'name' => $name,
357 8
			'value' => $value,
358 8
		], $value);
359
360 8
		if (is_array($value)) {
361 1
			elgg_log('Plugin settings cannot store arrays.', 'ERROR');
362
363 1
			return false;
364
		}
365
366 8
		return $this->setPrivateSetting($name, $value);
367
	}
368
369
	/**
370
	 * Removes a plugin setting name and value
371
	 *
372
	 * @param string $name The setting name to remove
373
	 *
374
	 * @return bool
375
	 */
376 2
	public function unsetSetting($name) {
377 2
		return $this->removePrivateSetting($name);
378
	}
379
380
	/**
381
	 * Removes all settings for this plugin
382
	 * @return bool
383
	 */
384 1
	public function unsetAllSettings() {
385 1
		$settings = $this->getAllSettings();
386
387 1
		foreach ($settings as $name => $value) {
388 1
			if (strpos($name, 'elgg:internal:') === 0) {
389 1
				continue;
390
			}
391 1
			$this->unsetSetting($name);
392
		}
393
394 1
		return true;
395
	}
396
397
398
	// User settings
399
400
	/**
401
	 * Returns a user's setting for this plugin
402
	 *
403
	 * @param string $name      The setting name
404
	 * @param int    $user_guid The user GUID
405
	 * @param mixed  $default   The default value to return if none is set
406
	 *
407
	 * @return mixed The setting string value, the default value or false if there is no user
408
	 * @throws DatabaseException
409
	 */
410 1
	public function getUserSetting($name, $user_guid = 0, $default = null) {
411 1
		$values = $this->getAllUserSettings($user_guid);
412 1
		if ($values === false) {
413
			return false;
414
		}
415
416 1
		return elgg_extract($name, $values, $default);
417
	}
418
419
	/**
420
	 * Returns an array of all user settings saved for this plugin for the user.
421
	 *
422
	 * @note Plugin settings are saved with a prefix. This removes that prefix.
423
	 *
424
	 * @param int $user_guid The user GUID. Defaults to logged in.
425
	 *
426
	 * @return array An array of key/value pairs
427
	 * @throws DatabaseException
428
	 */
429 1
	public function getAllUserSettings($user_guid = 0) {
430
431 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
432 1
		if (!$user instanceof ElggUser) {
433
			return [];
434
		}
435
436 1
		$defaults = $this->getStaticConfig('user_settings', []);
437
438 1
		$settings = _elgg_services()->plugins->getAllUserSettings($this, $user);
439
440 1
		return array_merge($defaults, $settings);
441
	}
442
443
	/**
444
	 * Sets a user setting for a plugin
445
	 *
446
	 * @param string $name      The setting name
447
	 * @param string $value     The setting value
448
	 * @param int    $user_guid The user GUID
449
	 *
450
	 * @return mixed The new setting ID or false
451
	 */
452 1
	public function setUserSetting($name, $value, $user_guid = 0) {
453 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
454 1
		if (!$user instanceof ElggUser) {
455
			return false;
456
		}
457
458 1
		$value = _elgg_services()->hooks->trigger('usersetting', 'plugin', [
459 1
			'user' => $user,
460 1
			'plugin' => $this,
461 1
			'plugin_id' => $this->getID(),
462 1
			'name' => $name,
463 1
			'value' => $value
464 1
		], $value);
465
466 1
		if (is_array($value)) {
467 1
			elgg_log('Plugin user settings cannot store arrays.', 'ERROR');
468
469 1
			return false;
470
		}
471
472 1
		$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
473
474 1
		return $user->setPrivateSetting($name, $value);
475
	}
476
477
	/**
478
	 * Removes a user setting name and value.
479
	 *
480
	 * @param string $name      The user setting name
481
	 * @param int    $user_guid The user GUID
482
	 *
483
	 * @return bool
484
	 */
485 1
	public function unsetUserSetting($name, $user_guid = 0) {
486 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
487
488 1
		if (!$user instanceof ElggUser) {
489
			return false;
490
		}
491
492 1
		$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
493
494 1
		return $user->removePrivateSetting($name);
495
	}
496
497
	/**
498
	 * Removes all plugin settings for a given user
499
	 *
500
	 * @param int $user_guid The user GUID to remove user settings.
501
	 *
502
	 * @return bool
503
	 * @throws DatabaseException
504
	 */
505 1
	public function unsetAllUserSettings($user_guid = 0) {
506 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
507
508 1
		if (!$user instanceof ElggUser) {
509
			return false;
510
		}
511
512 1
		$settings = $this->getAllUserSettings($user_guid);
513
514 1
		foreach ($settings as $name => $value) {
515 1
			$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
516 1
			$user->removePrivateSetting($name);
517
		}
518
519 1
		return true;
520
	}
521
522
	/**
523
	 * Returns if the plugin is complete, meaning has all required files
524
	 * and Elgg can read them and they make sense.
525
	 *
526
	 * @return bool
527
	 */
528 1
	public function isValid() {
529 1
		if (!$this->getID()) {
530
			$this->errorMsg = elgg_echo('ElggPlugin:MissingID', [$this->guid]);
531
532
			return false;
533
		}
534
535 1
		if (!$this->getPackage() instanceof ElggPluginPackage) {
536
			$this->errorMsg = elgg_echo('ElggPlugin:NoPluginPackagePackage', [
537
				$this->getID(),
538
				$this->guid
539
			]);
540
541
			return false;
542
		}
543
544 1
		if (!$this->getPackage()->isValid()) {
545
			$this->errorMsg = $this->getPackage()->getError();
546
547
			return false;
548
		}
549
550 1
		return true;
551
	}
552
553
	/**
554
	 * Is this plugin active?
555
	 *
556
	 * @return bool
557
	 */
558 164
	public function isActive() {
559 164
		if (isset($this->activated)) {
560 89
			return $this->activated;
561
		}
562
563 102
		$this->activated = elgg_is_active_plugin($this->getID());
564 102
		return $this->activated;
565
	}
566
567
	/**
568
	 * Checks if this plugin can be activated on the current
569
	 * Elgg installation.
570
	 *
571
	 * @return bool
572
	 */
573 16
	public function canActivate() {
574 16
		if ($this->isActive()) {
575
			return false;
576
		}
577
578 16
		if ($this->getPackage()) {
579 16
			$result = $this->getPackage()->isValid() && $this->getPackage()->checkDependencies();
580 16
			if (!$result) {
581
				$this->errorMsg = $this->getPackage()->getError();
582
			}
583
584 16
			return $result;
585
		}
586
587
		return false;
588
	}
589
590
591
	// activating and deactivating
592
593
	/**
594
	 * Actives the plugin for the current site.
595
	 *
596
	 * @return bool
597
	 * @throws InvalidParameterException
598
	 * @throws PluginException
599
	 */
600 16
	public function activate() {
601 16
		if ($this->isActive()) {
602
			return false;
603
		}
604
605 16
		if (!$this->canActivate()) {
606
			return false;
607
		}
608
609
		// Check this before setting status because the file could potentially throw
610 16
		if (!$this->isStaticConfigValid()) {
611
			return false;
612
		}
613
614 16
		if (!$this->setStatus(true)) {
615
			return false;
616
		}
617
618
		// perform tasks and emit events
619
		// emit an event. returning false will make this not be activated.
620
		// we need to do this after it's been fully activated
621
		// or the deactivate will be confused.
622
		$params = [
623 16
			'plugin_id' => $this->getID(),
624 16
			'plugin_entity' => $this,
625
		];
626
627 16
		$return = _elgg_services()->events->trigger('activate', 'plugin', $params);
628
629
		// if there are any on_enable functions, start the plugin now and run them
630
		// Note: this will not run re-run the init hooks!
631 16
		if ($return) {
632
			try {
633 16
				_elgg_services()->events->trigger('cache:flush', 'system');
634
635 16
				$this->register();
636
				
637
				// directly load languages to have them available during runtime
638 16
				$this->loadLanguages();
639
				
640 16
				$setup = $this->boot();
641 16
				if ($setup instanceof Closure) {
642 8
					$setup();
643
				}
644
645 16
				$this->getBootstrap()->activate();
646
647 16
				if ($this->canReadFile('activate.php')) {
648
					elgg_deprecated_notice("The usage of the activate.php for {$this->getDisplayName()} is deprecated. Use the Bootstrap->activate() function.", '3.1');
649
					$return = $this->includeFile('activate.php');
650
				}
651
652 16
				$this->init();
653
			} catch (PluginException $ex) {
654
				elgg_log($ex, \Psr\Log\LogLevel::ERROR);
655
656
				$return = false;
657
			}
658
		}
659
660 16
		if ($return === false) {
661
			$this->deactivate();
662
		} else {
663 16
			elgg_delete_admin_notice("cannot_start {$this->getID()}");
664
665 16
			_elgg_services()->events->trigger('cache:flush', 'system');
666 16
			_elgg_services()->logger->notice("Plugin {$this->getID()} has been activated");
667
		}
668
669 16
		return $return;
670
	}
671
672
	/**
673
	 * Checks if this plugin can be deactivated on the current
674
	 * Elgg installation. Validates that this plugin has no
675
	 * active dependants.
676
	 *
677
	 * @return bool
678
	 */
679 8
	public function canDeactivate() {
680 8
		if (!$this->isActive()) {
681
			return false;
682
		}
683
684 8
		$dependents = [];
685
686 8
		$active_plugins = elgg_get_plugins();
687
688 8
		foreach ($active_plugins as $plugin) {
689 7
			$manifest = $plugin->getManifest();
690 7
			if (!$manifest) {
691 2
				continue;
692
			}
693 7
			$requires = $manifest->getRequires();
694
695 7
			foreach ($requires as $required) {
696 7
				if ($required['type'] == 'plugin' && $required['name'] == $this->getID()) {
697
					// there are active dependents
698 7
					$dependents[$manifest->getPluginID()] = $plugin;
699
				}
700
			}
701
		}
702
703 8
		if (!empty($dependents)) {
704
			$list = array_map(function (\ElggPlugin $plugin) {
705
				$css_id = preg_replace('/[^a-z0-9-]/i', '-', $plugin->getManifest()->getID());
706
707
				return elgg_view('output/url', [
708
					'text' => $plugin->getDisplayName(),
709
					'href' => "#$css_id",
710
				]);
711
			}, $dependents);
712
			$name = $this->getDisplayName();
713
			$list = implode(', ', $list);
714
			$this->errorMsg = elgg_echo('ElggPlugin:Dependencies:ActiveDependent', [$name, $list]);
715
716
			return false;
717
		}
718
719 8
		return true;
720
	}
721
722
	/**
723
	 * Deactivates the plugin.
724
	 *
725
	 * @return bool
726
	 * @throws PluginException
727
	 */
728 9
	public function deactivate() {
729 9
		if (!$this->isActive()) {
730 3
			return false;
731
		}
732
733 8
		if (!$this->canDeactivate()) {
734
			return false;
735
		}
736
737
		// emit an event. returning false will cause this to not be deactivated.
738
		$params = [
739 8
			'plugin_id' => $this->getID(),
740 8
			'plugin_entity' => $this,
741
		];
742
743 8
		$return = _elgg_services()->events->trigger('deactivate', 'plugin', $params);
744 8
		if ($return === false) {
745
			return false;
746
		}
747
748 8
		$this->getBootstrap()->deactivate();
749
750
		// run any deactivate code
751 8
		if ($this->canReadFile('deactivate.php')) {
752
			elgg_deprecated_notice("The usage of the deactivate.php for {$this->getDisplayName()} is deprecated. Use the Bootstrap->deactivate() function.", '3.1');
753
			
754
			// allows you to prevent disabling a plugin by returning false in a deactivate.php file
755
			if ($this->includeFile('deactivate.php') === false) {
756
				return false;
757
			}
758
		}
759
760 8
		$this->deactivateEntities();
761
762 8
		_elgg_services()->events->trigger('cache:flush', 'system');
763
764 8
		_elgg_services()->logger->notice("Plugin {$this->getID()} has been deactivated");
765
766 8
		return $this->setStatus(false);
767
	}
768
769
	/**
770
	 * Bootstrap object
771
	 * @return \Elgg\PluginBootstrapInterface
772
	 * @throws PluginException
773
	 * @internal
774
	 */
775 240
	public function getBootstrap() {
776 240
		$bootstrap = $this->getStaticConfig('bootstrap');
777 240
		if ($bootstrap) {
778 83
			if (!is_subclass_of($bootstrap, \Elgg\PluginBootstrapInterface::class)) {
779
				throw PluginException::factory(
780
					'InvalidBootstrap',
781
					$this,
782
					elgg_echo('LogicException:InterfaceNotImplemented', [
783
						$bootstrap,
784
						\Elgg\PluginBootstrapInterface::class
785
					])
786
				);
787
			}
788
789 83
			return new $bootstrap($this, _elgg_services()->dic);
790
		}
791
792 236
		return new \Elgg\DefaultPluginBootstrap($this, _elgg_services()->dic);
793
	}
794
795
	/**
796
	 * Register plugin classes and require composer autoloader
797
	 *
798
	 * @return void
799
	 * @throws PluginException
800
	 * @internal
801
	 */
802 247
	public function autoload() {
803 247
		$this->registerClasses();
804
805 247
		$autoload_file = 'vendor/autoload.php';
806 247
		if (!$this->canReadFile($autoload_file)) {
807 247
			return;
808
		}
809
		
810
		$autoloader = Application::requireSetupFileOnce("{$this->getPath()}{$autoload_file}");
811
		
812
		if (!$autoloader instanceof \Composer\Autoload\ClassLoader) {
813
			return;
814
		}
815
		
816
		$autoloader->unregister();
817
		
818
		// plugins should be appended, composer defaults to prepend
819
		$autoloader->register(false);
820
	}
821
822
	/**
823
	 * Autoload plugin classes and vendor libraries
824
	 * Register plugin-specific entity classes and execute bootstrapped load scripts
825
	 * Register languages and views
826
	 *
827
	 * @return void
828
	 * @throws PluginException
829
	 * @internal
830
	 */
831 237
	public function register() {
832 237
		$this->autoload();
833
834 237
		$this->activateEntities();
835 237
		$this->registerLanguages();
836 237
		$this->registerViews();
837
838 237
		$this->getBootstrap()->load();
839 237
	}
840
841
	/**
842
	 * Boot the plugin
843
	 *
844
	 * @throws PluginException
845
	 * @return \Closure|null
846
	 * @internal
847
	 */
848 237
	public function boot() {
849 237
		$result = null;
850 237
		if ($this->canReadFile('start.php')) {
851 228
			$result = Application::requireSetupFileOnce("{$this->getPath()}start.php");
852
		}
853
854 237
		$this->getBootstrap()->boot();
855
856 237
		return $result;
857
	}
858
859
	/**
860
	 * Init the plugin
861
	 * @return void
862
	 * @throws InvalidParameterException
863
	 * @throws PluginException
864
	 * @internal
865
	 */
866 237
	public function init() {
867 237
		$this->registerRoutes();
868 237
		$this->registerActions();
869 237
		$this->registerEntities();
870 237
		$this->registerWidgets();
871 237
		$this->registerHooks();
872 237
		$this->registerEvents();
873
874 237
		$this->getBootstrap()->init();
875 237
	}
876
877
	/**
878
	 * Includes one of the plugins files
879
	 *
880
	 * @param string $filename The name of the file
881
	 *
882
	 * @throws PluginException
883
	 * @return mixed The return value of the included file (or 1 if there is none)
884
	 */
885 236
	protected function includeFile($filename) {
886 236
		$filepath = "{$this->getPath()}{$filename}";
887
888 236
		if (!$this->canReadFile($filename)) {
889
			$msg = elgg_echo(
890
				'ElggPlugin:Exception:CannotIncludeFile',
891
				[$filename, $this->getID(), $this->guid, $this->getPath()]
892
			);
893
894
			throw PluginException::factory('CannotIncludeFile', $this, $msg);
895
		}
896
897
		try {
898 236
			$ret = Application::requireSetupFileOnce($filepath);
899
		} catch (Exception $e) {
900
			$msg = elgg_echo(
901
				'ElggPlugin:Exception:IncludeFileThrew',
902
				[$filename, $this->getID(), $this->guid, $this->getPath()]
903
			);
904
905
			throw PluginException::factory('IncludeFileThrew', $this, $msg, $e);
906
		}
907
908 236
		return $ret;
909
	}
910
911
	/**
912
	 * Checks whether a plugin file with the given name exists
913
	 *
914
	 * @param string $filename The name of the file
915
	 *
916
	 * @return bool
917
	 */
918 251
	protected function canReadFile($filename) {
919 251
		$path = "{$this->getPath()}{$filename}";
920
921 251
		return is_file($path) && is_readable($path);
922
	}
923
924
	/**
925
	 * If a static config file is present, is it a serializable array?
926
	 *
927
	 * @return bool
928
	 * @throws PluginException
929
	 */
930 16
	private function isStaticConfigValid() {
0 ignored issues
show
Private method name "ElggPlugin::isStaticConfigValid" must be prefixed with an underscore
Loading history...
931 16
		if (!$this->canReadFile(ElggPluginPackage::STATIC_CONFIG_FILENAME)) {
932 5
			return true;
933
		}
934
935 11
		ob_start();
936 11
		$value = $this->includeFile(ElggPluginPackage::STATIC_CONFIG_FILENAME);
937 11
		if (ob_get_clean() !== '') {
938
			$this->errorMsg = elgg_echo('ElggPlugin:activate:ConfigSentOutput');
939
940
			return false;
941
		}
942
943
		// make sure can serialize
944 11
		$value = @unserialize(serialize($value));
945 11
		if (!is_array($value)) {
946
			$this->errorMsg = elgg_echo('ElggPlugin:activate:BadConfigFormat');
947
948
			return false;
949
		}
950
951 11
		return true;
952
	}
953
954
	/**
955
	 * Registers the plugin's views
956
	 *
957
	 * @throws PluginException
958
	 * @return void
959
	 */
960 237
	protected function registerViews() {
961 237
		if (_elgg_config()->system_cache_loaded) {
962 74
			return;
963
		}
964
965 168
		$views = _elgg_services()->views;
966
967
		// Declared views first
968 168
		$file = "{$this->getPath()}views.php";
969 168
		if (is_file($file)) {
970
			elgg_deprecated_notice("The usage of the views.php file for {$this->getDisplayName()} is deprecated. Use the elgg-plugin.php views section.", '3.1');
971
			
972
			$spec = Includer::includeFile($file);
973
			if (is_array($spec)) {
974
				$views->mergeViewsSpec($spec);
975
			}
976
		}
977
978 168
		$spec = $this->getStaticConfig('views');
979 168
		if ($spec) {
980 11
			$views->mergeViewsSpec($spec);
981
		}
982
983
		// Allow /views directory files to override
984 168
		if (!$views->registerPluginViews($this->getPath(), $failed_dir)) {
985
			$key = 'ElggPlugin:Exception:CannotRegisterViews';
986
			$args = [$this->getID(), $this->guid, $failed_dir];
987
			$msg = elgg_echo($key, $args);
988
989
			throw PluginException::factory('CannotRegisterViews', $this, $msg);
990
		}
991 168
	}
992
993
	/**
994
	 * Registers the plugin's entities
995
	 *
996
	 * @return void
997
	 */
998 237
	protected function registerEntities() {
999
1000 237
		$spec = (array) $this->getStaticConfig('entities', []);
1001 237
		if (empty($spec)) {
1002 194
			return;
1003
		}
1004
1005 184
		foreach ($spec as $entity) {
1006 184
			if (isset($entity['type'], $entity['subtype'], $entity['searchable']) && $entity['searchable']) {
1007 184
				elgg_register_entity_type($entity['type'], $entity['subtype']);
1008
			}
1009
		}
1010 184
	}
1011
1012
	/**
1013
	 * Registers the plugin's actions provided in the plugin config file
1014
	 *
1015
	 * @return void
1016
	 */
1017 237
	protected function registerActions() {
1018 237
		self::addActionsFromStaticConfig($this->getStaticConfig('actions', []), $this->getPath());
1019 237
	}
1020
1021
	/**
1022
	 * Register a plugin's actions provided in the config file
1023
	 *
1024
	 * @todo   move to a static config service
1025
	 *
1026
	 * @param array  $spec      'actions' section of static config
1027
	 * @param string $root_path Plugin path
1028
	 *
1029
	 * @return void
1030
	 * @internal
1031
	 */
1032 237
	public static function addActionsFromStaticConfig(array $spec, $root_path) {
1033 237
		$actions = _elgg_services()->actions;
1034 237
		$root_path = rtrim($root_path, '/\\');
1035
1036 237
		foreach ($spec as $action => $action_spec) {
1037 211
			if (!is_array($action_spec)) {
1038
				continue;
1039
			}
1040
1041 211
			$access = elgg_extract('access', $action_spec, 'logged_in');
1042 211
			$handler = elgg_extract('controller', $action_spec);
1043 211
			if (!$handler) {
1044 211
				$handler = elgg_extract('filename', $action_spec);
1045 211
				if (!$handler) {
1046 211
					$handler = "$root_path/actions/{$action}.php";
1047
				}
1048
			}
1049
1050 211
			$actions->register($action, $handler, $access);
1051
		}
1052 237
	}
1053
1054
	/**
1055
	 * Registers the plugin's routes provided in the plugin config file
1056
	 *
1057
	 * @return void
1058
	 * @throws InvalidParameterException
1059
	 */
1060 237
	protected function registerRoutes() {
1061 237
		$routes = _elgg_services()->routes;
1062
1063 237
		$spec = (array) $this->getStaticConfig('routes', []);
1064
1065 237
		foreach ($spec as $name => $route_spec) {
1066 213
			if (!is_array($route_spec)) {
1067
				continue;
1068
			}
1069
1070 213
			$routes->register($name, $route_spec);
1071
		}
1072 237
	}
1073
1074
	/**
1075
	 * Registers the plugin's widgets provided in the plugin config file
1076
	 *
1077
	 * @return void
1078
	 * @throws \InvalidParameterException
1079
	 */
1080 237
	protected function registerWidgets() {
1081 237
		$widgets = _elgg_services()->widgets;
1082
1083 237
		$spec = (array) $this->getStaticConfig('widgets', []);
1084 237
		foreach ($spec as $widget_id => $widget_definition) {
1085 183
			if (!is_array($widget_definition)) {
1086
				continue;
1087
			}
1088 183
			if (!isset($widget_definition['id'])) {
1089 183
				$widget_definition['id'] = $widget_id;
1090
			}
1091
1092 183
			$definition = \Elgg\WidgetDefinition::factory($widget_definition);
1093
1094 183
			$widgets->registerType($definition);
1095
		}
1096 237
	}
1097
1098
	/**
1099
	 * Registers the plugin's languages
1100
	 *
1101
	 * Makes the language paths available to the system. Commonly used during boot of engine.
1102
	 *
1103
	 * @return void
1104
	 */
1105 249
	public function registerLanguages() {
1106 249
		$languages_path = $this->getLanguagesPath();
1107 249
		if (empty($languages_path)) {
1108 100
			return;
1109
		}
1110
1111 239
		_elgg_services()->translator->registerLanguagePath($languages_path);
1112 239
	}
1113
1114
	/**
1115
	 * Loads the plugin's translations
1116
	 *
1117
	 * Directly loads the translations for this plugin into available translations.
1118
	 *
1119
	 * Use when on runtime activating a plugin.
1120
	 *
1121
	 * @return void
1122
	 */
1123 16
	protected function loadLanguages() {
1124 16
		$languages_path = $this->getLanguagesPath();
1125 16
		if (empty($languages_path)) {
1126 10
			return;
1127
		}
1128
1129 6
		_elgg_services()->translator->registerTranslations($languages_path);
1130 6
	}
1131
1132
	/**
1133
	 * Registers the plugin's classes
1134
	 *
1135
	 * @return void
1136
	 */
1137 247
	protected function registerClasses() {
1138 247
		$classes_path = "{$this->getPath()}classes";
1139
1140 247
		_elgg_services()->autoloadManager->addClasses($classes_path);
1141 247
	}
1142
1143
	/**
1144
	 * Activates the plugin's entities
1145
	 *
1146
	 * @return void
1147
	 */
1148 237
	protected function activateEntities() {
1149 237
		$spec = (array) $this->getStaticConfig('entities', []);
1150 237
		if (empty($spec)) {
1151 194
			return;
1152
		}
1153
1154 184
		foreach ($spec as $entity) {
1155 184
			if (isset($entity['type'], $entity['subtype'], $entity['class'])) {
1156 184
				elgg_set_entity_class($entity['type'], $entity['subtype'], $entity['class']);
1157
			}
1158
		}
1159 184
	}
1160
1161
	/**
1162
	 * Deactivates the plugin's entities
1163
	 *
1164
	 * @return void
1165
	 */
1166 8
	protected function deactivateEntities() {
1167 8
		$spec = (array) $this->getStaticConfig('entities', []);
1168 8
		if (empty($spec)) {
1169 8
			return;
1170
		}
1171
1172
		foreach ($spec as $entity) {
1173
			if (isset($entity['type'], $entity['subtype'], $entity['class'])) {
1174
				elgg_set_entity_class($entity['type'], $entity['subtype']);
1175
			}
1176
		}
1177
	}
1178
	
1179
	/**
1180
	 * Registers the plugin's hooks provided in the plugin config file
1181
	 *
1182
	 * @return void
1183
	 */
1184 237
	protected function registerHooks() {
1185 237
		$hooks = _elgg_services()->hooks;
1186
1187 237
		$spec = (array) $this->getStaticConfig('hooks', []);
1188
1189 237
		foreach ($spec as $name => $types) {
1190
			foreach ($types as $type => $callbacks) {
1191
				foreach ($callbacks as $callback => $hook_spec) {
1192
					if (!is_array($hook_spec)) {
1193
						continue;
1194
					}
1195
					
1196
					$unregister = (bool) elgg_extract('unregister', $hook_spec, false);
1197
					
1198
					if ($unregister) {
1199
						$hooks->unregisterHandler($name, $type, $callback);
1200
					} else {
1201
						$priority = (int) elgg_extract('priority', $hook_spec, 500);
1202
			
1203
						$hooks->registerHandler($name, $type, $callback, $priority);
1204
					}
1205
				}
1206
			}
1207
		}
1208 237
	}
1209
	
1210
	/**
1211
	 * Registers the plugin's events provided in the plugin config file
1212
	 *
1213
	 * @return void
1214
	 */
1215 237
	protected function registerEvents() {
1216 237
		$events = _elgg_services()->events;
1217
1218 237
		$spec = (array) $this->getStaticConfig('events', []);
1219
1220 237
		foreach ($spec as $name => $types) {
1221
			foreach ($types as $type => $callbacks) {
1222
				foreach ($callbacks as $callback => $hook_spec) {
1223
					if (!is_array($hook_spec)) {
1224
						continue;
1225
					}
1226
					
1227
					$unregister = (bool) elgg_extract('unregister', $hook_spec, false);
1228
1229
					if ($unregister) {
1230
						$events->unregisterHandler($name, $type, $callback);
1231
					} else {
1232
						$priority = (int) elgg_extract('priority', $hook_spec, 500);
1233
			
1234
						$events->registerHandler($name, $type, $callback, $priority);
1235
					}
1236
				}
1237
			}
1238
		}
1239 237
	}
1240
1241
	/**
1242
	 * Get an attribute, metadata or private setting value
1243
	 *
1244
	 * @param string $name Name of the attribute or private setting
1245
	 *
1246
	 * @return mixed
1247
	 * @throws DatabaseException
1248
	 */
1249 455
	public function __get($name) {
1250
		// See if its in our base attribute
1251 455
		if (array_key_exists($name, $this->attributes)) {
1252 455
			return $this->attributes[$name];
1253
		}
1254
1255
		// object title and description are stored as metadata
1256 455
		if (in_array($name, ['title', 'description'])) {
1257 455
			return parent::__get($name);
1258
		}
1259
1260 7
		$result = $this->getPrivateSetting($name);
1261 7
		if ($result !== null) {
1262 5
			return $result;
1263
		}
1264
1265 6
		$defaults = $this->getStaticConfig('settings', []);
1266
1267 6
		return elgg_extract($name, $defaults, $result);
1268
	}
1269
1270
	/**
1271
	 * Set a value as attribute, metadata or private setting.
1272
	 *
1273
	 * Metadata applies to title and description.
1274
	 *
1275
	 * @param string $name  Name of the attribute or private_setting
1276
	 * @param mixed  $value Value to be set
1277
	 *
1278
	 * @return void
1279
	 */
1280 354
	public function __set($name, $value) {
1281 354
		if (array_key_exists($name, $this->attributes)) {
1282
			// Check that we're not trying to change the guid!
1283 1
			if ((array_key_exists('guid', $this->attributes)) && ($name == 'guid')) {
1284
				return;
1285
			}
1286
1287 1
			$this->attributes[$name] = $value;
1288
1289 1
			return;
1290
		}
1291
1292
		// object title and description are stored as metadata
1293 354
		if (in_array($name, ['title', 'description'])) {
1294 354
			parent::__set($name, $value);
1295
1296 354
			return;
1297
		}
1298
1299
		// to make sure we trigger the correct hooks
1300 6
		$this->setSetting($name, $value);
1301 6
	}
1302
1303
	/**
1304
	 * Sets the plugin to active or inactive.
1305
	 *
1306
	 * @param bool $active Set to active or inactive
1307
	 *
1308
	 * @return bool
1309
	 */
1310 17
	private function setStatus($active) {
1311 17
		if (!$this->guid) {
1312
			return false;
1313
		}
1314
1315 17
		$site = elgg_get_site_entity();
1316 17
		if ($active) {
1317 16
			$result = add_entity_relationship($this->guid, 'active_plugin', $site->guid);
1318
		} else {
1319 8
			$result = remove_entity_relationship($this->guid, 'active_plugin', $site->guid);
1320
		}
1321
		
1322 17
		if ($result) {
1323 17
			$this->activated = $active;
1324
		}
1325
1326 17
		$this->invalidateCache();
1327
1328 17
		return $result;
1329
	}
1330
1331
	/**
1332
	 * Returns the last error message registered.
1333
	 *
1334
	 * @return string|null
1335
	 */
1336
	public function getError() {
1337
		return $this->errorMsg;
1338
	}
1339
1340
	/**
1341
	 * Returns this plugin's \ElggPluginManifest object
1342
	 *
1343
	 * @return ElggPluginManifest|null
1344
	 */
1345 348
	public function getManifest() {
1346 348
		if ($this->manifest instanceof ElggPluginManifest) {
1347 11
			return $this->manifest;
1348
		}
1349
1350
		try {
1351 347
			$package = $this->getPackage();
1352 347
			if (!$package) {
1353 6
				throw PluginException::factory('InvalidPackage', $this);
1354
			}
1355
1356 344
			$this->manifest = $package->getManifest();
1357
1358 344
			return $this->manifest;
1359 6
		} catch (PluginException $e) {
1360 6
			_elgg_services()->logger->warning("Failed to load manifest for plugin $this->guid. " . $e->getMessage());
1361 6
			$this->errorMsg = $e->getMessage();
1362
1363 6
			elgg_log($e, \Psr\Log\LogLevel::ERROR);
1364
		}
1365 6
	}
1366
1367
	/**
1368
	 * Returns this plugin's \ElggPluginPackage object
1369
	 *
1370
	 * @return ElggPluginPackage|null
1371
	 */
1372 354
	public function getPackage() {
1373 354
		if ($this->package instanceof ElggPluginPackage) {
1374 17
			return $this->package;
1375
		}
1376
1377
		try {
1378 354
			$this->package = new ElggPluginPackage($this->getPath(), false);
1379
1380 352
			return $this->package;
1381 6
		} catch (Exception $e) {
1382 6
			_elgg_services()->logger->warning("Failed to load package for $this->guid. " . $e->getMessage());
1383 6
			$this->errorMsg = $e->getMessage();
1384
1385 6
			elgg_log($e, \Psr\Log\LogLevel::ERROR);
1386
		}
1387 6
	}
1388
1389
	/**
1390
	 * {@inheritdoc}
1391
	 */
1392 385
	public function isCacheable() {
1393 385
		return true;
1394
	}
1395
1396
	/**
1397
	 * {@inheritdoc}
1398
	 */
1399 385
	public function cache($persist = true) {
1400 385
		_elgg_services()->plugins->cache($this);
1401
1402 385
		parent::cache($persist);
1403 385
	}
1404
1405
	/**
1406
	 * {@inheritdoc}
1407
	 */
1408 360
	public function invalidateCache() {
1409
		
1410 360
		_elgg_services()->boot->invalidateCache();
1411 360
		_elgg_services()->plugins->invalidateCache($this->getID());
1412
1413 360
		parent::invalidateCache();
1414 360
	}
1415
}
1416