Completed
Push — 3.0 ( a99576...2abced )
by Jeroen
87:04 queued 29:21
created

engine/classes/ElggPlugin.php (2 issues)

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 374
	protected function initializeAttributes() {
50 374
		parent::initializeAttributes();
51
52 374
		$this->attributes['subtype'] = "plugin";
53 374
	}
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 356
	public static function fromId($plugin_id, $path = null) {
66 356
		if (empty($plugin_id)) {
67 1
			throw new InvalidArgumentException('Plugin ID must be set');
68
		}
69
70 355
		$plugin = elgg_get_plugin_from_id($plugin_id);
71
72 355
		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 355
		if (!$path) {
82 337
			$path = elgg_get_plugins_path();
83
		}
84
85 355
		$path = rtrim($path, '/');
86 355
		$plugin->setPath($path . '/' . $plugin_id);
87
88 355
		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 408
	public function getID() {
124 408
		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) {
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
	 * @access private
149
	 */
150 396
	public function setPath($path) {
151 396
		$this->path = \Elgg\Project\Paths::sanitize($path, true);
152 396
	}
153
154
	/**
155
	 * Returns the plugin's full path with trailing slash.
156
	 *
157
	 * @return string
158
	 */
159 394
	public function getPath() {
160 394
		if (isset($this->path)) {
161 392
			return $this->path;
162
		}
163
		
164 54
		$this->setPath(elgg_get_plugins_path() . $this->getID());
165 54
		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 206
	public function getLanguagesPath() {
175 206
		$languages_path = $this->getPath() . 'languages/';
176 206
		if (!is_dir($languages_path)) {
177 58
			return false;
178
		}
179
		
180 196
		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
	 * @access   private
193
	 * @internal For Elgg internal use only
194
	 */
195 200
	public function getStaticConfig($key, $default = null) {
196 200
		if ($this->static_config === null) {
197 194
			$this->static_config = [];
198
199
			try {
200 194
				if ($this->canReadFile(ElggPluginPackage::STATIC_CONFIG_FILENAME)) {
201 194
					$this->static_config = $this->includeFile(ElggPluginPackage::STATIC_CONFIG_FILENAME);
202
				}
203
			} catch (PluginException $ex) {
204
				elgg_log($ex, \Psr\Log\LogLevel::ERROR);
205
			}
206
		}
207
208 200
		if (isset($this->static_config[$key])) {
209 190
			return $this->static_config[$key];
210
		} else {
211 199
			return $default;
212
		}
213
	}
214
215
	/**
216
	 * Returns an array of available markdown files for this plugin
217
	 *
218
	 * @return array
219
	 */
220 1
	public function getAvailableTextFiles() {
221 1
		$filenames = $this->getPackage()->getTextFilenames();
222
223 1
		$files = [];
224 1
		foreach ($filenames as $filename) {
225 1
			if ($this->canReadFile($filename)) {
226 1
				$files[$filename] = "{$this->getPath()}{$filename}";
227
			}
228
		}
229
230 1
		return $files;
231
	}
232
233
	// Load Priority
234
235
	/**
236
	 * Gets the plugin's load priority.
237
	 *
238
	 * @return int|null
239
	 */
240 15
	public function getPriority() {
241 15
		$name = _elgg_services()->plugins->namespacePrivateSetting('internal', 'priority');
242
243 15
		$priority = $this->getPrivateSetting($name);
244 15
		if (isset($priority)) {
245 8
			return (int) $priority;
246
		}
247
248 11
		return null;
249
	}
250
251
	/**
252
	 * Sets the priority of the plugin
253
	 * Returns the new priority or false on error
254
	 *
255
	 * @param mixed $priority The priority to set
256
	 *                        One of +1, -1, first, last, or a number.
257
	 *                        If given a number, this will displace all plugins at that number
258
	 *                        and set their priorities +1
259
	 *
260
	 * @return int|false
261
	 * @throws DatabaseException
262
	 */
263 10
	public function setPriority($priority) {
264 10
		$priority = $this->normalizePriority($priority);
265
266 10
		return _elgg_services()->plugins->setPriority($this, $priority);
267
	}
268
269
	/**
270
	 * Normalize and validate new priority
271
	 *
272
	 * @param mixed $priority Priority to normalize
273
	 *
274
	 * @return int
275
	 * @access private
276
	 */
277 10
	public function normalizePriority($priority) {
278
		// if no priority assume a priority of 1
279 10
		$old_priority = $this->getPriority();
280 10
		$old_priority = $old_priority ? : 1;
281 10
		$max_priority = _elgg_get_max_plugin_priority() ? : 1;
282
283
		// can't use switch here because it's not strict and php evaluates +1 == 1
284 10
		if ($priority === '+1') {
285 1
			$priority = $old_priority + 1;
286 10
		} else if ($priority === '-1') {
287 1
			$priority = $old_priority - 1;
288 10
		} else if ($priority === 'first') {
289 1
			$priority = 1;
290 10
		} else if ($priority === 'last') {
291 1
			$priority = $max_priority;
292 10
		} else if ($priority === 'new') {
293 10
			$max_priority++;
294 10
			$priority = $max_priority;
295
		}
296
297 10
		return min($max_priority, max(1, (int) $priority));
298
	}
299
300
	// Plugin settings
301
302
	/**
303
	 * Returns a plugin setting
304
	 *
305
	 * @param string $name    The setting name
306
	 * @param mixed  $default The default value to return if none is set
307
	 *
308
	 * @return mixed
309
	 */
310 4
	public function getSetting($name, $default = null) {
311 4
		$values = $this->getAllSettings();
312 4
		return elgg_extract($name, $values, $default);
313
	}
314
315
	/**
316
	 * Returns an array of all settings saved for this plugin.
317
	 *
318
	 * @note Unlike user settings, plugin settings are not namespaced.
319
	 *
320
	 * @return array An array of key/value pairs.
321
	 */
322 41
	public function getAllSettings() {
323
324
		try {
325 41
			$defaults = [];
326 41
			if ($this->isActive()) {
327
				// only load settings from static config for active plugins to prevent issues
328
				// with internal plugin references ie. classes and language keys
329 36
				$defaults = $this->getStaticConfig('settings', []);
330
			}
331
332 41
			if (!$this->guid) {
333 1
				$settings = $this->temp_private_settings;
334
			} else {
335 40
				$settings = _elgg_services()->plugins->getAllSettings($this);
336
			}
337
338 41
			return array_merge($defaults, $settings);
339
		} catch (DatabaseException $ex) {
340
			return [];
341
		}
342
	}
343
344
	/**
345
	 * Set a plugin setting for the plugin
346
	 *
347
	 * @param string $name  The name to set
348
	 * @param string $value The value to set
349
	 *
350
	 * @return bool
351
	 */
352 8
	public function setSetting($name, $value) {
353
354 8
		$value = _elgg_services()->hooks->trigger('setting', 'plugin', [
355 8
			'plugin_id' => $this->getID(),
356 8
			'plugin' => $this,
357 8
			'name' => $name,
358 8
			'value' => $value,
359 8
		], $value);
360
361 8
		if (is_array($value)) {
362 1
			elgg_log('Plugin settings cannot store arrays.', 'ERROR');
363
364 1
			return false;
365
		}
366
367 8
		return $this->setPrivateSetting($name, $value);
368
	}
369
370
	/**
371
	 * Removes a plugin setting name and value
372
	 *
373
	 * @param string $name The setting name to remove
374
	 *
375
	 * @return bool
376
	 */
377 2
	public function unsetSetting($name) {
378 2
		return $this->removePrivateSetting($name);
379
	}
380
381
	/**
382
	 * Removes all settings for this plugin
383
	 * @return bool
384
	 */
385 1
	public function unsetAllSettings() {
386 1
		$settings = $this->getAllSettings();
387
388 1
		foreach ($settings as $name => $value) {
389 1
			if (strpos($name, 'elgg:internal:') === 0) {
390 1
				continue;
391
			}
392 1
			$this->unsetSetting($name);
393
		}
394
395 1
		return true;
396
	}
397
398
399
	// User settings
400
401
	/**
402
	 * Returns a user's setting for this plugin
403
	 *
404
	 * @param string $name      The setting name
405
	 * @param int    $user_guid The user GUID
406
	 * @param mixed  $default   The default value to return if none is set
407
	 *
408
	 * @return mixed The setting string value, the default value or false if there is no user
409
	 * @throws DatabaseException
410
	 */
411 1
	public function getUserSetting($name, $user_guid = 0, $default = null) {
412 1
		$values = $this->getAllUserSettings($user_guid);
413 1
		if ($values === false) {
414
			return false;
415
		}
416
417 1
		return elgg_extract($name, $values, $default);
418
	}
419
420
	/**
421
	 * Returns an array of all user settings saved for this plugin for the user.
422
	 *
423
	 * @note Plugin settings are saved with a prefix. This removes that prefix.
424
	 *
425
	 * @param int $user_guid The user GUID. Defaults to logged in.
426
	 *
427
	 * @return array An array of key/value pairs
428
	 * @throws DatabaseException
429
	 */
430 1
	public function getAllUserSettings($user_guid = 0) {
431
432 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
433 1
		if (!$user instanceof ElggUser) {
434
			return [];
435
		}
436
437 1
		$defaults = $this->getStaticConfig('user_settings', []);
438
439 1
		$settings = _elgg_services()->plugins->getAllUserSettings($this, $user);
440
441 1
		return array_merge($defaults, $settings);
442
	}
443
444
	/**
445
	 * Sets a user setting for a plugin
446
	 *
447
	 * @param string $name      The setting name
448
	 * @param string $value     The setting value
449
	 * @param int    $user_guid The user GUID
450
	 *
451
	 * @return mixed The new setting ID or false
452
	 */
453 1
	public function setUserSetting($name, $value, $user_guid = 0) {
454 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
455 1
		if (!$user instanceof ElggUser) {
456
			return false;
457
		}
458
459 1
		$value = _elgg_services()->hooks->trigger('usersetting', 'plugin', [
460 1
			'user' => $user,
461 1
			'plugin' => $this,
462 1
			'plugin_id' => $this->getID(),
463 1
			'name' => $name,
464 1
			'value' => $value
465 1
		], $value);
466
467 1
		if (is_array($value)) {
468 1
			elgg_log('Plugin user settings cannot store arrays.', 'ERROR');
469
470 1
			return false;
471
		}
472
473 1
		$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
474
475 1
		return $user->setPrivateSetting($name, $value);
476
	}
477
478
	/**
479
	 * Removes a user setting name and value.
480
	 *
481
	 * @param string $name      The user setting name
482
	 * @param int    $user_guid The user GUID
483
	 *
484
	 * @return bool
485
	 */
486 1
	public function unsetUserSetting($name, $user_guid = 0) {
487 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
488
489 1
		if (!$user instanceof ElggUser) {
490
			return false;
491
		}
492
493 1
		$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
494
495 1
		return $user->removePrivateSetting($name);
496
	}
497
498
	/**
499
	 * Removes all plugin settings for a given user
500
	 *
501
	 * @param int $user_guid The user GUID to remove user settings.
502
	 *
503
	 * @return bool
504
	 * @throws DatabaseException
505
	 */
506 1
	public function unsetAllUserSettings($user_guid = 0) {
507 1
		$user = _elgg_services()->entityTable->getUserForPermissionsCheck($user_guid);
508
509 1
		if (!$user instanceof ElggUser) {
510
			return false;
511
		}
512
513 1
		$settings = $this->getAllUserSettings($user_guid);
514
515 1
		foreach ($settings as $name => $value) {
516 1
			$name = _elgg_services()->plugins->namespacePrivateSetting('user_setting', $name, $this->getID());
517 1
			$user->removePrivateSetting($name);
518
		}
519
520 1
		return true;
521
	}
522
523
	/**
524
	 * Returns if the plugin is complete, meaning has all required files
525
	 * and Elgg can read them and they make sense.
526
	 *
527
	 * @return bool
528
	 */
529 1
	public function isValid() {
530 1
		if (!$this->getID()) {
531
			$this->errorMsg = elgg_echo('ElggPlugin:MissingID', [$this->guid]);
532
533
			return false;
534
		}
535
536 1
		if (!$this->getPackage() instanceof ElggPluginPackage) {
537
			$this->errorMsg = elgg_echo('ElggPlugin:NoPluginPackagePackage', [
538
				$this->getID(),
539
				$this->guid
540
			]);
541
542
			return false;
543
		}
544
545 1
		if (!$this->getPackage()->isValid()) {
546
			$this->errorMsg = $this->getPackage()->getError();
547
548
			return false;
549
		}
550
551 1
		return true;
552
	}
553
554
	/**
555
	 * Is this plugin active?
556
	 *
557
	 * @return bool
558
	 */
559 93
	public function isActive() {
560 93
		if (isset($this->activated)) {
561 54
			return $this->activated;
562
		}
563
564 59
		$this->activated = elgg_is_active_plugin($this->getID());
565 59
		return $this->activated;
566
	}
567
568
	/**
569
	 * Checks if this plugin can be activated on the current
570
	 * Elgg installation.
571
	 *
572
	 * @return bool
573
	 */
574 15
	public function canActivate() {
575 15
		if ($this->isActive()) {
576
			return false;
577
		}
578
579 15
		if ($this->getPackage()) {
580 15
			$result = $this->getPackage()->isValid() && $this->getPackage()->checkDependencies();
581 15
			if (!$result) {
582
				$this->errorMsg = $this->getPackage()->getError();
583
			}
584
585 15
			return $result;
586
		}
587
588
		return false;
589
	}
590
591
592
	// activating and deactivating
593
594
	/**
595
	 * Actives the plugin for the current site.
596
	 *
597
	 * @return bool
598
	 * @throws InvalidParameterException
599
	 * @throws PluginException
600
	 */
601 15
	public function activate() {
602 15
		if ($this->isActive()) {
603
			return false;
604
		}
605
606 15
		if (!$this->canActivate()) {
607
			return false;
608
		}
609
610
		// Check this before setting status because the file could potentially throw
611 15
		if (!$this->isStaticConfigValid()) {
612
			return false;
613
		}
614
615 15
		if (!$this->setStatus(true)) {
616
			return false;
617
		}
618
619
		// perform tasks and emit events
620
		// emit an event. returning false will make this not be activated.
621
		// we need to do this after it's been fully activated
622
		// or the deactivate will be confused.
623
		$params = [
624 15
			'plugin_id' => $this->getID(),
625 15
			'plugin_entity' => $this,
626
		];
627
628 15
		$return = _elgg_services()->events->trigger('activate', 'plugin', $params);
0 ignored issues
show
$params of type array<string,ElggPlugin|string> is incompatible with the type string expected by parameter $object of Elgg\EventsService::trigger(). ( Ignorable by Annotation )

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

628
		$return = _elgg_services()->events->trigger('activate', 'plugin', /** @scrutinizer ignore-type */ $params);
Loading history...
629
630
		// if there are any on_enable functions, start the plugin now and run them
631
		// Note: this will not run re-run the init hooks!
632 15
		if ($return) {
633
			try {
634 15
				_elgg_services()->events->trigger('cache:flush', 'system');
635
636 15
				$this->register();
637
				
638
				// directly load languages to have them available during runtime
639 15
				$this->loadLanguages();
640
				
641 15
				$setup = $this->boot();
642 15
				if ($setup instanceof Closure) {
643 7
					$setup();
644
				}
645
646 15
				$this->getBootstrap()->activate();
647
648 15
				if ($this->canReadFile('activate.php')) {
649
					$return = $this->includeFile('activate.php');
650
				}
651
652 15
				$this->init();
653
			} catch (PluginException $ex) {
654
				elgg_log($ex, \Psr\Log\LogLevel::ERROR);
655
656
				$return = false;
657
			}
658
		}
659
660 15
		if ($return === false) {
661
			$this->deactivate();
662
		} else {
663 15
			elgg_delete_admin_notice("cannot_start {$this->getID()}");
664
665 15
			_elgg_services()->events->trigger('cache:flush', 'system');
666 15
			_elgg_services()->logger->notice("Plugin {$this->getID()} has been activated");
667
		}
668
669 15
		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 7
	public function canDeactivate() {
680 7
		if (!$this->isActive()) {
681
			return false;
682
		}
683
684 7
		$dependents = [];
685
686 7
		$active_plugins = elgg_get_plugins();
687
688 7
		foreach ($active_plugins as $plugin) {
689 6
			$manifest = $plugin->getManifest();
690 6
			if (!$manifest) {
691 2
				continue;
692
			}
693 6
			$requires = $manifest->getRequires();
694
695 6
			foreach ($requires as $required) {
696 6
				if ($required['type'] == 'plugin' && $required['name'] == $this->getID()) {
697
					// there are active dependents
698 6
					$dependents[$manifest->getPluginID()] = $plugin;
699
				}
700
			}
701
		}
702
703 7
		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 7
		return true;
720
	}
721
722
	/**
723
	 * Deactivates the plugin.
724
	 *
725
	 * @return bool
726
	 * @throws PluginException
727
	 */
728 8
	public function deactivate() {
729 8
		if (!$this->isActive()) {
730 3
			return false;
731
		}
732
733 7
		if (!$this->canDeactivate()) {
734
			return false;
735
		}
736
737
		// emit an event. returning false will cause this to not be deactivated.
738
		$params = [
739 7
			'plugin_id' => $this->getID(),
740 7
			'plugin_entity' => $this,
741
		];
742
743 7
		$return = _elgg_services()->events->trigger('deactivate', 'plugin', $params);
0 ignored issues
show
$params of type array<string,ElggPlugin|string> is incompatible with the type string expected by parameter $object of Elgg\EventsService::trigger(). ( Ignorable by Annotation )

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

743
		$return = _elgg_services()->events->trigger('deactivate', 'plugin', /** @scrutinizer ignore-type */ $params);
Loading history...
744 7
		if ($return === false) {
745
			return false;
746
		}
747
748 7
		$this->getBootstrap()->deactivate();
749
750
		// run any deactivate code
751 7
		if ($this->canReadFile('deactivate.php')) {
752
			// allows you to prevent disabling a plugin by returning false in a deactivate.php file
753
			if ($this->includeFile('deactivate.php') === false) {
754
				return false;
755
			}
756
		}
757
758 7
		$this->deactivateEntities();
759
760 7
		_elgg_services()->events->trigger('cache:flush', 'system');
761
762 7
		_elgg_services()->logger->notice("Plugin {$this->getID()} has been deactivated");
763
764 7
		return $this->setStatus(false);
765
	}
766
767
	/**
768
	 * Bootstrap object
769
	 * @return \Elgg\PluginBootstrapInterface
770
	 * @throws PluginException
771
	 * @access private
772
	 * @internal
773
	 */
774 197
	public function getBootstrap() {
775 197
		$bootstrap = $this->getStaticConfig('bootstrap');
776 197
		if ($bootstrap) {
777 40
			if (!is_subclass_of($bootstrap, \Elgg\PluginBootstrapInterface::class)) {
778
				throw PluginException::factory(
779
					'InvalidBootstrap',
780
					$this,
781
					elgg_echo('LogicException:InterfaceNotImplemented', [
782
						$bootstrap,
783
						\Elgg\PluginBootstrapInterface::class
784
					])
785
				);
786
			}
787
788 40
			return new $bootstrap($this, _elgg_services()->dic);
789
		}
790
791 193
		return new \Elgg\DefaultPluginBootstrap($this, _elgg_services()->dic);
792
	}
793
794
	/**
795
	 * Register plugin classes and require composer autoloader
796
	 *
797
	 * @return void
798
	 * @throws PluginException
799
	 * @access private
800
	 * @internal
801
	 */
802 204
	public function autoload() {
803 204
		$this->registerClasses();
804
805 204
		$autoload_file = 'vendor/autoload.php';
806 204
		if (!$this->canReadFile($autoload_file)) {
807 204
			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
	 * @access private
830
	 * @internal
831
	 */
832 194
	public function register() {
833 194
		$this->autoload();
834
835 194
		$this->activateEntities();
836 194
		$this->registerLanguages();
837 194
		$this->registerViews();
838
839 194
		$this->getBootstrap()->load();
840 194
	}
841
842
	/**
843
	 * Boot the plugin
844
	 *
845
	 * @throws PluginException
846
	 * @return \Closure|null
847
	 * @access private
848
	 * @internal
849
	 */
850 194
	public function boot() {
851 194
		$result = null;
852 194
		if ($this->canReadFile('start.php')) {
853 185
			$result = Application::requireSetupFileOnce("{$this->getPath()}start.php");
854
		}
855
856 194
		$this->getBootstrap()->boot();
857
858 194
		return $result;
859
	}
860
861
	/**
862
	 * Init the plugin
863
	 * @return void
864
	 * @throws InvalidParameterException
865
	 * @throws PluginException
866
	 * @access private
867
	 * @internal
868
	 */
869 194
	public function init() {
870 194
		$this->registerRoutes();
871 194
		$this->registerActions();
872 194
		$this->registerEntities();
873 194
		$this->registerWidgets();
874
875 194
		$this->getBootstrap()->init();
876 194
	}
877
878
	/**
879
	 * Includes one of the plugins files
880
	 *
881
	 * @param string $filename The name of the file
882
	 *
883
	 * @throws PluginException
884
	 * @return mixed The return value of the included file (or 1 if there is none)
885
	 */
886 189
	protected function includeFile($filename) {
887 189
		$filepath = "{$this->getPath()}{$filename}";
888
889 189
		if (!$this->canReadFile($filename)) {
890
			$msg = elgg_echo(
891
				'ElggPlugin:Exception:CannotIncludeFile',
892
				[$filename, $this->getID(), $this->guid, $this->getPath()]
893
			);
894
895
			throw PluginException::factory('CannotIncludeFile', $this, $msg);
896
		}
897
898
		try {
899 189
			$ret = Application::requireSetupFileOnce($filepath);
900
		} catch (Exception $e) {
901
			$msg = elgg_echo(
902
				'ElggPlugin:Exception:IncludeFileThrew',
903
				[$filename, $this->getID(), $this->guid, $this->getPath()]
904
			);
905
906
			throw PluginException::factory('IncludeFileThrew', $this, $msg, $e);
907
		}
908
909 189
		return $ret;
910
	}
911
912
	/**
913
	 * Checks whether a plugin file with the given name exists
914
	 *
915
	 * @param string $filename The name of the file
916
	 *
917
	 * @return bool
918
	 */
919 208
	protected function canReadFile($filename) {
920 208
		$path = "{$this->getPath()}{$filename}";
921
922 208
		return is_file($path) && is_readable($path);
923
	}
924
925
	/**
926
	 * If a static config file is present, is it a serializable array?
927
	 *
928
	 * @return bool
929
	 * @throws PluginException
930
	 */
931 15
	private function isStaticConfigValid() {
932 15
		if (!$this->canReadFile(ElggPluginPackage::STATIC_CONFIG_FILENAME)) {
933 5
			return true;
934
		}
935
936 10
		ob_start();
937 10
		$value = $this->includeFile(ElggPluginPackage::STATIC_CONFIG_FILENAME);
938 10
		if (ob_get_clean() !== '') {
939
			$this->errorMsg = elgg_echo('ElggPlugin:activate:ConfigSentOutput');
940
941
			return false;
942
		}
943
944
		// make sure can serialize
945 10
		$value = @unserialize(serialize($value));
946 10
		if (!is_array($value)) {
947
			$this->errorMsg = elgg_echo('ElggPlugin:activate:BadConfigFormat');
948
949
			return false;
950
		}
951
952 10
		return true;
953
	}
954
955
	/**
956
	 * Registers the plugin's views
957
	 *
958
	 * @throws PluginException
959
	 * @return void
960
	 */
961 194
	protected function registerViews() {
962 194
		if (_elgg_config()->system_cache_loaded) {
963 32
			return;
964
		}
965
966 166
		$views = _elgg_services()->views;
967
968
		// Declared views first
969 166
		$file = "{$this->getPath()}views.php";
970 166
		if (is_file($file)) {
971
			$spec = Includer::includeFile($file);
972
			if (is_array($spec)) {
973
				$views->mergeViewsSpec($spec);
974
			}
975
		}
976
977 166
		$spec = $this->getStaticConfig('views');
978 166
		if ($spec) {
979 9
			$views->mergeViewsSpec($spec);
980
		}
981
982
		// Allow /views directory files to override
983 166
		if (!$views->registerPluginViews($this->getPath(), $failed_dir)) {
984
			$key = 'ElggPlugin:Exception:CannotRegisterViews';
985
			$args = [$this->getID(), $this->guid, $failed_dir];
986
			$msg = elgg_echo($key, $args);
987
988
			throw PluginException::factory('CannotRegisterViews', $this, $msg);
989
		}
990 166
	}
991
992
	/**
993
	 * Registers the plugin's entities
994
	 *
995
	 * @return void
996
	 */
997 194
	protected function registerEntities() {
998
999 194
		$spec = (array) $this->getStaticConfig('entities', []);
1000 194
		if (empty($spec)) {
1001 87
			return;
1002
		}
1003
1004 141
		foreach ($spec as $entity) {
1005 141
			if (isset($entity['type'], $entity['subtype'], $entity['searchable']) && $entity['searchable']) {
1006 141
				elgg_register_entity_type($entity['type'], $entity['subtype']);
1007
			}
1008
		}
1009 141
	}
1010
1011
	/**
1012
	 * Registers the plugin's actions provided in the plugin config file
1013
	 *
1014
	 * @return void
1015
	 */
1016 194
	protected function registerActions() {
1017 194
		self::addActionsFromStaticConfig($this->getStaticConfig('actions', []), $this->getPath());
1018 194
	}
1019
1020
	/**
1021
	 * Register a plugin's actions provided in the config file
1022
	 *
1023
	 * @todo   move to a static config service
1024
	 *
1025
	 * @param array  $spec      'actions' section of static config
1026
	 * @param string $root_path Plugin path
1027
	 *
1028
	 * @return void
1029
	 * @access private
1030
	 * @internal
1031
	 */
1032 194
	public static function addActionsFromStaticConfig(array $spec, $root_path) {
1033 194
		$actions = _elgg_services()->actions;
1034 194
		$root_path = rtrim($root_path, '/\\');
1035
1036 194
		foreach ($spec as $action => $action_spec) {
1037 168
			if (!is_array($action_spec)) {
1038
				continue;
1039
			}
1040
1041 168
			$access = elgg_extract('access', $action_spec, 'logged_in');
1042 168
			$handler = elgg_extract('controller', $action_spec);
1043 168
			if (!$handler) {
1044 168
				$handler = elgg_extract('filename', $action_spec);
1045 168
				if (!$handler) {
1046 168
					$handler = "$root_path/actions/{$action}.php";
1047
				}
1048
			}
1049
1050 168
			$actions->register($action, $handler, $access);
1051
		}
1052 194
	}
1053
1054
	/**
1055
	 * Registers the plugin's routes provided in the plugin config file
1056
	 *
1057
	 * @return void
1058
	 * @throws InvalidParameterException
1059
	 */
1060 194
	protected function registerRoutes() {
1061 194
		$routes = _elgg_services()->routes;
1062
1063 194
		$spec = (array) $this->getStaticConfig('routes', []);
1064
1065 194
		foreach ($spec as $name => $route_spec) {
1066 170
			if (!is_array($route_spec)) {
1067
				continue;
1068
			}
1069
1070 170
			$routes->register($name, $route_spec);
1071
		}
1072 194
	}
1073
1074
	/**
1075
	 * Registers the plugin's widgets provided in the plugin config file
1076
	 *
1077
	 * @return void
1078
	 * @throws \InvalidParameterException
1079
	 */
1080 194
	protected function registerWidgets() {
1081 194
		$widgets = _elgg_services()->widgets;
1082
1083 194
		$spec = (array) $this->getStaticConfig('widgets', []);
1084 194
		foreach ($spec as $widget_id => $widget_definition) {
1085 135
			if (!is_array($widget_definition)) {
1086
				continue;
1087
			}
1088 135
			if (!isset($widget_definition['id'])) {
1089 135
				$widget_definition['id'] = $widget_id;
1090
			}
1091
1092 135
			$definition = \Elgg\WidgetDefinition::factory($widget_definition);
1093
1094 135
			$widgets->registerType($definition);
1095
		}
1096 194
	}
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 206
	public function registerLanguages() {
1106 206
		$languages_path = $this->getLanguagesPath();
1107 206
		if (empty($languages_path)) {
1108 58
			return;
1109
		}
1110
1111 196
		_elgg_services()->translator->registerLanguagePath($languages_path);
1112 196
	}
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 15
	protected function loadLanguages() {
1124 15
		$languages_path = $this->getLanguagesPath();
1125 15
		if (empty($languages_path)) {
1126 10
			return;
1127
		}
1128
1129 5
		_elgg_services()->translator->registerTranslations($languages_path);
1130 5
	}
1131
1132
	/**
1133
	 * Registers the plugin's classes
1134
	 *
1135
	 * @return void
1136
	 */
1137 204
	protected function registerClasses() {
1138 204
		$classes_path = "{$this->getPath()}classes";
1139
1140 204
		_elgg_services()->autoloadManager->addClasses($classes_path);
1141 204
	}
1142
1143
	/**
1144
	 * Activates the plugin's entities
1145
	 *
1146
	 * @return void
1147
	 */
1148 194
	protected function activateEntities() {
1149 194
		$spec = (array) $this->getStaticConfig('entities', []);
1150 194
		if (empty($spec)) {
1151 87
			return;
1152
		}
1153
1154 141
		foreach ($spec as $entity) {
1155 141
			if (isset($entity['type'], $entity['subtype'], $entity['class'])) {
1156 141
				elgg_set_entity_class($entity['type'], $entity['subtype'], $entity['class']);
1157
			}
1158
		}
1159 141
	}
1160
1161
	/**
1162
	 * Deactivates the plugin's entities
1163
	 *
1164
	 * @return void
1165
	 */
1166 7
	protected function deactivateEntities() {
1167 7
		$spec = (array) $this->getStaticConfig('entities', []);
1168 7
		if (empty($spec)) {
1169 7
			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
	 * Get an attribute, metadata or private setting value
1181
	 *
1182
	 * @param string $name Name of the attribute or private setting
1183
	 *
1184
	 * @return mixed
1185
	 * @throws DatabaseException
1186
	 */
1187 410
	public function __get($name) {
1188
		// See if its in our base attribute
1189 410
		if (array_key_exists($name, $this->attributes)) {
1190 410
			return $this->attributes[$name];
1191
		}
1192
1193
		// object title and description are stored as metadata
1194 408
		if (in_array($name, ['title', 'description'])) {
1195 408
			return parent::__get($name);
1196
		}
1197
1198 7
		$result = $this->getPrivateSetting($name);
1199 7
		if ($result !== null) {
1200 5
			return $result;
1201
		}
1202
1203 6
		$defaults = $this->getStaticConfig('settings', []);
1204
1205 6
		return elgg_extract($name, $defaults, $result);
1206
	}
1207
1208
	/**
1209
	 * Set a value as attribute, metadata or private setting.
1210
	 *
1211
	 * Metadata applies to title and description.
1212
	 *
1213
	 * @param string $name  Name of the attribute or private_setting
1214
	 * @param mixed  $value Value to be set
1215
	 *
1216
	 * @return void
1217
	 */
1218 348
	public function __set($name, $value) {
1219 348
		if (array_key_exists($name, $this->attributes)) {
1220
			// Check that we're not trying to change the guid!
1221 1
			if ((array_key_exists('guid', $this->attributes)) && ($name == 'guid')) {
1222
				return;
1223
			}
1224
1225 1
			$this->attributes[$name] = $value;
1226
1227 1
			return;
1228
		}
1229
1230
		// object title and description are stored as metadata
1231 348
		if (in_array($name, ['title', 'description'])) {
1232 348
			parent::__set($name, $value);
1233
1234 348
			return;
1235
		}
1236
1237
		// to make sure we trigger the correct hooks
1238 6
		$this->setSetting($name, $value);
1239 6
	}
1240
1241
	/**
1242
	 * Sets the plugin to active or inactive.
1243
	 *
1244
	 * @param bool $active Set to active or inactive
1245
	 *
1246
	 * @return bool
1247
	 */
1248 16
	private function setStatus($active) {
1249 16
		if (!$this->guid) {
1250
			return false;
1251
		}
1252
1253 16
		$site = elgg_get_site_entity();
1254 16
		if ($active) {
1255 15
			$result = add_entity_relationship($this->guid, 'active_plugin', $site->guid);
1256
		} else {
1257 7
			$result = remove_entity_relationship($this->guid, 'active_plugin', $site->guid);
1258
		}
1259
		
1260 16
		if ($result) {
1261 16
			$this->activated = $active;
1262
		}
1263
1264 16
		$this->invalidateCache();
1265
1266 16
		return $result;
1267
	}
1268
1269
	/**
1270
	 * Returns the last error message registered.
1271
	 *
1272
	 * @return string|null
1273
	 */
1274
	public function getError() {
1275
		return $this->errorMsg;
1276
	}
1277
1278
	/**
1279
	 * Returns this plugin's \ElggPluginManifest object
1280
	 *
1281
	 * @return ElggPluginManifest|null
1282
	 */
1283 343
	public function getManifest() {
1284 343
		if ($this->manifest instanceof ElggPluginManifest) {
1285 16
			return $this->manifest;
1286
		}
1287
1288
		try {
1289 338
			$package = $this->getPackage();
1290 338
			if (!$package) {
1291 6
				throw PluginException::factory('InvalidPackage', $this);
1292
			}
1293
1294 335
			$this->manifest = $package->getManifest();
1295
1296 335
			return $this->manifest;
1297 6
		} catch (PluginException $e) {
1298 6
			_elgg_services()->logger->warning("Failed to load manifest for plugin $this->guid. " . $e->getMessage());
1299 6
			$this->errorMsg = $e->getMessage();
1300
1301 6
			elgg_log($e, \Psr\Log\LogLevel::ERROR);
1302
		}
1303 6
	}
1304
1305
	/**
1306
	 * Returns this plugin's \ElggPluginPackage object
1307
	 *
1308
	 * @return ElggPluginPackage|null
1309
	 */
1310 343
	public function getPackage() {
1311 343
		if ($this->package instanceof ElggPluginPackage) {
1312 16
			return $this->package;
1313
		}
1314
1315
		try {
1316 343
			$this->package = new ElggPluginPackage($this->getPath(), false);
1317
1318 341
			return $this->package;
1319 6
		} catch (Exception $e) {
1320 6
			_elgg_services()->logger->warning("Failed to load package for $this->guid. " . $e->getMessage());
1321 6
			$this->errorMsg = $e->getMessage();
1322
1323 6
			elgg_log($e, \Psr\Log\LogLevel::ERROR);
1324
		}
1325 6
	}
1326
1327
	/**
1328
	 * {@inheritdoc}
1329
	 */
1330 373
	public function isCacheable() {
1331 373
		return true;
1332
	}
1333
1334
	/**
1335
	 * {@inheritdoc}
1336
	 */
1337 373
	public function cache($persist = true) {
1338 373
		_elgg_services()->plugins->cache($this);
1339
1340 373
		parent::cache($persist);
1341 373
	}
1342
1343
	/**
1344
	 * {@inheritdoc}
1345
	 */
1346 353
	public function invalidateCache() {
1347
		
1348 353
		_elgg_services()->boot->invalidateCache();
1349 353
		_elgg_services()->plugins->invalidateCache($this->getID());
1350
1351 353
		parent::invalidateCache();
1352 353
	}
1353
}
1354