Elgg  Version master
Application.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
37 
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();
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 
487  public static function elggDir() {
488  return Local::elggRoot();
489  }
490 
496  public static function projectDir() {
497  return Local::projectRoot();
498  }
499 
505  public static function install() {
506  ini_set('display_errors', 1);
507 
508  try {
509  $installer = new \ElggInstaller();
510  $response = $installer->run();
511  } catch (\Exception $ex) {
512  $response = new ErrorResponse($ex->getMessage(), 500);
513  }
514 
515  return self::respond($response);
516  }
517 
532  public static function upgrade() {
533 
534  try {
535  self::migrate();
536  self::start();
537 
538  $request = self::$_instance->internal_services->request;
539  $signer = self::$_instance->internal_services->urlSigner;
540 
541  $url = $request->getCurrentURL();
542  $query = $request->getParams();
543 
544  // We need to resign the URL because the path is different
545  $mac = elgg_extract(UrlSigner::KEY_MAC, $query);
546  if (isset($mac) && !$signer->isValid($url)) {
547  throw new HttpException(elgg_echo('invalid_request_signature'), ELGG_HTTP_FORBIDDEN);
548  }
549 
550  unset($query[UrlSigner::KEY_MAC]);
551 
552  $base_url = elgg_normalize_site_url('upgrade/init');
554 
555  if (isset($mac)) {
556  $url = self::$_instance->internal_services->urlSigner->sign($url);
557  }
558 
560  } catch (\Exception $ex) {
561  $response = new ErrorResponse($ex->getMessage(), $ex->getCode() ?: ELGG_HTTP_INTERNAL_SERVER_ERROR);
562  }
563 
564  return self::respond($response);
565  }
566 
573  public static function migrate() {
574 
575  $constants = self::elggDir()->getPath('engine/lib/constants.php');
576  Includer::requireFileOnce($constants);
577 
578  $conf = self::elggDir()->getPath('engine/schema/migrations.php');
579  if (!$conf) {
580  throw new InstallationException('Settings file is required to run database migrations.');
581  }
582 
583  // setting timeout because some database migrations can take a long time
584  set_time_limit(0);
585 
586  $app = new \Phinx\Console\PhinxApplication();
587  $wrapper = new \Phinx\Wrapper\TextWrapper($app, [
588  'configuration' => $conf,
589  ]);
590  $log = $wrapper->getMigrate();
591 
592  if (!empty($_SERVER['argv']) && in_array('--verbose', $_SERVER['argv'])) {
593  error_log($log);
594  }
595 
596  return true;
597  }
598 
604  public static function getMigrationSettings() {
605 
606  $config = Config::factory();
607  $db_config = DbConfig::fromElggConfig($config);
608 
609  if ($db_config->isDatabaseSplit()) {
610  $conn = $db_config->getConnectionConfig(DbConfig::WRITE);
611  } else {
612  $conn = $db_config->getConnectionConfig();
613  }
614 
615  return [
616  'paths' => [
617  'migrations' => Paths::elgg() . 'engine/schema/migrations/',
618  ],
619  'environments' => [
620  'default_migration_table' => "{$conn['prefix']}migrations",
621  'default_database' => 'prod',
622  'prod' => [
623  'adapter' => 'mysql',
624  'host' => $conn['host'],
625  'port' => $conn['port'],
626  'name' => $conn['database'],
627  'user' => $conn['user'],
628  'pass' => $conn['password'],
629  'charset' => $conn['encoding'],
630  'table_prefix' => $conn['prefix'],
631  ],
632  ],
633  ];
634  }
635 
642  public function allowPathRewrite() {
643  $request = $this->internal_services->request;
644  $new = $this->internal_services->router->allowRewrite($request);
645  if ($new === $request) {
646  return;
647  }
648 
649  $this->internal_services->set('request', $new);
650  }
651 
657  public static function isCli() {
658  switch (PHP_SAPI) {
659  case 'cli':
660  case 'phpdbg':
661  return true;
662 
663  default:
664  return false;
665  }
666  }
667 
673  public static function getRequest() {
674  if (self::$_instance) {
675  return self::$_instance->internal_services->request;
676  }
677 
678  return HttpRequest::createFromGlobals();
679  }
680 
686  public static function getStdIn() {
687  if (self::isCli()) {
688  $request = self::getRequest();
689  $argv = $request->server->get('argv') ?: [];
690  return new ArgvInput($argv);
691  }
692 
693  return new ArrayInput([]);
694  }
695 
701  public static function getStdOut() {
702  if (self::isCli()) {
703  return new ConsoleOutput();
704  } else {
705  return new NullOutput();
706  }
707  }
708 
714  public static function getStdErr() {
715  $std_out = self::getStdOut();
716  if (is_callable([$std_out, 'getErrorOutput'])) {
717  return $std_out->getErrorOutput();
718  }
719 
720  return $std_out;
721  }
722 
728  public static function getResponseTransport() {
729  if (self::isCli()) {
730  return new \Elgg\Http\OutputBufferTransport();
731  }
732 
733  return new \Elgg\Http\HttpProtocolTransport();
734  }
735 
743  private static function getEngineLibs() {
744  return [
745  'elgglib.php',
746  'events.php',
747  'access.php',
748  'actions.php',
749  'admin.php',
750  'annotations.php',
751  'breadcrumbs.php',
752  'cache.php',
753  'configuration.php',
754  'constants.php',
755  'context.php',
756  'entities.php',
757  'external_files.php',
758  'filestore.php',
759  'gatekeepers.php',
760  'input.php',
761  'languages.php',
762  'mb_wrapper.php',
763  'metadata.php',
764  'navigation.php',
765  'notification.php',
766  'output.php',
767  'pagehandler.php',
768  'pageowner.php',
769  'pam.php',
770  'plugins.php',
771  'relationships.php',
772  'river.php',
773  'sessions.php',
774  'users.php',
775  'views.php',
776  'widgets.php',
777  ];
778  }
779 }
static index()
Elgg&#39;s front controller.
static factory(array $spec=[])
Create a new application.
elgg
Definition: install.js:27
allowPathRewrite()
Allow plugins to rewrite the path.
run()
Routes the request, booting core if not yet booted.
Handler for uncaught exceptions.
HTTP response builder interface.
elgg_is_logged_in()
Returns whether or not the user is currently logged in.
Definition: sessions.php:43
const ELGG_HTTP_FORBIDDEN
Definition: constants.php:67
static install()
Renders a web UI for installing Elgg.
A generic parent class for Configuration exceptions.
getStatusCode()
Returns status code.
Exception thrown if an argument is not of the expected type.
elgg_normalize_site_url(string $unsafe_url)
From untrusted input, get a site URL safe for forwarding.
Definition: output.php:175
isRedirection()
Check if response is redirection.
static projectDir()
Returns a directory that points to the project root, where composer is installed. ...
__construct(InternalContainer $internal_services)
Constructor.
getContent()
Returns response body.
$defaults
Generic entity header upload helper.
Definition: header.php:6
static $_instance
Reference to the loaded Application.
Definition: Application.php:72
static loadCore()
Define all Elgg global functions and constants, wire up boot events, but don&#39;t boot.
$response
Definition: content.php:10
elgg_get_login_url(array $query=[], string $fragment= '')
Returns site&#39;s login URL Triggers a &#39;login_url&#39;, &#39;site&#39; event that can be used by plugins to alter th...
Definition: users.php:236
$request
Definition: livesearch.php:12
Error response builder.
const ELGG_HTTP_PERMANENTLY_REDIRECT
Definition: constants.php:63
static isCoreLoaded()
Are Elgg&#39;s global functions loaded?
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is void
Definition: LICENSE.txt:215
elgg_echo(string $message_key, array $args=[], string $language= '')
Elgg language module Functions to manage language and translations.
Definition: languages.php:17
static getStdOut()
Load console output interface.
static getStdErr()
Load console error output interface.
$type
Definition: delete.php:22
getDb()
Get a Database wrapper for performing queries without booting Elgg.
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:254
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
string project
Definition: conf.py:49
Updates the basic settings for the primary site object.
static start()
Start and boot the core.
if(!$pagination &&$limit!==false &&!empty($items)&&count($items) >=$limit) $base_url
Definition: list.php:114
$path
Definition: details.php:70
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
Thrown when page is not accessible.
const REFERRER
Used in calls to forward() to specify the browser should be redirected to the referring page...
Definition: constants.php:37
bootCore()
Bootstrap the Elgg engine, loads plugins, and calls initial system events.
static getResponseTransport()
Build a transport for sending responses.
static getMigrationSettings()
Returns configuration array for database migrations.
static upgrade()
Elgg upgrade script.
static isCli()
Is application running in CLI.
global $CONFIG
Thrown when there is a major problem with the installation.
static respond(ResponseBuilder $builder)
Build and send a response.
static route(HttpRequest $request)
Route a request.
Handle system and PHP errors.
static getRequest()
Build request object.
elgg_http_add_url_query_elements(string $url, array $elements)
Sets elements in a URL&#39;s query string.
Definition: elgglib.php:181
if(!$entity->delete()) $forward_url
Definition: delete.php:31
$token
Redirect response builder.
getBootStatus(string $type)
Retrieve the boot status of the application.
static getStdIn()
Load console input interface.
Load, boot, and implement a front controller for an Elgg application.
Definition: Application.php:48
Generic HTTP exception.
$query
$content
Set robots.txt action.
Definition: set_robots.php:6
const ELGG_HTTP_INTERNAL_SERVER_ERROR
Definition: constants.php:91
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
static elggDir()
Returns a directory that points to the root of Elgg, but not necessarily the install root...
setBootStatus(string $type, bool $status)
Sets the boot status.
getHeaders()
Returns additional response headers.
static setGlobalConfig(Application $application)
Make the global $CONFIG a reference to this application&#39;s config service.
Thrown when one of the gatekeepers prevents access.
Handles application boot sequence.
Definition: BootHandler.php:12
OK response builder.
Definition: OkResponse.php:8
static setInstance(Application $application=null)
Set the global Application instance.
static migrate()
Runs database migrations.
getForwardURL()
Returns redirect URL.
getRedirectUrl()
Get preferred redirect URL.
static getInstance()
Get the global Application instance.
Definition: Application.php:91