Elgg  Version master
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;
27 use Symfony\Component\Console\Input\ArgvInput;
28 use Symfony\Component\Console\Input\ArrayInput;
29 use Symfony\Component\Console\Input\InputInterface;
30 use Symfony\Component\Console\Output\ConsoleOutput;
31 use Symfony\Component\Console\Output\NullOutput;
32 use Symfony\Component\Console\Output\OutputInterface;
33 use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirect;
34 use Symfony\Component\HttpFoundation\Response;
35 
47 class Application {
48 
49  use Loggable;
50 
57 
64 
71  public static $_instance;
72 
78  protected $boot_status = [
79  'application_boot_completed' => false,
80  'full_boot_completed' => false,
81  'plugins_boot_completed' => false,
82  'service_boot_completed' => false,
83  ];
84 
90  public static function getInstance() {
91  if (self::$_instance === null) {
92  self::$_instance = self::factory();
93  self::setGlobalConfig(self::$_instance);
94  }
95 
96  return self::$_instance;
97  }
98 
106  public static function setInstance(?Application $application = null) {
107  self::$_instance = $application;
108  }
109 
117  public function __construct(InternalContainer $internal_services) {
118  $this->internal_services = $internal_services;
119  $this->public_services = PublicContainer::factory();
120  }
121 
133  public static function loadCore() {
134  $path = Paths::elgg() . 'engine/lib';
135 
136  // include library files, capturing setup functions
137  foreach (self::getEngineLibs() as $file) {
138  try {
139  Includer::requireFileOnce("$path/$file");
140  } catch (\Error $e) {
141  throw new InstallationException("Elgg lib file failed include: $path/$file");
142  }
143  }
144  }
145 
151  public static function start() {
152  $app = self::getInstance();
153  $app->bootCore();
154 
155  return $app;
156  }
157 
163  public static function isCoreLoaded() {
164  return function_exists('elgg');
165  }
166 
186  public function bootCore() {
187  $boot = new BootHandler($this);
188  $boot();
189  }
190 
199  public function getBootStatus(string $type): bool {
200  return $this->boot_status[$type] ?? false;
201  }
202 
212  public function setBootStatus(string $type, bool $status): void {
213  $this->boot_status[$type] = $status;
214  }
215 
225  public function getDb() {
226  return $this->internal_services->publicDb;
227  }
228 
236  public static function setGlobalConfig(Application $application) {
237  global $CONFIG;
238  $CONFIG = $application->internal_services->config;
239  }
240 
254  public static function factory(array $spec = []) {
255 
256  $defaults = [
257  'config' => null,
258  'handle_exceptions' => true,
259  'handle_shutdown' => true,
260  'request' => null,
261  'internal_services' => null,
262  'set_start_time' => true,
263  'settings_path' => '',
264  ];
265  $spec = array_merge($defaults, $spec);
266 
267  if ($spec['set_start_time']) {
268  // The time with microseconds when the Elgg engine was started.
269  if (!isset($GLOBALS['START_MICROTIME'])) {
270  $GLOBALS['START_MICROTIME'] = microtime(true);
271  }
272  }
273 
274  if ($spec['handle_exceptions']) {
275  set_error_handler(new ErrorHandler());
276  set_exception_handler(new ExceptionHandler());
277  }
278 
279  self::loadCore();
280 
281  if (!$spec['internal_services']) {
282  if (!$spec['config']) {
283  $spec['config'] = Config::factory($spec['settings_path']);
284  }
285 
286  $spec['internal_services'] = InternalContainer::factory(['config' => $spec['config']]);
287  }
288 
289  if ($spec['request']) {
290  if (!$spec['request'] instanceof HttpRequest) {
291  throw new InvalidArgumentException('Given request is not a ' . HttpRequest::class);
292  }
293 
294  $spec['request']->initializeTrustedProxyConfiguration($spec['internal_services']->config);
295  $spec['request']->correctBaseURL($spec['internal_services']->config);
296  $spec['internal_services']->set('request', $spec['request']);
297  }
298 
299  $app = new self($spec['internal_services']);
300 
301  if ($spec['handle_shutdown']) {
302  register_shutdown_function(new ShutdownHandler($app));
303  }
304 
305  return $app;
306  }
307 
315  public static function route(HttpRequest $request) {
316  self::loadCore();
317 
318  if ($request->isRewriteCheck()) {
319  $response = new OkResponse(HttpRequest::REWRITE_TEST_OUTPUT);
320  return self::respond($response);
321  }
322 
323  if (self::$_instance) {
324  $app = self::$_instance;
325  $app->internal_services->set('request', $request);
326  } else {
327  try {
328  $app = self::factory([
329  'request' => $request,
330  ]);
331 
332  self::setGlobalConfig($app);
333  self::setInstance($app);
334  } catch (ConfigurationException $ex) {
335  return self::install();
336  }
337  }
338 
339  return $app->run();
340  }
341 
352  public static function respond(ResponseBuilder $builder): ?Response {
353  if (self::$_instance) {
354  self::$_instance->internal_services->responseFactory->respond($builder);
355 
356  return self::$_instance->internal_services->responseFactory->getSentResponse();
357  }
358 
359  try {
360  $content = $builder->getContent();
361  $status = $builder->getStatusCode();
362  $headers = $builder->getHeaders();
363 
364  if ($builder->isRedirection()) {
365  $forward_url = $builder->getForwardURL();
366  $response = new SymfonyRedirect($forward_url, $status, $headers);
367  } else {
368  $response = new Response($content, $status, $headers);
369  }
370  } catch (\Exception $ex) {
371  $response = new Response($ex->getMessage(), 500);
372  }
373 
374  $response->headers->set('Pragma', 'public');
375  $response->headers->set('Cache-Control', 'no-store, must-revalidate');
376  $response->headers->set('Expires', 'Fri, 05 Feb 1982 00:00:00 -0500');
377 
378  self::getResponseTransport()->send($response);
379 
380  return $response;
381  }
382 
388  public static function index() {
389  return self::route(self::getRequest());
390  }
391 
397  public function run() {
398  $config = $this->internal_services->config;
399  $request = $this->internal_services->request;
400 
401  try {
402  if ($request->isCliServer()) {
403  if ($request->isCliServable(Paths::project())) {
404  return false;
405  }
406 
407  // overwrite value from settings
408  $www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
409  $config->wwwroot = $www_root;
410  $config->wwwroot_cli_server = $www_root;
411  }
412 
413  if (str_starts_with($request->getElggPath(), '/cache/')) {
414  $config->_disable_session_save = true;
415  $response = $this->internal_services->cacheHandler->handleRequest($request, $this)->prepare($request);
416  self::getResponseTransport()->send($response);
417 
418  return $response;
419  }
420 
421  if ($request->getElggPath() === '/refresh_token') {
422  $config->_disable_session_save = true;
423  $token = new \Elgg\Controllers\RefreshCsrfToken();
425  self::getResponseTransport()->send($response);
426 
427  return $response;
428  }
429 
430  if (str_starts_with($request->getElggPath(), '/serve-file/')) {
431  $config->_disable_session_save = true;
432  $response = $this->internal_services->serveFileHandler->getResponse($request);
433  self::getResponseTransport()->send($response);
434 
435  return $response;
436  }
437 
438  if ($this->isCli()) {
439  $config->_disable_session_save = true;
440  }
441 
442  $this->bootCore();
443 
444  // re-fetch new request from services in case it was replaced by route:rewrite
445  $request = $this->internal_services->request;
446 
447  if (!$this->internal_services->router->route($request)) {
448  throw new PageNotFoundException();
449  }
450  } catch (HttpException $ex) {
451  $forward_url = $ex->getRedirectUrl();
452  if (!$forward_url) {
453  if ($ex instanceof GatekeeperException) {
455  } else if ($request->getFirstUrlSegment() == 'action') {
457  }
458  }
459 
460  $forward_url = (string) $this->internal_services->events->triggerResults('forward', $ex->getCode(), ['exception' => $ex], $forward_url);
461 
462  if ($forward_url && !$request->isXmlHttpRequest()) {
463  if ($ex->getMessage()) {
464  $this->internal_services->system_messages->addErrorMessage($ex->getMessage());
465  }
466 
468  } else {
469  $response = new ErrorResponse($ex->getMessage(), $ex->getCode(), $forward_url);
470  }
471 
472  $response->setException($ex);
473 
474  self::respond($response);
475  }
476 
477  return $this->internal_services->responseFactory->getSentResponse();
478  }
479 
485  public static function install() {
486  ini_set('display_errors', 1);
487 
488  try {
489  $installer = new \ElggInstaller();
490  $response = $installer->run();
491  } catch (\Exception $ex) {
492  $response = new ErrorResponse($ex->getMessage(), 500);
493  }
494 
495  return self::respond($response);
496  }
497 
512  public static function upgrade() {
513 
514  try {
515  self::migrate();
516  self::start();
517 
518  $request = self::$_instance->internal_services->request;
519  $signer = self::$_instance->internal_services->urlSigner;
520 
521  $url = $request->getCurrentURL();
522  $query = $request->getParams();
523 
524  // We need to resign the URL because the path is different
525  $mac = elgg_extract(UrlSigner::KEY_MAC, $query);
526  if (isset($mac) && !$signer->isValid($url)) {
527  throw new HttpException(elgg_echo('invalid_request_signature'), ELGG_HTTP_FORBIDDEN);
528  }
529 
530  unset($query[UrlSigner::KEY_MAC]);
531 
532  $base_url = elgg_normalize_site_url('upgrade/init');
534 
535  if (isset($mac)) {
536  $url = self::$_instance->internal_services->urlSigner->sign($url);
537  }
538 
540  } catch (\Exception $ex) {
541  $response = new ErrorResponse($ex->getMessage(), $ex->getCode() ?: ELGG_HTTP_INTERNAL_SERVER_ERROR);
542  }
543 
544  return self::respond($response);
545  }
546 
553  public static function migrate() {
554 
555  $constants = Paths::elgg() . 'engine/lib/constants.php';
556  Includer::requireFileOnce($constants);
557 
558  $conf = Paths::elgg() . 'engine/schema/migrations.php';
559  if (!$conf) {
560  throw new InstallationException('Settings file is required to run database migrations.');
561  }
562 
563  // setting timeout because some database migrations can take a long time
564  set_time_limit(0);
565 
566  $app = new \Phinx\Console\PhinxApplication();
567  $wrapper = new \Phinx\Wrapper\TextWrapper($app, [
568  'configuration' => $conf,
569  ]);
570  $log = $wrapper->getMigrate();
571 
572  if (!empty($_SERVER['argv']) && in_array('--verbose', $_SERVER['argv'])) {
573  error_log($log);
574  }
575 
576  return true;
577  }
578 
584  public static function getMigrationSettings() {
585 
586  $config = Config::factory();
587  $db_config = DbConfig::fromElggConfig($config);
588 
589  if ($db_config->isDatabaseSplit()) {
590  $conn = $db_config->getConnectionConfig(DbConfig::WRITE);
591  } else {
592  $conn = $db_config->getConnectionConfig();
593  }
594 
595  return [
596  'paths' => [
597  'migrations' => Paths::elgg() . 'engine/schema/migrations/',
598  ],
599  'environments' => [
600  'default_migration_table' => "{$conn['prefix']}migrations",
601  'default_environment' => 'prod',
602  'prod' => [
603  'adapter' => 'mysql',
604  'host' => $conn['host'],
605  'port' => $conn['port'],
606  'name' => $conn['database'],
607  'user' => $conn['user'],
608  'pass' => $conn['password'],
609  'charset' => $conn['encoding'],
610  'table_prefix' => $conn['prefix'],
611  ],
612  ],
613  ];
614  }
615 
622  public function allowPathRewrite() {
623  $request = $this->internal_services->request;
624  $new = $this->internal_services->router->allowRewrite($request);
625  if ($new === $request) {
626  return;
627  }
628 
629  $this->internal_services->set('request', $new);
630  }
631 
637  public static function isCli() {
638  switch (PHP_SAPI) {
639  case 'cli':
640  case 'phpdbg':
641  return true;
642 
643  default:
644  return false;
645  }
646  }
647 
653  public static function getRequest() {
654  if (self::$_instance) {
655  return self::$_instance->internal_services->request;
656  }
657 
658  return HttpRequest::createFromGlobals();
659  }
660 
666  public static function getStdIn() {
667  if (self::isCli()) {
668  $request = self::getRequest();
669  $argv = $request->server->get('argv') ?: [];
670  return new ArgvInput($argv);
671  }
672 
673  return new ArrayInput([]);
674  }
675 
681  public static function getStdOut() {
682  if (self::isCli()) {
683  return new ConsoleOutput();
684  } else {
685  return new NullOutput();
686  }
687  }
688 
694  public static function getStdErr() {
695  $std_out = self::getStdOut();
696  if (is_callable([$std_out, 'getErrorOutput'])) {
697  return $std_out->getErrorOutput();
698  }
699 
700  return $std_out;
701  }
702 
708  public static function getResponseTransport() {
709  if (self::isCli()) {
710  return new \Elgg\Http\OutputBufferTransport();
711  }
712 
713  return new \Elgg\Http\HttpProtocolTransport();
714  }
715 
723  private static function getEngineLibs() {
724  return [
725  'elgglib.php',
726  'events.php',
727  'access.php',
728  'actions.php',
729  'admin.php',
730  'annotations.php',
731  'breadcrumbs.php',
732  'cache.php',
733  'configuration.php',
734  'constants.php',
735  'context.php',
736  'deprecated-6.1.php',
737  'entities.php',
738  'external_files.php',
739  'filestore.php',
740  'gatekeepers.php',
741  'input.php',
742  'languages.php',
743  'mb_wrapper.php',
744  'metadata.php',
745  'navigation.php',
746  'notification.php',
747  'output.php',
748  'pagehandler.php',
749  'pageowner.php',
750  'pam.php',
751  'plugins.php',
752  'relationships.php',
753  'river.php',
754  'sessions.php',
755  'users.php',
756  'views.php',
757  'widgets.php',
758  ];
759  }
760 }
$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:47
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:90
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:71
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
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:183
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:256
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