Test Failed
Push — master ( c2873c...a077d1 )
by Jeroen
01:35
created

Application::factory()   C

Complexity

Conditions 12
Paths 154

Size

Total Lines 70
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
cc 12
eloc 37
nc 154
nop 1
dl 0
loc 70
ccs 0
cts 31
cp 0
crap 156
rs 5.1917
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
381
		if (self::$_instance !== null) {
382
			return self::$_instance;
383
		}
384
385
		$defaults = [
386
			'service_provider' => null,
387
			'config' => null,
388
			'settings_path' => null,
389
			'handle_exceptions' => true,
390
			'handle_shutdown' => true,
391
			'overwrite_global_config' => true,
392
			'set_start_time' => true,
393
			'request' => null,
394
		];
395
		$spec = array_merge($defaults, $spec);
396
397
		if ($spec['set_start_time']) {
398
			/**
399
			 * The time with microseconds when the Elgg engine was started.
400
			 *
401
			 * @global float
402
			 */
403
			if (!isset($GLOBALS['START_MICROTIME'])) {
404
				$GLOBALS['START_MICROTIME'] = microtime(true);
405
			}
406
		}
407
408
		if (!$spec['service_provider']) {
409
			if (!$spec['config']) {
410
				$spec['config'] = Config::factory($spec['settings_path']);
411
			}
412
			$spec['service_provider'] = new ServiceProvider($spec['config']);
413
		}
414
415
		if ($spec['request']) {
416
			if ($spec['request'] instanceof Request) {
417
				$spec['service_provider']->setValue('request', $spec['request']);
418
			} else {
419
				throw new \InvalidArgumentException("Given request is not a " . Request::class);
420
			}
421
		}
422
423
		self::$_instance = new self($spec['service_provider']);
424
425
		if ($spec['handle_exceptions']) {
426
			set_error_handler([self::$_instance, 'handleErrors']);
427
			set_exception_handler([self::$_instance, 'handleExceptions']);
428
		}
429
430
		if ($spec['handle_shutdown']) {
431
			// we need to register for shutdown before Symfony registers the
432
			// session_write_close() function. https://github.com/Elgg/Elgg/issues/9243
433
			register_shutdown_function(function () {
434
				// There are cases where we may exit before this function is defined
435
				if (function_exists('_elgg_shutdown_hook')) {
436
					_elgg_shutdown_hook();
437
				}
438
			});
439
		}
440
441
		if ($spec['overwrite_global_config']) {
442
			global $CONFIG;
443
444
			// this will be buggy be at least PHP will log failures
445
			$CONFIG = $spec['service_provider']->config;
446
		}
447
448
		return self::$_instance;
449
	}
450
451
	/**
452
	 * Elgg's front controller. Handles basically all incoming URL requests.
453
	 *
454
	 * @return bool True if Elgg will handle the request, false if the server should (PHP-CLI server)
455
	 */
456
	public static function index() {
457
		$req = Request::createFromGlobals();
458
		/** @var Request $req */
459
460
		if ($req->isRewriteCheck()) {
461
			echo Request::REWRITE_TEST_OUTPUT;
462
			return true;
463
		}
464
465
		return self::factory(['request' => $req])->run();
466
	}
467
468
	/**
469
	 * Routes the request, booting core if not yet booted
470
	 *
471
	 * @return bool False if Elgg wants the PHP CLI server to handle the request
472
	 */
473
	public function run() {
474
		$config = $this->services->config;
475
		$request = $this->services->request;
476
477
		if ($request->isCliServer()) {
478
			if ($request->isCliServable(Paths::project())) {
479
				return false;
480
			}
481
482
			// overwrite value from settings
483
			$www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
484
			$config->wwwroot = $www_root;
485
			$config->wwwroot_cli_server = $www_root;
1 ignored issue
show
Bug introduced by
The property wwwroot_cli_server does not seem to exist. Did you mean wwwroot?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
486
		}
487
488
		if (0 === strpos($request->getElggPath(), '/cache/')) {
489
			$this->services->cacheHandler->handleRequest($request)->prepare($request)->send();
490
			return true;
491
		}
492
493
		if (0 === strpos($request->getElggPath(), '/serve-file/')) {
494
			$this->services->serveFileHandler->getResponse($request)->send();
495
			return true;
496
		}
497
498
		$this->bootCore();
499
500
		// TODO use formal Response object instead
501
		// This is to set the charset to UTF-8.
502
		header("Content-Type: text/html;charset=utf-8", true);
503
504
		// re-fetch new request from services in case it was replaced by route:rewrite
505
		$request = $this->services->request;
506
507
		if (!$this->services->router->route($request)) {
508
			forward('', '404');
509
		}
510
	}
511
512
	/**
513
	 * Returns a directory that points to the root of Elgg, but not necessarily
514
	 * the install root. See `self::root()` for that.
515
	 *
516
	 * @return Directory
517
	 */
518
	public static function elggDir() {
519
		return Local::elggRoot();
520
	}
521
522
	/**
523
	 * Returns a directory that points to the project root, where composer is installed.
524
	 *
525
	 * @return Directory
526
	 */
527
	public static function projectDir() {
528
		return Local::projectRoot();
529
	}
530
531
	/**
532
	 * Renders a web UI for installing Elgg.
533
	 *
534
	 * @return void
535
	 */
536
	public static function install() {
537
		ini_set('display_errors', 1);
538
		$installer = new \ElggInstaller();
539
		$installer->run();
540
	}
541
542
	/**
543
	 * Elgg upgrade script.
544
	 *
545
	 * This script triggers any necessary upgrades. If the site has been upgraded
546
	 * to the most recent version of the code, no upgrades are run but the caches
547
	 * are flushed.
548
	 *
549
	 * Upgrades use a table {db_prefix}upgrade_lock as a mutex to prevent concurrent upgrades.
550
	 *
551
	 * The URL to forward to after upgrades are complete can be specified by setting $_GET['forward']
552
	 * to a relative URL.
553
	 *
554
	 * @return void
555
	 */
556
	public static function upgrade() {
557
		// we want to know if an error occurs
558
		ini_set('display_errors', 1);
559
		$is_cli = (php_sapi_name() === 'cli');
560
561
		$forward = function ($url) use ($is_cli) {
562
			if ($is_cli) {
563
				echo "Open $url in your browser to continue.";
564
				exit;
565
			}
566
567
			forward($url);
568
		};
569
570
		define('UPGRADING', 'upgrading');
571
572
		self::start();
573
		
574
		// check security settings
575
		if (!$is_cli && _elgg_config()->security_protect_upgrade && !elgg_is_admin_logged_in()) {
576
			// only admin's or users with a valid token can run upgrade.php
577
			elgg_signed_request_gatekeeper();
578
		}
579
		
580
		$site_url = _elgg_config()->url;
581
		$site_host = parse_url($site_url, PHP_URL_HOST) . '/';
582
583
		// turn any full in-site URLs into absolute paths
584
		$forward_url = get_input('forward', '/admin', false);
585
		$forward_url = str_replace([$site_url, $site_host], '/', $forward_url);
586
587
		if (strpos($forward_url, '/') !== 0) {
588
			$forward_url = '/' . $forward_url;
589
		}
590
591
		if ($is_cli || (get_input('upgrade') == 'upgrade')) {
592
			$upgrader = _elgg_services()->upgrades;
593
			$result = $upgrader->run();
594
595
			if ($result['failure'] == true) {
596
				register_error($result['reason']);
597
				$forward($forward_url);
598
			}
599
600
			// Find unprocessed batch upgrade classes and save them as ElggUpgrade objects
601
			$core_upgrades = (require self::elggDir()->getPath('engine/lib/upgrades/async-upgrades.php'));
602 196
			$has_pending_upgrades = _elgg_services()->upgradeLocator->run($core_upgrades);
603 196
604 196
			if ($has_pending_upgrades) {
605
				// Forward to the list of pending upgrades
606
				$forward_url = '/admin/upgrades';
607
			}
608
		} else {
609
			$rewriteTester = new \ElggRewriteTester();
610 1
			$url = elgg_get_site_url() . "__testing_rewrite?__testing_rewrite=1";
611 1
			if (!$rewriteTester->runRewriteTest($url)) {
612
				// see if there is a problem accessing the site at all
613
				// due to ip restrictions for example
614
				if (!$rewriteTester->runLocalhostAccessTest()) {
615
					// note: translation may not be available until after upgrade
616
					$msg = elgg_echo("installation:htaccess:localhost:connectionfailed");
617
					if ($msg === "installation:htaccess:localhost:connectionfailed") {
618
						$msg = "Elgg cannot connect to itself to test rewrite rules properly. Check "
619
								. "that curl is working and there are no IP restrictions preventing "
620
								. "localhost connections.";
621
					}
622
					echo $msg;
623
					exit;
624
				}
625
626
				// note: translation may not be available until after upgrade
627
				$msg = elgg_echo("installation:htaccess:needs_upgrade");
628
				if ($msg === "installation:htaccess:needs_upgrade") {
629
					$msg = "You must update your .htaccess file (use install/config/htaccess.dist as a guide).";
630
				}
631
				echo $msg;
632
				exit;
633
			}
634
635
			$vars = [
636
				'forward' => $forward_url
637
			];
638
639
			// reset cache to have latest translations available during upgrade
640
			elgg_reset_system_cache();
641
642
			echo elgg_view_page(elgg_echo('upgrading'), '', 'upgrade', $vars);
643
			exit;
644
		}
645
646
		$forward($forward_url);
647
	}
648
649
	/**
650
	 * Allow plugins to rewrite the path.
651
	 *
652
	 * @return void
653
	 */
654
	private function allowPathRewrite() {
655
		$request = $this->services->request;
656
		$new = $this->services->router->allowRewrite($request);
657
		if ($new === $request) {
658
			return;
659
		}
660
661
		$this->services->setValue('request', $new);
662
		$this->services->context->initialize($new);
663
	}
664
665
	/**
666
	 * Flag this application as running for testing (PHPUnit)
667
	 *
668
	 * @param bool $testing Is testing application
669
	 * @return void
670
	 */
671
	public static function setTestingApplication($testing = true) {
672
		self::$testing_app = $testing;
673
	}
674
675
	/**
676
	 * Checks if the application is running in PHPUnit
677
	 * @return bool
678
	 */
679
	public static function isTestingApplication() {
680
		return (bool) self::$testing_app;
681
	}
682
683
	/**
684
	 * Intercepts, logs, and displays uncaught exceptions.
685
	 *
686
	 * To use a viewtype other than failsafe, create the views:
687
	 *  <viewtype>/messages/exceptions/admin_exception
688
	 *  <viewtype>/messages/exceptions/exception
689
	 * See the json viewtype for an example.
690
	 *
691
	 * @warning This function should never be called directly.
692
	 *
693
	 * @see http://www.php.net/set-exception-handler
694
	 *
695
	 * @param \Exception|\Error $exception The exception/error being handled
696
	 *
697
	 * @return void
698
	 * @access private
699
	 */
700
	public function handleExceptions($exception) {
701
		$timestamp = time();
702
		error_log("Exception at time $timestamp: $exception");
703
704
		// Wipe any existing output buffer
705
		ob_end_clean();
706
707
		// make sure the error isn't cached
708
		header("Cache-Control: no-cache, must-revalidate", true);
709
		header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true);
710
711
		if ($exception instanceof \InstallationException) {
712
			forward('/install.php');
713
		}
714
715
		if (!self::$core_loaded) {
716
			http_response_code(500);
717
			echo "Exception loading Elgg core. Check log at time $timestamp";
718
			return;
719
		}
720
721
		try {
722
			// allow custom scripts to trigger on exception
723
			// value in settings.php should be a system path to a file to include
724
			$exception_include = $this->services->config->exception_include;
725
726
			if ($exception_include && is_file($exception_include)) {
727
				ob_start();
728
729
				// don't isolate, these scripts may use the local $exception var.
730
				include $exception_include;
731
732
				$exception_output = ob_get_clean();
733
734
				// if content is returned from the custom handler we will output
735
				// that instead of our default failsafe view
736
				if (!empty($exception_output)) {
737
					echo $exception_output;
738
					exit;
739
				}
740
			}
741
742
			if (elgg_is_xhr()) {
743
				elgg_set_viewtype('json');
744
				$response = new \Symfony\Component\HttpFoundation\JsonResponse(null, 500);
745
			} else {
746
				elgg_set_viewtype('failsafe');
747
				$response = new \Symfony\Component\HttpFoundation\Response('', 500);
748
			}
749
750
			if (elgg_is_admin_logged_in()) {
751
				$body = elgg_view("messages/exceptions/admin_exception", [
752
					'object' => $exception,
753
					'ts' => $timestamp
754
				]);
755
			} else {
756
				$body = elgg_view("messages/exceptions/exception", [
757
					'object' => $exception,
758
					'ts' => $timestamp
759
				]);
760
			}
761
762
			$response->setContent(elgg_view_page(elgg_echo('exception:title'), $body));
763
			$response->send();
764
		} catch (\Exception $e) {
765
			$timestamp = time();
766
			$message = $e->getMessage();
767
			http_response_code(500);
768
			echo "Fatal error in exception handler. Check log for Exception at time $timestamp";
769
			error_log("Exception at time $timestamp : fatal error in exception handler : $message");
770
		}
771
	}
772
773
	/**
774
	 * Intercepts catchable PHP errors.
775
	 *
776
	 * @warning This function should never be called directly.
777
	 *
778
	 * @internal
779
	 * For catchable fatal errors, throws an Exception with the error.
780
	 *
781
	 * For non-fatal errors, depending upon the debug settings, either
782
	 * log the error or ignore it.
783
	 *
784
	 * @see http://www.php.net/set-error-handler
785
	 *
786
	 * @param int    $errno    The level of the error raised
787
	 * @param string $errmsg   The error message
788
	 * @param string $filename The filename the error was raised in
789
	 * @param int    $linenum  The line number the error was raised at
790
	 * @param array  $vars     An array that points to the active symbol table where error occurred
791
	 *
792
	 * @return true
793
	 * @throws \Exception
794
	 * @access private
795
	 */
796
	public function handleErrors($errno, $errmsg, $filename, $linenum, $vars) {
0 ignored issues
show
Unused Code introduced by
The parameter $vars is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Coding Style introduced by
handleErrors uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
797
		$error = date("Y-m-d H:i:s (T)") . ": \"$errmsg\" in file $filename (line $linenum)";
798
799
		$log = function ($message, $level) {
800
			if (self::$core_loaded) {
801
				return elgg_log($message, $level);
802
			}
803
804
			return false;
805
		};
806
807
		switch ($errno) {
808
			case E_USER_ERROR:
809
				if (!$log("PHP: $error", 'ERROR')) {
810
					error_log("PHP ERROR: $error");
811
				}
812
				if (self::$core_loaded) {
813
					register_error("ERROR: $error");
814
				}
815
816
				// Since this is a fatal error, we want to stop any further execution but do so gracefully.
817
				throw new \Exception($error);
818
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
819
820
			case E_WARNING :
821
			case E_USER_WARNING :
822
			case E_RECOVERABLE_ERROR: // (e.g. type hint violation)
823
824
				// check if the error wasn't suppressed by the error control operator (@)
825
				if (error_reporting() && !$log("PHP: $error", 'WARNING')) {
826
					error_log("PHP WARNING: $error");
827
				}
828
				break;
829
830
			default:
831
				if (function_exists('_elgg_config')) {
832
					$debug = _elgg_config()->debug;
833
				} else {
834
					$debug = isset($GLOBALS['CONFIG']->debug) ? $GLOBALS['CONFIG']->debug : null;
835
				}
836
				if ($debug !== 'NOTICE') {
837
					return true;
838
				}
839
840
				if (!$log("PHP (errno $errno): $error", 'NOTICE')) {
841
					error_log("PHP NOTICE: $error");
842
				}
843
		}
844
845
		return true;
846
	}
847
848
	/**
849
	 * Does nothing.
850
	 *
851
	 * @return void
852
	 * @deprecated
853
	 */
854
	public function loadSettings() {
855
		trigger_error(__METHOD__ . ' is no longer needed and will be removed.');
856
	}
857
858
	/**
859
	 * Get all engine/lib library filenames
860
	 *
861
	 * @note We can't just pull in all directory files because some users leave old files in place.
862
	 *
863
	 * @return string[]
864
	 */
865
	private static function getEngineLibs() {
866
		return [
867
			'access.php',
868
			'actions.php',
869
			'admin.php',
870
			'annotations.php',
871
			'autoloader.php',
872
			'cache.php',
873
			'comments.php',
874
			'configuration.php',
875
			'constants.php',
876
			'cron.php',
877
			'database.php',
878
			'deprecated-3.0.php',
879
			'elgglib.php',
880
			'entities.php',
881
			'filestore.php',
882
			'group.php',
883
			'input.php',
884
			'languages.php',
885
			'mb_wrapper.php',
886
			'memcache.php',
887
			'metadata.php',
888
			'metastrings.php',
889
			'navigation.php',
890
			'notification.php',
891
			'output.php',
892
			'pagehandler.php',
893
			'pageowner.php',
894
			'pam.php',
895
			'plugins.php',
896
			'private_settings.php',
897
			'relationships.php',
898
			'river.php',
899
			'sessions.php',
900
			'statistics.php',
901
			'system_log.php',
902
			'tags.php',
903
			'upgrade.php',
904
			'user_settings.php',
905
			'users.php',
906
			'views.php',
907
			'widgets.php',
908
		];
909
	}
910
}
911