Test Failed
Push — master ( 294b60...945405 )
by Jerome
02:06
created

engine/classes/Elgg/Application.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Elgg;
4
5
use Elgg\Database\DbConfig;
6
use Elgg\Database\SiteSecret;
7
use Elgg\Di\ServiceProvider;
8
use Elgg\Filesystem\Directory;
9
use Elgg\Http\Request;
10
use Elgg\Filesystem\Directory\Local;
11
use ConfigurationException;
12
use Elgg\Project\Paths;
13
use Exception;
14
use InstallationException;
15
16
/**
17
 * Load, boot, and implement a front controller for an Elgg application
18
 *
19
 * To run as PHP CLI server:
20
 * <code>php -S localhost:8888 /full/path/to/elgg/index.php</code>
21
 *
22
 * The full path is necessary to work around this: https://bugs.php.net/bug.php?id=55726
23
 *
24
 * @since 2.0.0
25
 *
26
 * @property-read \Elgg\Menu\Service $menus
27
 * @property-read \Elgg\Views\TableColumn\ColumnFactory $table_columns
28
 */
29
class Application {
30
31
	const DEFAULT_LANG = 'en';
32
	const DEFAULT_LIMIT = 10;
33
34
	/**
35
	 * @var ServiceProvider
36
	 */
37
	private $services;
38
39
	/**
40
	 * @var bool
41
	 */
42
	private static $core_loaded = false;
43
44
	/**
45
	 * @var bool
46
	 */
47
	private static $testing_app;
48
49
	/**
50
	 * Property names of the service provider to be exposed via __get()
51
	 *
52
	 * E.g. the presence of `'foo' => true` in the list would allow _elgg_services()->foo to
53
	 * be accessed via elgg()->foo.
54
	 *
55
	 * @var string[]
56
	 */
57
	private static $public_services = [
58
		//'config' => true,
59
		'menus' => true,
60
		'table_columns' => true,
61
	];
62
63
	/**
64
	 * Reference to the loaded Application returned by elgg()
65
	 *
66
	 * @internal Do not use this. use elgg() to access the application
67
	 * @access private
68
	 * @var Application
69
	 */
70
	public static $_instance;
71 198
72 198
	/**
73
	 * Constructor
74
	 *
75
	 * Upon construction, no actions are taken to load or boot Elgg.
76
	 *
77
	 * @param ServiceProvider $services Elgg services provider
78
	 * @throws ConfigurationException
79 198
	 */
80 1
	public function __construct(ServiceProvider $services) {
81
		$this->services = $services;
82
		$services->setValue('app', $this);
83 198
84
		$this->initConfig();
85
	}
86
87
	/**
88
	 * Validate, normalize, fill in missing values, and lock some
89
	 *
90
	 * @return void
91
	 * @throws ConfigurationException
92 198
	 */
93
	private function initConfig() {
94
		$config = $this->services->config;
95
96 198
		if ($config->elgg_config_locks === null) {
97 198
			$config->elgg_config_locks = true;
98
		}
99
100
		if ($config->elgg_config_locks) {
101
			$lock = function ($name) use ($config) {
102
				$config->lock($name);
103
			};
104
		} else {
105
			// the installer needs to build an application with defaults then update
106
			// them after they're validated, so we don't want to lock them.
107
			$lock = function () {
108
			};
109
		}
110
111
		$this->services->timer->begin([]);
112
113
		// Until DB loads, let's log problems
114
		if ($config->debug === null) {
115
			$config->debug = 'NOTICE';
116
		}
117
118
		if ($config->dataroot) {
119
			$config->dataroot = rtrim($config->dataroot, '\\/') . DIRECTORY_SEPARATOR;
120 196
		} else {
121 196
			if (!$config->installer_running) {
122 196
				throw new ConfigurationException('Config value "dataroot" is required.');
123
			}
124
		}
125
		$lock('dataroot');
126
127
		if ($config->cacheroot) {
128
			$config->cacheroot = rtrim($config->cacheroot, '\\/') . DIRECTORY_SEPARATOR;
129
		} else {
130
			$config->cacheroot = $config->dataroot;
131
		}
132
		$lock('cacheroot');
133
134
		if ($config->wwwroot) {
135
			$config->wwwroot = rtrim($config->wwwroot, '/') . '/';
136
		} else {
137
			$config->wwwroot = $this->services->request->sniffElggUrl();
138
		}
139
		$lock('wwwroot');
140
141
		if (!$config->language) {
142
			$config->language = self::DEFAULT_LANG;
143
		}
144
145
		if ($config->default_limit) {
146
			$lock('default_limit');
147
		} else {
148
			$config->default_limit = self::DEFAULT_LIMIT;
149
		}
150
151
		$locked_props = [
152
			'site_guid' => 1,
153
			'path' => Paths::project(),
154
			'plugins_path' => Paths::project() . "mod/",
155
			'pluginspath' => Paths::project() . "mod/",
156
			'url' => $config->wwwroot,
157
		];
158
		foreach ($locked_props as $name => $value) {
159
			$config->$name = $value;
160
			$lock($name);
161
		}
162
163
		// move sensitive credentials into isolated services
164
		$this->services->dbConfig;
165
166
		// If the site secret is in the settings file, let's move it to that component
167
		// right away to keep this value out of config.
168
		$secret = SiteSecret::fromConfig($config);
169
		if ($secret) {
170
			$this->services->setValue('siteSecret', $secret);
171
			$config->elgg_config_set_secret = true;
172
		}
173
174
		$config->boot_complete = false;
175
	}
176
177
	/**
178
	 * Get the DB credentials.
179
	 *
180
	 * We no longer leave DB credentials in the config in case it gets accidentally dumped.
181
	 *
182
	 * @return \Elgg\Database\DbConfig
183
	 */
184
	public function getDbConfig() {
185
		return $this->services->dbConfig;
186
	}
187
188
	/**
189
	 * Define all Elgg global functions and constants, wire up boot events, but don't boot
190
	 *
191
	 * This includes all the .php files in engine/lib (not upgrades). If a script returns a function,
192
	 * it is queued and executed at the end.
193
	 *
194
	 * @return void
195
	 * @access private
196
	 * @internal
197
	 * @throws \InstallationException
198
	 */
199
	public function loadCore() {
200
		if (self::$core_loaded) {
201
			return;
202
		}
203
204
		$setups = [];
205
		$path = Paths::elgg() . 'engine/lib';
206
207
		// include library files, capturing setup functions
208
		foreach (self::getEngineLibs() as $file) {
209
			$return = Includer::includeFile("$path/$file");
210
			if (!$return) {
211
				throw new \InstallationException("Elgg lib file failed include: engine/lib/$file");
212
			}
213
			if ($return instanceof \Closure) {
214
				$setups[$file] = $return;
215
			}
216
		}
217
218
		// store instance to be returned by elgg()
219
		self::$_instance = $this;
220
221
		// allow global services access. :(
222
		_elgg_services($this->services);
0 ignored issues
show
The call to the function _elgg_services() seems unnecessary as the function has no side-effects.
Loading history...
223
224
		// setup logger and inject into config
225
		//$this->services->config->setLogger($this->services->logger);
226
227
		$hooks = $this->services->hooks;
228
		$events = $hooks->getEvents();
229
230
		// run setups
231
		foreach ($setups as $func) {
232
			$func($events, $hooks);
233
		}
234
235
		self::$core_loaded = true;
236
	}
237
238
	/**
239
	 * Start and boot the core
240
	 *
241
	 * @return self
242
	 */
243
	public static function start() {
244
		$app = self::factory();
245
		$app->bootCore();
246
		return $app;
247
	}
248
249
	/**
250
	 * Bootstrap the Elgg engine, loads plugins, and calls initial system events
251
	 *
252
	 * This method loads the full Elgg engine, checks the installation
253
	 * state, and triggers a series of events to finish booting Elgg:
254
	 * 	- {@elgg_event boot system}
255
	 * 	- {@elgg_event init system}
256
	 * 	- {@elgg_event ready system}
257
	 *
258
	 * If Elgg is not fully installed, the browser will be redirected to an installation page.
259
	 *
260
	 * @return void
261
	 */
262
	public function bootCore() {
263
		$config = $this->services->config;
264
265
		if ($this->isTestingApplication()) {
266
			throw new \RuntimeException('Unit tests should not call ' . __METHOD__);
267
		}
268
269
		if ($config->boot_complete) {
270
			return;
271
		}
272
273
		// in case not loaded already
274
		$this->loadCore();
275
276
		if (!$this->services->db) {
277
			// no database boot!
278
			elgg_views_boot();
279
			$this->services->session->start();
280
			$this->services->translator->loadTranslations();
281
282
			actions_init();
283
			_elgg_init();
284
			_elgg_input_init();
285
			_elgg_nav_init();
286
287
			$config->boot_complete = true;
288
			$config->lock('boot_complete');
289
			return;
290
		}
291
292
		// Connect to database, load language files, load configuration, init session
293
		$this->services->boot->boot($this->services);
294
295
		elgg_views_boot();
296
297
		// Load the plugins that are active
298
		$this->services->plugins->load();
299
300
		if (Paths::project() != Paths::elgg()) {
301
			// Elgg is installed as a composer dep, so try to treat the root directory
302
			// as a custom plugin that is always loaded last and can't be disabled...
303 View Code Duplication
			if (!$config->system_cache_loaded) {
1 ignored issue
show
This code seems to be duplicated across your project.

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

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

Loading history...
304
				// configure view locations for the custom plugin (not Elgg core)
305
				$viewsFile = Paths::project() . 'views.php';
306
				if (is_file($viewsFile)) {
307
					$viewsSpec = Includer::includeFile($viewsFile);
308
					if (is_array($viewsSpec)) {
309
						$this->services->views->mergeViewsSpec($viewsSpec);
310
					}
311
				}
312
313
				// find views for the custom plugin (not Elgg core)
314
				$this->services->views->registerPluginViews(Paths::project());
315
			}
316
317
			if (!$config->i18n_loaded_from_cache) {
318
				$this->services->translator->registerPluginTranslations(Paths::project());
319
			}
320
321
			// This is root directory start.php
322
			$root_start = Paths::project() . "start.php";
323
			if (is_file($root_start)) {
324
				require $root_start;
325
			}
326
		}
327
328
		// after plugins are started we know which viewtypes are populated
329
		$this->services->views->clampViewtypeToPopulatedViews();
330
331
		$this->allowPathRewrite();
332
333
		$events = $this->services->hooks->getEvents();
334
335
		// Allows registering handlers strictly before all init, system handlers
336
		$events->trigger('plugins_boot', 'system');
337
338
		// Complete the boot process for both engine and plugins
339
		$events->trigger('init', 'system');
340
341
		$config->boot_complete = true;
342
		$config->lock('boot_complete');
343
344
		// System loaded and ready
345
		$events->trigger('ready', 'system');
346
	}
347
348
	/**
349
	 * Get a Database wrapper for performing queries without booting Elgg
350
	 *
351
	 * If settings has not been loaded, it will be loaded to configure the DB connection.
352
	 *
353
	 * @note Before boot, the Database instance will not yet be bound to a Logger.
354
	 *
355
	 * @return \Elgg\Application\Database
356
	 */
357
	public function getDb() {
358
		return $this->services->publicDb;
359
	}
360
361
	/**
362
	 * Get an undefined property
363
	 *
364
	 * @param string $name The property name accessed
365
	 *
366
	 * @return mixed
367
	 */
368
	public function __get($name) {
369
		if (isset(self::$public_services[$name])) {
370
			return $this->services->{$name};
371
		}
372
		trigger_error("Undefined property: " . __CLASS__ . ":\${$name}");
373
	}
374
375
	/**
376
	 * Creates a new, trivial instance of Elgg\Application and set it as the singleton instance.
377
	 * If the singleton is already set, it's returned.
378
	 *
379
	 * @param array $spec Specification for initial call.
380
	 * @return self
381
	 * @throws ConfigurationException
382
	 */
383
	public static function factory(array $spec = []) {
384
		if (self::$_instance !== null) {
385
			return self::$_instance;
386
		}
387
388
		$defaults = [
389
			'service_provider' => null,
390
			'config' => null,
391
			'settings_path' => null,
392
			'handle_exceptions' => true,
393
			'handle_shutdown' => true,
394
			'overwrite_global_config' => true,
395
			'set_start_time' => true,
396
			'request' => null,
397
		];
398
		$spec = array_merge($defaults, $spec);
399
400
		if ($spec['set_start_time']) {
401
			/**
402
			 * The time with microseconds when the Elgg engine was started.
403
			 *
404
			 * @global float
405
			 */
406
			if (!isset($GLOBALS['START_MICROTIME'])) {
407
				$GLOBALS['START_MICROTIME'] = microtime(true);
408
			}
409
		}
410
411
		if (!$spec['service_provider']) {
412
			if (!$spec['config']) {
413
				$spec['config'] = Config::factory($spec['settings_path']);
414
			}
415
			$spec['service_provider'] = new ServiceProvider($spec['config']);
416
		}
417
418
		if ($spec['request']) {
419
			if ($spec['request'] instanceof Request) {
420
				$spec['service_provider']->setValue('request', $spec['request']);
421
			} else {
422
				throw new \InvalidArgumentException("Given request is not a " . Request::class);
423
			}
424
		}
425
426
		self::$_instance = new self($spec['service_provider']);
427
428
		if ($spec['handle_exceptions']) {
429
			set_error_handler([self::$_instance, 'handleErrors']);
430
			set_exception_handler([self::$_instance, 'handleExceptions']);
431
		}
432
433
		if ($spec['handle_shutdown']) {
434
			// we need to register for shutdown before Symfony registers the
435
			// session_write_close() function. https://github.com/Elgg/Elgg/issues/9243
436
			register_shutdown_function(function () {
437
				// There are cases where we may exit before this function is defined
438
				if (function_exists('_elgg_shutdown_hook')) {
439
					_elgg_shutdown_hook();
440
				}
441
			});
442
		}
443
444
		if ($spec['overwrite_global_config']) {
445
			global $CONFIG;
446
447
			// this will be buggy be at least PHP will log failures
448
			$CONFIG = $spec['service_provider']->config;
449
		}
450
451
		return self::$_instance;
452
	}
453
454
	/**
455
	 * Elgg's front controller. Handles basically all incoming URL requests.
456
	 *
457
	 * @return bool True if Elgg will handle the request, false if the server should (PHP-CLI server)
458
	 */
459
	public static function index() {
460
		$req = Request::createFromGlobals();
461
		/** @var Request $req */
462
463
		if ($req->isRewriteCheck()) {
464
			echo Request::REWRITE_TEST_OUTPUT;
465
			return true;
466
		}
467
468
		return self::factory(['request' => $req])->run();
469
	}
470
471
	/**
472
	 * Routes the request, booting core if not yet booted
473
	 *
474
	 * @return bool False if Elgg wants the PHP CLI server to handle the request
475
	 */
476
	public function run() {
477
		$config = $this->services->config;
478
		$request = $this->services->request;
479
480
		if ($request->isCliServer()) {
481
			if ($request->isCliServable(Paths::project())) {
482
				return false;
483
			}
484
485
			// overwrite value from settings
486
			$www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
487
			$config->wwwroot = $www_root;
488
			$config->wwwroot_cli_server = $www_root;
489
		}
490
491
		if (0 === strpos($request->getElggPath(), '/cache/')) {
492
			$this->services->cacheHandler->handleRequest($request)->prepare($request)->send();
493
			return true;
494
		}
495
496
		if (0 === strpos($request->getElggPath(), '/serve-file/')) {
497
			$this->services->serveFileHandler->getResponse($request)->send();
498
			return true;
499
		}
500
501
		$this->bootCore();
502
503
		// TODO use formal Response object instead
504
		// This is to set the charset to UTF-8.
505
		header("Content-Type: text/html;charset=utf-8", true);
506
507
		// re-fetch new request from services in case it was replaced by route:rewrite
508
		$request = $this->services->request;
509
510
		if (!$this->services->router->route($request)) {
511
			forward('', '404');
512
		}
513
	}
514
515
	/**
516
	 * Returns a directory that points to the root of Elgg, but not necessarily
517
	 * the install root. See `self::root()` for that.
518
	 *
519
	 * @return Directory
520
	 */
521
	public static function elggDir() {
522
		return Local::elggRoot();
523
	}
524
525
	/**
526
	 * Returns a directory that points to the project root, where composer is installed.
527
	 *
528
	 * @return Directory
529
	 */
530
	public static function projectDir() {
531
		return Local::projectRoot();
532
	}
533
534
	/**
535
	 * Renders a web UI for installing Elgg.
536
	 *
537
	 * @return void
538
	 */
539
	public static function install() {
540
		ini_set('display_errors', 1);
541
		$installer = new \ElggInstaller();
542
		$installer->run();
543
	}
544
545
	/**
546
	 * Elgg upgrade script.
547
	 *
548
	 * This script triggers any necessary upgrades. If the site has been upgraded
549
	 * to the most recent version of the code, no upgrades are run but the caches
550
	 * are flushed.
551
	 *
552
	 * Upgrades use a table {db_prefix}upgrade_lock as a mutex to prevent concurrent upgrades.
553
	 *
554
	 * The URL to forward to after upgrades are complete can be specified by setting $_GET['forward']
555
	 * to a relative URL.
556
	 *
557
	 * @return void
558
	 */
559
	public static function upgrade() {
560
		// we want to know if an error occurs
561
		ini_set('display_errors', 1);
562
		$is_cli = (php_sapi_name() === 'cli');
563
564
		$forward = function ($url) use ($is_cli) {
565
			if ($is_cli) {
566
				fwrite(STDOUT, "Open $url in your browser to continue." . PHP_EOL);
567
				return;
568
			}
569
570
			forward($url);
571
		};
572
573
		define('UPGRADING', 'upgrading');
574
575
		self::migrate();
576
		self::start();
577
578
		// check security settings
579
		if (!$is_cli && _elgg_config()->security_protect_upgrade && !elgg_is_admin_logged_in()) {
580
			// only admin's or users with a valid token can run upgrade.php
581
			elgg_signed_request_gatekeeper();
582
		}
583
584
		$site_url = _elgg_config()->url;
585
		$site_host = parse_url($site_url, PHP_URL_HOST) . '/';
586
587
		// turn any full in-site URLs into absolute paths
588
		$forward_url = get_input('forward', '/admin', false);
589
		$forward_url = str_replace([$site_url, $site_host], '/', $forward_url);
590
591
		if (strpos($forward_url, '/') !== 0) {
592
			$forward_url = '/' . $forward_url;
593
		}
594
595
		if ($is_cli || (get_input('upgrade') == 'upgrade')) {
596
			$upgrader = _elgg_services()->upgrades;
597
			$result = $upgrader->run();
598
599
			if ($result['failure'] == true) {
600
				register_error($result['reason']);
601
				$forward($forward_url);
602 196
			}
603 196
604 196
			// Find unprocessed batch upgrade classes and save them as ElggUpgrade objects
605
			$core_upgrades = (require self::elggDir()->getPath('engine/lib/upgrades/async-upgrades.php'));
606
			$has_pending_upgrades = _elgg_services()->upgradeLocator->run($core_upgrades);
607
608
			if ($has_pending_upgrades) {
609
				// Forward to the list of pending upgrades
610 1
				$forward_url = '/admin/upgrades';
611 1
			}
612
		} else {
613
			$rewriteTester = new \ElggRewriteTester();
614
			$url = elgg_get_site_url() . "__testing_rewrite?__testing_rewrite=1";
615
			if (!$rewriteTester->runRewriteTest($url)) {
616
				// see if there is a problem accessing the site at all
617
				// due to ip restrictions for example
618
				if (!$rewriteTester->runLocalhostAccessTest()) {
619
					// note: translation may not be available until after upgrade
620
					$msg = elgg_echo("installation:htaccess:localhost:connectionfailed");
621
					if ($msg === "installation:htaccess:localhost:connectionfailed") {
622
						$msg = "Elgg cannot connect to itself to test rewrite rules properly. Check "
623
								. "that curl is working and there are no IP restrictions preventing "
624
								. "localhost connections.";
625
					}
626
					echo $msg;
627
					exit;
628
				}
629
630
				// note: translation may not be available until after upgrade
631
				$msg = elgg_echo("installation:htaccess:needs_upgrade");
632
				if ($msg === "installation:htaccess:needs_upgrade") {
633
					$msg = "You must update your .htaccess file (use install/config/htaccess.dist as a guide).";
634
				}
635
				echo $msg;
636
				exit;
637
			}
638
639
			$vars = [
640
				'forward' => $forward_url
641
			];
642
643
			// reset cache to have latest translations available during upgrade
644
			elgg_reset_system_cache();
645
646
			echo elgg_view_page(elgg_echo('upgrading'), '', 'upgrade', $vars);
647
			exit;
648
		}
649
650
		$forward($forward_url);
651
	}
652
653
	/**
654
	 * Runs database migrations
655
	 *
656
	 * @throws InstallationException
657
	 * @return bool
658
	 */
659
	public static function migrate() {
660
		$conf = self::elggDir()->getPath('engine/schema/settings.php');
661
		if (!$conf) {
662
			throw new Exception('Settings file is required to run database migrations.');
663
		}
664
665
		$app = new \Phinx\Console\PhinxApplication();
666
		$wrapper = new \Phinx\Wrapper\TextWrapper($app, [
667
			'configuration' => $conf,
668
		]);
669
		$log = $wrapper->getMigrate();
670
		error_log($log);
671
672
		return true;
673
	}
674
675
	/**
676
	 * Returns configuration array for database migrations
677
	 * @return array
678
	 */
679
	public static function getMigrationSettings() {
680
681
		$config = Config::factory();
682
		$db_config = DbConfig::fromElggConfig($config);
683
		if ($db_config->isDatabaseSplit()) {
684
			$conn = $db_config->getConnectionConfig(DbConfig::WRITE);
685
		} else {
686
			$conn = $db_config->getConnectionConfig();
687
		}
688
689
		return [
690
			"paths" => [
691
				"migrations" => Paths::elgg() . 'engine/schema/migrations/',
692
			],
693
			"environments" => [
694
				"default_migration_table" => "{$conn['prefix']}migrations",
695
				"default_database" => "prod",
696
				"prod" => [
697
					"adapter" => "mysql",
698
					"host" => $conn['host'],
699
					"name" => $conn['database'],
700
					"user" => $conn['user'],
701
					"pass" => $conn['password'],
702
					"charset" => $conn['encoding'],
703
					"table_prefix" => $conn['prefix'],
704
				],
705
			],
706
		];
707
	}
708
709
	/**
710
	 * Allow plugins to rewrite the path.
711
	 *
712
	 * @return void
713
	 */
714
	private function allowPathRewrite() {
715
		$request = $this->services->request;
716
		$new = $this->services->router->allowRewrite($request);
717
		if ($new === $request) {
718
			return;
719
		}
720
721
		$this->services->setValue('request', $new);
722
		$this->services->context->initialize($new);
723
	}
724
725
	/**
726
	 * Flag this application as running for testing (PHPUnit)
727
	 *
728
	 * @param bool $testing Is testing application
729
	 * @return void
730
	 */
731
	public static function setTestingApplication($testing = true) {
732
		self::$testing_app = $testing;
733
	}
734
735
	/**
736
	 * Checks if the application is running in PHPUnit
737
	 * @return bool
738
	 */
739
	public static function isTestingApplication() {
740
		return (bool) self::$testing_app;
741
	}
742
743
	/**
744
	 * Intercepts, logs, and displays uncaught exceptions.
745
	 *
746
	 * To use a viewtype other than failsafe, create the views:
747
	 *  <viewtype>/messages/exceptions/admin_exception
748
	 *  <viewtype>/messages/exceptions/exception
749
	 * See the json viewtype for an example.
750
	 *
751
	 * @warning This function should never be called directly.
752
	 *
753
	 * @see http://www.php.net/set-exception-handler
754
	 *
755
	 * @param \Exception|\Error $exception The exception/error being handled
756
	 *
757
	 * @return void
758
	 * @access private
759
	 */
760
	public function handleExceptions($exception) {
761
		$timestamp = time();
762
		error_log("Exception at time $timestamp: $exception");
763
764
		// Wipe any existing output buffer
765
		ob_end_clean();
766
767
		// make sure the error isn't cached
768
		header("Cache-Control: no-cache, must-revalidate", true);
769
		header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true);
770
771
		if ($exception instanceof \InstallationException) {
772
			forward('/install.php');
773
		}
774
775
		if (!self::$core_loaded) {
776
			http_response_code(500);
777
			echo "Exception loading Elgg core. Check log at time $timestamp";
778
			return;
779
		}
780
781
		try {
782
			// allow custom scripts to trigger on exception
783
			// value in settings.php should be a system path to a file to include
784
			$exception_include = $this->services->config->exception_include;
785
786
			if ($exception_include && is_file($exception_include)) {
787
				ob_start();
788
789
				// don't isolate, these scripts may use the local $exception var.
790
				include $exception_include;
791
792
				$exception_output = ob_get_clean();
793
794
				// if content is returned from the custom handler we will output
795
				// that instead of our default failsafe view
796
				if (!empty($exception_output)) {
797
					echo $exception_output;
798
					exit;
799
				}
800
			}
801
802
			if (elgg_is_xhr()) {
803
				elgg_set_viewtype('json');
804
				$response = new \Symfony\Component\HttpFoundation\JsonResponse(null, 500);
805
			} else {
806
				elgg_set_viewtype('failsafe');
807
				$response = new \Symfony\Component\HttpFoundation\Response('', 500);
808
			}
809
810
			if (elgg_is_admin_logged_in()) {
811
				$body = elgg_view("messages/exceptions/admin_exception", [
812
					'object' => $exception,
813
					'ts' => $timestamp
814
				]);
815
			} else {
816
				$body = elgg_view("messages/exceptions/exception", [
817
					'object' => $exception,
818
					'ts' => $timestamp
819
				]);
820
			}
821
822
			$response->setContent(elgg_view_page(elgg_echo('exception:title'), $body));
823
			$response->send();
824
		} catch (\Exception $e) {
825
			$timestamp = time();
826
			$message = $e->getMessage();
827
			http_response_code(500);
828
			echo "Fatal error in exception handler. Check log for Exception at time $timestamp";
829
			error_log("Exception at time $timestamp : fatal error in exception handler : $message");
830
		}
831
	}
832
833
	/**
834
	 * Intercepts catchable PHP errors.
835
	 *
836
	 * @warning This function should never be called directly.
837
	 *
838
	 * @internal
839
	 * For catchable fatal errors, throws an Exception with the error.
840
	 *
841
	 * For non-fatal errors, depending upon the debug settings, either
842
	 * log the error or ignore it.
843
	 *
844
	 * @see http://www.php.net/set-error-handler
845
	 *
846
	 * @param int    $errno    The level of the error raised
847
	 * @param string $errmsg   The error message
848
	 * @param string $filename The filename the error was raised in
849
	 * @param int    $linenum  The line number the error was raised at
850
	 * @param array  $vars     An array that points to the active symbol table where error occurred
851
	 *
852
	 * @return true
853
	 * @throws \Exception
854
	 * @access private
855
	 */
856
	public function handleErrors($errno, $errmsg, $filename, $linenum, $vars) {
857
		$error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)";
858
859
		$log = function ($message, $level) {
860
			if (self::$core_loaded) {
861
				return elgg_log($message, $level);
862
			}
863
864
			return false;
865
		};
866
867
		switch ($errno) {
868
			case E_USER_ERROR:
869
				if (!$log("PHP: $error", 'ERROR')) {
870
					error_log("PHP ERROR: $error");
871
				}
872
				if (self::$core_loaded) {
873
					register_error("ERROR: $error");
874
				}
875
876
				// Since this is a fatal error, we want to stop any further execution but do so gracefully.
877
				throw new \Exception($error);
878
				break;
879
880
			case E_WARNING :
881
			case E_USER_WARNING :
882
			case E_RECOVERABLE_ERROR: // (e.g. type hint violation)
883
884
				// check if the error wasn't suppressed by the error control operator (@)
885
				if (error_reporting() && !$log("PHP: $error", 'WARNING')) {
886
					error_log("PHP WARNING: $error");
887
				}
888
				break;
889
890
			default:
891
				if (function_exists('_elgg_config')) {
892
					$debug = _elgg_config()->debug;
893
				} else {
894
					$debug = isset($GLOBALS['CONFIG']->debug) ? $GLOBALS['CONFIG']->debug : null;
895
				}
896
				if ($debug !== 'NOTICE') {
897
					return true;
898
				}
899
900
				if (!$log("PHP (errno $errno): $error", 'NOTICE')) {
901
					error_log("PHP NOTICE: $error");
902
				}
903
		}
904
905
		return true;
906
	}
907
908
	/**
909
	 * Does nothing.
910
	 *
911
	 * @return void
912
	 * @deprecated
913
	 */
914
	public function loadSettings() {
915
		trigger_error(__METHOD__ . ' is no longer needed and will be removed.');
916
	}
917
918
	/**
919
	 * Get all engine/lib library filenames
920
	 *
921
	 * @note We can't just pull in all directory files because some users leave old files in place.
922
	 *
923
	 * @return string[]
924
	 */
925
	private static function getEngineLibs() {
926
		return [
927
			'access.php',
928
			'actions.php',
929
			'admin.php',
930
			'annotations.php',
931
			'autoloader.php',
932
			'cache.php',
933
			'comments.php',
934
			'configuration.php',
935
			'constants.php',
936
			'cron.php',
937
			'database.php',
938
			'deprecated-3.0.php',
939
			'elgglib.php',
940
			'entities.php',
941
			'filestore.php',
942
			'group.php',
943
			'input.php',
944
			'languages.php',
945
			'mb_wrapper.php',
946
			'memcache.php',
947
			'metadata.php',
948
			'metastrings.php',
949
			'navigation.php',
950
			'notification.php',
951
			'output.php',
952
			'pagehandler.php',
953
			'pageowner.php',
954
			'pam.php',
955
			'plugins.php',
956
			'private_settings.php',
957
			'relationships.php',
958
			'river.php',
959
			'sessions.php',
960
			'statistics.php',
961
			'system_log.php',
962
			'tags.php',
963
			'upgrade.php',
964
			'user_settings.php',
965
			'users.php',
966
			'views.php',
967
			'widgets.php',
968
		];
969
	}
970
}
971