Elgg  Version 6.3
Application.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
21 use Elgg\Http\Request as HttpRequest;
26 use Elgg\Traits\Loggable;
28 use Symfony\Component\Console\Input\ArgvInput;
29 use Symfony\Component\Console\Input\ArrayInput;
30 use Symfony\Component\Console\Input\InputInterface;
31 use Symfony\Component\Console\Output\ConsoleOutput;
32 use Symfony\Component\Console\Output\NullOutput;
33 use Symfony\Component\Console\Output\OutputInterface;
34 use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirect;
35 use Symfony\Component\HttpFoundation\Response;
36 
48 class Application {
49 
50  use Loggable;
51 
58 
65 
72  public static $_instance;
73 
79  protected $boot_status = [
80  'application_boot_completed' => false,
81  'full_boot_completed' => false,
82  'plugins_boot_completed' => false,
83  'service_boot_completed' => false,
84  ];
85 
91  public static function getInstance() {
92  if (self::$_instance === null) {
93  self::$_instance = self::factory();
94  self::setGlobalConfig(self::$_instance);
95  }
96 
97  return self::$_instance;
98  }
99 
107  public static function setInstance(?Application $application = null) {
108  self::$_instance = $application;
109  }
110 
118  public function __construct(InternalContainer $internal_services) {
119  $this->internal_services = $internal_services;
120  $this->public_services = PublicContainer::factory();
121  }
122 
134  public static function loadCore() {
135  $path = Paths::elgg() . 'engine/lib';
136 
137  // include library files, capturing setup functions
138  foreach (self::getEngineLibs() as $file) {
139  try {
140  Includer::requireFileOnce("$path/$file");
141  } catch (\Error $e) {
142  throw new InstallationException("Elgg lib file failed include: $path/$file");
143  }
144  }
145  }
146 
152  public static function start() {
153  $app = self::getInstance();
154  $app->bootCore();
155 
156  return $app;
157  }
158 
164  public static function isCoreLoaded() {
165  return function_exists('elgg');
166  }
167 
187  public function bootCore() {
188  $boot = new BootHandler($this);
189  $boot();
190  }
191 
200  public function getBootStatus(string $type): bool {
201  return $this->boot_status[$type] ?? false;
202  }
203 
213  public function setBootStatus(string $type, bool $status): void {
214  $this->boot_status[$type] = $status;
215  }
216 
226  public function getDb() {
227  return $this->internal_services->publicDb;
228  }
229 
237  public static function setGlobalConfig(Application $application) {
238  global $CONFIG;
239  $CONFIG = $application->internal_services->config;
240  }
241 
255  public static function factory(array $spec = []) {
256 
257  $defaults = [
258  'config' => null,
259  'handle_exceptions' => true,
260  'handle_shutdown' => true,
261  'request' => null,
262  'internal_services' => null,
263  'set_start_time' => true,
264  'settings_path' => '',
265  ];
266  $spec = array_merge($defaults, $spec);
267 
268  if ($spec['set_start_time']) {
269  // The time with microseconds when the Elgg engine was started.
270  if (!isset($GLOBALS['START_MICROTIME'])) {
271  $GLOBALS['START_MICROTIME'] = microtime(true);
272  }
273  }
274 
275  if ($spec['handle_exceptions']) {
276  set_error_handler(new ErrorHandler());
277  set_exception_handler(new ExceptionHandler());
278  }
279 
280  self::loadCore();
281 
282  if (!$spec['internal_services']) {
283  if (!$spec['config']) {
284  $spec['config'] = Config::factory($spec['settings_path']);
285  }
286 
287  $spec['internal_services'] = InternalContainer::factory(['config' => $spec['config']]);
288  }
289 
290  if ($spec['request']) {
291  if (!$spec['request'] instanceof HttpRequest) {
292  throw new InvalidArgumentException('Given request is not a ' . HttpRequest::class);
293  }
294 
295  $spec['request']->initializeTrustedProxyConfiguration($spec['internal_services']->config);
296  $spec['request']->correctBaseURL($spec['internal_services']->config);
297  $spec['internal_services']->set('request', $spec['request']);
298  }
299 
300  $app = new self($spec['internal_services']);
301 
302  if ($spec['handle_shutdown']) {
303  register_shutdown_function(new ShutdownHandler($app));
304  }
305 
306  return $app;
307  }
308 
316  public static function route(HttpRequest $request) {
317  self::loadCore();
318 
319  if ($request->isRewriteCheck()) {
320  $response = new OkResponse(HttpRequest::REWRITE_TEST_OUTPUT);
321  return self::respond($response);
322  }
323 
324  if (self::$_instance) {
325  $app = self::$_instance;
326  $app->internal_services->set('request', $request);
327  } else {
328  try {
329  $app = self::factory([
330  'request' => $request,
331  ]);
332 
333  self::setGlobalConfig($app);
334  self::setInstance($app);
335  } catch (ConfigurationException $ex) {
336  return self::install();
337  }
338  }
339 
340  return $app->run();
341  }
342 
353  public static function respond(ResponseBuilder $builder): ?Response {
354  if (self::$_instance) {
355  self::$_instance->internal_services->responseFactory->respond($builder);
356 
357  return self::$_instance->internal_services->responseFactory->getSentResponse();
358  }
359 
360  try {
361  $content = $builder->getContent();
362  $status = $builder->getStatusCode();
363  $headers = $builder->getHeaders();
364 
365  if ($builder->isRedirection()) {
366  $forward_url = $builder->getForwardURL();
367  $response = new SymfonyRedirect($forward_url, $status, $headers);
368  } else {
369  $response = new Response($content, $status, $headers);
370  }
371  } catch (\Exception $ex) {
372  $response = new Response($ex->getMessage(), 500);
373  }
374 
375  $response->headers->set('Pragma', 'public');
376  $response->headers->set('Cache-Control', 'no-store, must-revalidate');
377  $response->headers->set('Expires', 'Fri, 05 Feb 1982 00:00:00 -0500');
378 
379  self::getResponseTransport()->send($response);
380 
381  return $response;
382  }
383 
389  public static function index() {
390  return self::route(self::getRequest());
391  }
392 
398  public function run() {
399  $config = $this->internal_services->config;
400  $request = $this->internal_services->request;
401 
402  try {
403  if ($request->isCliServer()) {
404  if ($request->isCliServable(Paths::project())) {
405  return false;
406  }
407 
408  // overwrite value from settings
409  $www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
410  $config->wwwroot = $www_root;
411  $config->wwwroot_cli_server = $www_root;
412  }
413 
414  if (str_starts_with($request->getElggPath(), '/cache/')) {
415  $config->_disable_session_save = true;
416  $response = $this->internal_services->cacheHandler->handleRequest($request, $this)->prepare($request);
417  self::getResponseTransport()->send($response);
418 
419  return $response;
420  }
421 
422  if ($request->getElggPath() === '/refresh_token') {
423  $config->_disable_session_save = true;
424  $token = new \Elgg\Controllers\RefreshCsrfToken();
426  self::getResponseTransport()->send($response);
427 
428  return $response;
429  }
430 
431  if (str_starts_with($request->getElggPath(), '/serve-file/')) {
432  $config->_disable_session_save = true;
433  $response = $this->internal_services->serveFileHandler->getResponse($request);
434  self::getResponseTransport()->send($response);
435 
436  return $response;
437  }
438 
439  if ($this->isCli()) {
440  $config->_disable_session_save = true;
441  }
442 
443  $this->bootCore();
444 
445  // re-fetch new request from services in case it was replaced by route:rewrite
446  $request = $this->internal_services->request;
447 
448  if (!$this->internal_services->router->route($request)) {
449  throw new PageNotFoundException();
450  }
451  } catch (HttpException $ex) {
452  $forward_url = $ex->getRedirectUrl();
453  if (!$forward_url) {
454  if ($ex instanceof GatekeeperException) {
456  } else if ($request->getFirstUrlSegment() == 'action') {
458  }
459  }
460 
461  $forward_url = (string) $this->internal_services->events->triggerResults('forward', $ex->getCode(), ['exception' => $ex], $forward_url);
462 
463  if ($forward_url && !$request->isXmlHttpRequest()) {
464  if ($ex->getMessage()) {
465  $this->internal_services->system_messages->addErrorMessage($ex->getMessage());
466  }
467 
469  } else {
470  $response = new ErrorResponse($ex->getMessage(), $ex->getCode(), $forward_url);
471  }
472 
473  $response->setException($ex);
474 
475  self::respond($response);
476  }
477 
478  return $this->internal_services->responseFactory->getSentResponse();
479  }
480 
486  public static function install() {
487  ini_set('display_errors', 1);
488 
489  try {
490  $installer = new \ElggInstaller();
491  $response = $installer->run();
492  } catch (\Exception $ex) {
493  $response = new ErrorResponse($ex->getMessage(), 500);
494  }
495 
496  return self::respond($response);
497  }
498 
513  public static function upgrade() {
514 
515  try {
516  self::migrate();
517  self::start();
518 
519  $request = self::$_instance->internal_services->request;
520  $signer = self::$_instance->internal_services->urlSigner;
521 
522  $url = $request->getCurrentURL();
523  $query = $request->getParams();
524 
525  // We need to resign the URL because the path is different
526  $mac = elgg_extract(UrlSigner::KEY_MAC, $query);
527  if (isset($mac) && !$signer->isValid($url)) {
528  throw new HttpException(elgg_echo('invalid_request_signature'), ELGG_HTTP_FORBIDDEN);
529  }
530 
531  unset($query[UrlSigner::KEY_MAC]);
532 
533  $base_url = elgg_normalize_site_url('upgrade/init');
535 
536  if (isset($mac)) {
537  $url = self::$_instance->internal_services->urlSigner->sign($url);
538  }
539 
541  } catch (\Exception $ex) {
542  $response = new ErrorResponse($ex->getMessage(), $ex->getCode() ?: ELGG_HTTP_INTERNAL_SERVER_ERROR);
543  }
544 
545  return self::respond($response);
546  }
547 
554  public static function migrate() {
555 
556  $constants = Paths::elgg() . 'engine/lib/constants.php';
557  Includer::requireFileOnce($constants);
558 
559  $conf = Paths::elgg() . 'engine/schema/migrations.php';
560  if (!$conf) {
561  throw new InstallationException('Settings file is required to run database migrations.');
562  }
563 
564  // setting timeout because some database migrations can take a long time
565  set_time_limit(0);
566 
567  $app = new \Phinx\Console\PhinxApplication();
568  $wrapper = new PhinxWrapper($app, [
569  'configuration' => $conf,
570  ]);
571  $wrapper->getMigrate();
572 
573  return empty($wrapper->getExitCode());
574  }
575 
581  public static function getMigrationSettings() {
582 
583  $config = Config::factory();
584  $db_config = DbConfig::fromElggConfig($config);
585 
586  if ($db_config->isDatabaseSplit()) {
587  $conn = $db_config->getConnectionConfig(DbConfig::WRITE);
588  } else {
589  $conn = $db_config->getConnectionConfig();
590  }
591 
592  return [
593  'paths' => [
594  'migrations' => Paths::elgg() . 'engine/schema/migrations/',
595  ],
596  'environments' => [
597  'default_migration_table' => "{$conn['prefix']}migrations",
598  'default_environment' => 'prod',
599  'prod' => [
600  'adapter' => 'mysql',
601  'host' => $conn['host'],
602  'port' => $conn['port'],
603  'name' => $conn['database'],
604  'user' => $conn['user'],
605  'pass' => $conn['password'],
606  'charset' => $conn['encoding'],
607  'table_prefix' => $conn['prefix'],
608  ],
609  ],
610  ];
611  }
612 
619  public function allowPathRewrite() {
620  $request = $this->internal_services->request;
621  $new = $this->internal_services->router->allowRewrite($request);
622  if ($new === $request) {
623  return;
624  }
625 
626  $this->internal_services->set('request', $new);
627  }
628 
634  public static function isCli() {
635  switch (PHP_SAPI) {
636  case 'cli':
637  case 'phpdbg':
638  return true;
639 
640  default:
641  return false;
642  }
643  }
644 
650  public static function getRequest() {
651  if (self::$_instance) {
652  return self::$_instance->internal_services->request;
653  }
654 
655  return HttpRequest::createFromGlobals();
656  }
657 
663  public static function getStdIn() {
664  if (self::isCli()) {
665  $request = self::getRequest();
666  $argv = $request->server->get('argv') ?: [];
667  return new ArgvInput($argv);
668  }
669 
670  return new ArrayInput([]);
671  }
672 
678  public static function getStdOut() {
679  if (self::isCli()) {
680  return new ConsoleOutput();
681  } else {
682  return new NullOutput();
683  }
684  }
685 
691  public static function getStdErr() {
692  $std_out = self::getStdOut();
693  if (is_callable([$std_out, 'getErrorOutput'])) {
694  return $std_out->getErrorOutput();
695  }
696 
697  return $std_out;
698  }
699 
705  public static function getResponseTransport() {
706  if (self::isCli()) {
707  return new \Elgg\Http\OutputBufferTransport();
708  }
709 
710  return new \Elgg\Http\HttpProtocolTransport();
711  }
712 
720  private static function getEngineLibs() {
721  return [
722  'elgglib.php',
723  'events.php',
724  'access.php',
725  'actions.php',
726  'admin.php',
727  'annotations.php',
728  'breadcrumbs.php',
729  'cache.php',
730  'configuration.php',
731  'constants.php',
732  'context.php',
733  'deprecated-6.1.php',
734  'deprecated-6.3.php',
735  'entities.php',
736  'external_files.php',
737  'filestore.php',
738  'gatekeepers.php',
739  'input.php',
740  'languages.php',
741  'mb_wrapper.php',
742  'metadata.php',
743  'navigation.php',
744  'notification.php',
745  'output.php',
746  'pagehandler.php',
747  'pageowner.php',
748  'pam.php',
749  'plugins.php',
750  'relationships.php',
751  'river.php',
752  'sessions.php',
753  'users.php',
754  'views.php',
755  'widgets.php',
756  ];
757  }
758 }
$content
Set robots.txt action.
Definition: set_robots.php:6
if(! $entity->delete(true, true)) $forward_url
Definition: delete.php:30
$type
Definition: delete.php:21
return[ 'admin/delete_admin_notices'=>['access'=> 'admin'], 'admin/menu/save'=>['access'=> 'admin'], 'admin/plugins/activate'=>['access'=> 'admin'], 'admin/plugins/activate_all'=>['access'=> 'admin'], 'admin/plugins/deactivate'=>['access'=> 'admin'], 'admin/plugins/deactivate_all'=>['access'=> 'admin'], 'admin/plugins/set_priority'=>['access'=> 'admin'], 'admin/security/security_txt'=>['access'=> 'admin'], 'admin/security/settings'=>['access'=> 'admin'], 'admin/security/regenerate_site_secret'=>['access'=> 'admin'], 'admin/site/cache/invalidate'=>['access'=> 'admin'], 'admin/site/flush_cache'=>['access'=> 'admin'], 'admin/site/icons'=>['access'=> 'admin'], 'admin/site/set_maintenance_mode'=>['access'=> 'admin'], 'admin/site/set_robots'=>['access'=> 'admin'], 'admin/site/theme'=>['access'=> 'admin'], 'admin/site/unlock_upgrade'=>['access'=> 'admin'], 'admin/site/settings'=>['access'=> 'admin'], 'admin/upgrade'=>['access'=> 'admin'], 'admin/upgrade/reset'=>['access'=> 'admin'], 'admin/user/ban'=>['access'=> 'admin'], 'admin/user/bulk/ban'=>['access'=> 'admin'], 'admin/user/bulk/delete'=>['access'=> 'admin'], 'admin/user/bulk/unban'=>['access'=> 'admin'], 'admin/user/bulk/validate'=>['access'=> 'admin'], 'admin/user/change_email'=>['access'=> 'admin'], 'admin/user/delete'=>['access'=> 'admin'], 'admin/user/login_as'=>['access'=> 'admin'], 'admin/user/logout_as'=>[], 'admin/user/makeadmin'=>['access'=> 'admin'], 'admin/user/resetpassword'=>['access'=> 'admin'], 'admin/user/removeadmin'=>['access'=> 'admin'], 'admin/user/unban'=>['access'=> 'admin'], 'admin/user/validate'=>['access'=> 'admin'], 'annotation/delete'=>[], 'avatar/upload'=>[], 'comment/save'=>[], 'diagnostics/download'=>['access'=> 'admin'], 'entity/chooserestoredestination'=>[], 'entity/delete'=>[], 'entity/mute'=>[], 'entity/restore'=>[], 'entity/subscribe'=>[], 'entity/trash'=>[], 'entity/unmute'=>[], 'entity/unsubscribe'=>[], 'login'=>['access'=> 'logged_out'], 'logout'=>[], 'notifications/mute'=>['access'=> 'public'], 'plugins/settings/remove'=>['access'=> 'admin'], 'plugins/settings/save'=>['access'=> 'admin'], 'plugins/usersettings/save'=>[], 'register'=>['access'=> 'logged_out', 'middleware'=>[\Elgg\Router\Middleware\RegistrationAllowedGatekeeper::class,],], 'river/delete'=>[], 'settings/notifications'=>[], 'settings/notifications/subscriptions'=>[], 'user/changepassword'=>['access'=> 'public'], 'user/requestnewpassword'=>['access'=> 'public'], 'useradd'=>['access'=> 'admin'], 'usersettings/save'=>[], 'widgets/add'=>[], 'widgets/delete'=>[], 'widgets/move'=>[], 'widgets/save'=>[],]
Definition: actions.php:73
$query
Handles application boot sequence.
Definition: BootHandler.php:12
Handle system and PHP errors.
Handler for uncaught exceptions.
Load, boot, and implement a front controller for an Elgg application.
Definition: Application.php:48
static isCoreLoaded()
Are Elgg's global functions loaded?
bootCore()
Bootstrap the Elgg engine, loads plugins, and calls initial system events.
static upgrade()
Elgg upgrade script.
static index()
Elgg's front controller.
static getStdErr()
Load console error output interface.
static migrate()
Runs database migrations.
static getStdOut()
Load console output interface.
static respond(ResponseBuilder $builder)
Build and send a response.
static getMigrationSettings()
Returns configuration array for database migrations.
static getResponseTransport()
Build a transport for sending responses.
static setInstance(?Application $application=null)
Set the global Application instance.
static factory(array $spec=[])
Create a new application.
static getInstance()
Get the global Application instance.
Definition: Application.php:91
static getRequest()
Build request object.
run()
Routes the request, booting core if not yet booted.
setBootStatus(string $type, bool $status)
Sets the boot status.
static start()
Start and boot the core.
static $_instance
Reference to the loaded Application.
Definition: Application.php:72
allowPathRewrite()
Allow plugins to rewrite the path.
static getStdIn()
Load console input interface.
getBootStatus(string $type)
Retrieve the boot status of the application.
getDb()
Get a Database wrapper for performing queries without booting Elgg.
static isCli()
Is application running in CLI.
static install()
Renders a web UI for installing Elgg.
static route(HttpRequest $request)
Route a request.
__construct(InternalContainer $internal_services)
Constructor.
static loadCore()
Define all Elgg global functions and constants, wire up boot events, but don't boot.
static setGlobalConfig(Application $application)
Make the global $CONFIG a reference to this application's config service.
Database configuration service.
Definition: DbConfig.php:13
A generic parent class for Configuration exceptions.
Thrown when there is a major problem with the installation.
Generic HTTP exception.
getRedirectUrl()
Get preferred redirect URL.
Thrown when one of the gatekeepers prevents access.
Thrown when page is not accessible.
Exception thrown if an argument is not of the expected type.
Error response builder.
OK response builder.
Definition: OkResponse.php:8
Redirect response builder.
Elgg HTTP request.
Definition: Request.php:17
Find Elgg and project paths.
Definition: Paths.php:8
Component for creating signed URLs.
Definition: UrlSigner.php:13
Elgg implementation of the Phinx Text Wrapper to ensure the correct OutputInterface.
const ELGG_HTTP_INTERNAL_SERVER_ERROR
Definition: constants.php:91
const ELGG_HTTP_PERMANENTLY_REDIRECT
Definition: constants.php:63
const ELGG_HTTP_FORBIDDEN
Definition: constants.php:67
const REFERRER
Used in calls to forward() to specify the browser should be redirected to the referring page.
Definition: constants.php:37
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
if(! $pagination && $limit !==false &&!empty($items) &&count($items) >=$limit) $base_url
Definition: list.php:114
elgg()
Bootstrapping and helper procedural code available for use in Elgg core and plugins.
Definition: elgglib.php:12
elgg_http_add_url_query_elements(string $url, array $elements)
Sets elements in a URL's query string.
Definition: elgglib.php:167
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:240
elgg_get_login_url(array $query=[], string $fragment='')
Returns site's login URL Triggers a 'login_url', 'site' event that can be used by plugins to alter th...
Definition: users.php:228
$defaults
Generic entity header upload helper.
Definition: header.php:6
HTTP response builder interface.
HTTP response transport interface.
elgg_echo(string $message_key, array $args=[], string $language='')
Elgg language module Functions to manage language and translations.
Definition: languages.php:17
$request
Definition: livesearch.php:12
string project
Definition: conf.py:52
$headers
Definition: section.php:21
$mac
Definition: contents.php:14
$path
Definition: details.php:70
elgg_normalize_site_url(string $unsafe_url)
From untrusted input, get a site URL safe for forwarding.
Definition: output.php:175
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10
$token
elgg_is_logged_in()
Returns whether or not the user is currently logged in.
Definition: sessions.php:43
global $CONFIG
$response
Definition: content.php:10