Elgg  Version 3.0
Application.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
6 use Closure;
25 use ElggInstaller;
26 use Exception;
39 
50 class Application {
51 
52  use Loggable;
53 
54  const DEFAULT_LANG = 'en';
55  const DEFAULT_LIMIT = 10;
56 
62  public $_services;
63 
67  private static $_setups = [];
68 
75  public static $_instance;
76 
83  public static function getInstance() {
84  if (self::$_instance === null) {
85  self::$_instance = self::factory();
86  self::setGlobalConfig(self::$_instance);
87  }
88 
89  return self::$_instance;
90  }
91 
99  public static function setInstance(Application $application = null) {
100  self::$_instance = $application;
101  }
102 
110  public function __construct(ServiceProvider $services) {
111  $this->_services = $services;
112  }
113 
125  public static function loadCore() {
126  $setups = [];
127 
128  $path = Paths::elgg() . 'engine/lib';
129 
130  // include library files, capturing setup functions
131  foreach (self::getEngineLibs() as $file) {
132  try {
133  $setups[] = self::requireSetupFileOnce("$path/$file");
134  } catch (\Error $e) {
135  throw new \InstallationException("Elgg lib file failed include: $path/$file");
136  }
137  }
138 
139  return $setups;
140  }
141 
150  public static function requireSetupFileOnce($file) {
151  if (isset(self::$_setups[$file])) {
152  return self::$_setups[$file];
153  }
154 
155  $return = Includer::requireFileOnce($file);
156  self::$_setups[$file] = $return;
157 
158  return $return;
159  }
160 
172  public static function start() {
173  $app = self::getInstance();
174  $app->bootCore();
175 
176  return $app;
177  }
178 
184  public static function isCoreLoaded() {
185  return function_exists('elgg');
186  }
187 
213  public function bootCore() {
214  $boot = new BootHandler($this);
215  $boot();
216  }
217 
225  public function getDbConfig() {
226  return $this->_services->dbConfig;
227  }
228 
238  public function getDb() {
239  return $this->_services->publicDb;
240  }
241 
249  public static function setGlobalConfig(Application $application) {
250  global $CONFIG;
251  $CONFIG = $application->_services->config;
252  }
253 
268  public static function factory(array $spec = []) {
269 
270  $defaults = [
271  'config' => null,
272  'handle_exceptions' => true,
273  'handle_shutdown' => true,
274  'request' => null,
275  'service_provider' => null,
276  'set_start_time' => true,
277  'settings_path' => null,
278  ];
279  $spec = array_merge($defaults, $spec);
280 
281  if ($spec['set_start_time']) {
287  if (!isset($GLOBALS['START_MICROTIME'])) {
288  $GLOBALS['START_MICROTIME'] = microtime(true);
289  }
290  }
291 
292  if ($spec['handle_exceptions']) {
293  set_error_handler(new ErrorHandler());
294  set_exception_handler(new ExceptionHandler());
295  }
296 
297  self::loadCore();
298 
299  if (!$spec['service_provider']) {
300  if (!$spec['config']) {
301  $spec['config'] = Config::factory($spec['settings_path']);
302  }
303  $spec['service_provider'] = new ServiceProvider($spec['config']);
304  }
305 
306  if ($spec['request']) {
307  if ($spec['request'] instanceof HttpRequest) {
308  $spec['service_provider']->setValue('request', $spec['request']);
309  } else {
310  throw new InvalidArgumentException("Given request is not a " . HttpRequest::class);
311  }
312  }
313 
314  $app = new self($spec['service_provider']);
315 
316  if ($spec['handle_shutdown']) {
317  register_shutdown_function(new ShutdownHandler($app));
318  }
319 
320  return $app;
321  }
322 
335  public static function route(HttpRequest $request) {
336  self::loadCore();
337 
338  if ($request->isRewriteCheck()) {
339  $response = new OkResponse(HttpRequest::REWRITE_TEST_OUTPUT);
340  return self::respond($response);
341  }
342 
343  if (self::$_instance) {
344  $app = self::$_instance;
345  $app->_services->setValue('request', $request);
346  } else {
347  try {
348  $app = self::factory([
349  'request' => $request,
350  ]);
351 
352  self::setGlobalConfig($app);
353  self::setInstance($app);
354  } catch (ConfigurationException $ex) {
355  return self::install();
356  }
357  }
358 
359  return $app->run();
360  }
361 
373  public static function respond(ResponseBuilder $builder) {
374  if (self::$_instance) {
375  self::$_instance->_services->responseFactory->respond($builder);
376 
377  return self::$_instance->_services->responseFactory->getSentResponse();
378  }
379 
380  try {
381  $content = $builder->getContent();
382  $status = $builder->getStatusCode();
383  $headers = $builder->getHeaders();
384 
385  if ($builder->isRedirection()) {
386  $forward_url = $builder->getForwardURL();
387  $response = new SymfonyRedirect($forward_url, $status, $headers);
388  } else {
389  $response = new Response($content, $status, $headers);
390  }
391  } catch (Exception $ex) {
392  $response = new Response($ex->getMessage(), 500);
393  }
394 
395  $response->headers->set('Pragma', 'public');
396  $response->headers->set('Cache-Control', 'no-cache, must-revalidate');
397  $response->headers->set('Expires', 'Fri, 05 Feb 1982 00:00:00 -0500');
398 
399  self::getResponseTransport()->send($response);
400 
401  return $response;
402  }
403 
414  public static function index() {
415  return self::route(self::getRequest());
416  }
417 
428  public function run() {
429  $config = $this->_services->config;
430  $request = $this->_services->request;
431 
432  try {
433  if ($request->isCliServer()) {
434  if ($request->isCliServable(Paths::project())) {
435  return false;
436  }
437 
438  // overwrite value from settings
439  $www_root = rtrim($request->getSchemeAndHttpHost() . $request->getBaseUrl(), '/') . '/';
440  $config->wwwroot = $www_root;
441  $config->wwwroot_cli_server = $www_root;
442  }
443 
444  if (0 === strpos($request->getElggPath(), '/cache/')) {
445  $config->_disable_session_save = true;
446  $response = $this->_services->cacheHandler->handleRequest($request, $this)->prepare($request);
447  self::getResponseTransport()->send($response);
448 
449  return $response;
450  }
451 
452  if (0 === strpos($request->getElggPath(), '/serve-file/')) {
453  $response = $this->_services->serveFileHandler->getResponse($request);
454  self::getResponseTransport()->send($response);
455 
456  return $response;
457  }
458 
459  $this->bootCore();
460 
461  // re-fetch new request from services in case it was replaced by route:rewrite
462  $request = $this->_services->request;
463 
464  if (!$this->_services->router->route($request)) {
465  throw new PageNotFoundException();
466  }
467  } catch (HttpException $ex) {
468  $forward_url = $ex->getRedirectUrl();
469  if (!$forward_url) {
470  if ($ex instanceof GatekeeperException) {
472  } else if ($request->getFirstUrlSegment() == 'action') {
474  }
475  }
476 
477  $hook_params = [
478  'exception' => $ex,
479  ];
480 
481  $forward_url = $this->_services->hooks->trigger('forward', $ex->getCode(), $hook_params, $forward_url);
482 
483  if ($forward_url) {
484  if ($ex->getMessage()) {
485  $this->_services->systemMessages->addErrorMessage($ex->getMessage());
486  }
487  $response = new RedirectResponse($forward_url);
488  } else {
489  $response = new ErrorResponse($ex->getMessage(), $ex->getCode(), $forward_url);
490  }
491 
492  self::respond($response);
493  }
494 
495  return $this->_services->responseFactory->getSentResponse();
496  }
497 
504  public static function elggDir() {
505  return Local::elggRoot();
506  }
507 
513  public static function projectDir() {
514  return Local::projectRoot();
515  }
516 
523  public static function install() {
524  ini_set('display_errors', 1);
525 
526  try {
527  $installer = new ElggInstaller();
528  $response = $installer->run();
529  } catch (Exception $ex) {
530  $response = new ErrorResponse($ex->getMessage(), 500);
531  }
532 
533  return self::respond($response);
534  }
535 
556  public static function upgrade() {
557 
558  try {
559  self::migrate();
560  self::start();
561 
562  $request = self::$_instance->_services->request;
563  $signer = self::$_instance->_services->urlSigner;
564 
565  $url = $request->getCurrentURL();
566  $query = $request->getParams();
567 
568  // We need to resign the URL because the path is different
569  $mac = elgg_extract(UrlSigner::KEY_MAC, $query);
570  if (isset($mac) && !$signer->isValid($url)) {
571  throw new \Elgg\HttpException(elgg_echo('invalid_request_signature'), ELGG_HTTP_FORBIDDEN);
572  }
573 
574  unset($query[UrlSigner::KEY_MAC]);
575 
576  $base_url = elgg_normalize_site_url('upgrade/init');
578 
579  if (isset($mac)) {
580  $url = self::$_instance->_services->urlSigner->sign($url);
581  }
582 
584  } catch (Exception $ex) {
585  $response = new ErrorResponse($ex->getMessage(), $ex->getCode() ? : ELGG_HTTP_INTERNAL_SERVER_ERROR);
586  }
587 
588  return self::respond($response);
589  }
590 
597  public static function migrate() {
598 
599  $constants = self::elggDir()->getPath('engine/lib/constants.php');
600  self::requireSetupFileOnce($constants);
601 
602  $conf = self::elggDir()->getPath('engine/conf/migrations.php');
603  if (!$conf) {
604  throw new InstallationException('Settings file is required to run database migrations.');
605  }
606 
607  // setting timeout because some database migrations can take a long time
608  set_time_limit(0);
609 
610  $app = new \Phinx\Console\PhinxApplication();
611  $wrapper = new \Phinx\Wrapper\TextWrapper($app, [
612  'configuration' => $conf,
613  ]);
614  $log = $wrapper->getMigrate();
615 
616  if (!empty($_SERVER['argv']) && in_array('--verbose', $_SERVER['argv'])) {
617  error_log($log);
618  }
619 
620  return true;
621  }
622 
627  public static function getMigrationSettings() {
628 
629  $config = Config::factory();
630  $db_config = DbConfig::fromElggConfig($config);
631 
632  if ($db_config->isDatabaseSplit()) {
633  $conn = $db_config->getConnectionConfig(DbConfig::WRITE);
634  } else {
635  $conn = $db_config->getConnectionConfig();
636  }
637 
638  return [
639  "paths" => [
640  "migrations" => Paths::elgg() . 'engine/schema/migrations/',
641  ],
642  "environments" => [
643  "default_migration_table" => "{$conn['prefix']}migrations",
644  "default_database" => "prod",
645  "prod" => [
646  "adapter" => "mysql",
647  "host" => $conn['host'],
648  "name" => $conn['database'],
649  "user" => $conn['user'],
650  "pass" => $conn['password'],
651  "charset" => $conn['encoding'],
652  "table_prefix" => $conn['prefix'],
653  ],
654  ],
655  ];
656  }
657 
665  public function allowPathRewrite() {
666  $request = $this->_services->request;
667  $new = $this->_services->router->allowRewrite($request);
668  if ($new === $request) {
669  return;
670  }
671 
672  $this->_services->setValue('request', $new);
673  }
674 
679  public static function isCli() {
680  switch (php_sapi_name()) {
681  case 'cli' :
682  case 'phpdbg' :
683  return true;
684 
685  default:
686  return false;
687  }
688  }
689 
694  public static function getRequest() {
695  if (self::$_instance) {
696  return self::$_instance->_services->request;
697  }
698 
699  return HttpRequest::createFromGlobals();
700  }
701 
706  public static function getStdIn() {
707  if (self::isCli()) {
708  $request = self::getRequest();
709  $argv = $request->server->get('argv') ? : [];
710  return new ArgvInput($argv);
711  }
712 
713  return new ArrayInput([]);
714  }
715 
720  public static function getStdOut() {
721  if (self::isCli()) {
722  return new ConsoleOutput();
723  } else {
724  return new NullOutput();
725  }
726  }
727 
732  public static function getStdErr() {
733  $std_out = self::getStdOut();
734  if (is_callable([$std_out, 'getErrorOutput'])) {
735  return $std_out->getErrorOutput();
736  }
737 
738  return $std_out;
739  }
740 
745  public static function getResponseTransport() {
746  if (self::isCli()) {
747  return new \Elgg\Http\OutputBufferTransport();
748  }
749 
750  return new \Elgg\Http\HttpProtocolTransport();
751  }
752 
760  private static function getEngineLibs() {
761  return [
762  'elgglib.php',
763  'access.php',
764  'actions.php',
765  'admin.php',
766  'annotations.php',
767  'cache.php',
768  'comments.php',
769  'configuration.php',
770  'constants.php',
771  'cron.php',
772  'database.php',
773  'deprecated-2.3.php',
774  'deprecated-3.0.php',
775  'entities.php',
776  'filestore.php',
777  'group.php',
778  'input.php',
779  'languages.php',
780  'mb_wrapper.php',
781  'metadata.php',
782  'navigation.php',
783  'notification.php',
784  'output.php',
785  'pagehandler.php',
786  'pageowner.php',
787  'pam.php',
788  'plugins.php',
789  'relationships.php',
790  'river.php',
791  'search.php',
792  'sessions.php',
793  'statistics.php',
794  'tags.php',
795  'upgrade.php',
796  'user_settings.php',
797  'users.php',
798  'views.php',
799  'widgets.php',
800  ];
801  }
802 }
static index()
Elgg&#39;s front controller.
static factory(array $spec=[])
Create a new application.
elgg_http_add_url_query_elements($url, array $elements)
Sets elements in a URL&#39;s query string.
Definition: elgglib.php:942
Generic HTTP exception.
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:48
$query
Definition: groups.php:8
const ELGG_HTTP_FORBIDDEN
Definition: constants.php:82
static install()
Renders a web UI for installing Elgg.
if(!$entity->delete()) $forward_url
Definition: delete.php:30
if(!array_key_exists($filename, $text_files)) $file
getStatusCode()
Returns status code.
isRedirection()
Check if response is redirection.
static projectDir()
Returns a directory that points to the project root, where composer is installed. ...
getContent()
Returns response body.
static $_instance
Reference to the loaded Application.
Definition: Application.php:75
static loadCore()
Define all Elgg global functions and constants, wire up boot events, but don&#39;t boot.
$defaults
Thrown when page is not accessible.
$request
Page handler for autocomplete endpoint.
Definition: livesearch.php:9
Error response builder.
const ELGG_HTTP_PERMANENTLY_REDIRECT
Definition: constants.php:78
static isCoreLoaded()
Are Elgg&#39;s global functions loaded?
elgg_normalize_site_url($unsafe_url)
From untrusted input, get a site URL safe for forwarding.
Definition: output.php:237
trait Loggable
Enables adding a logger.
Definition: Loggable.php:12
static getStdOut()
Load console output interface.
$path
Definition: details.php:89
getDbConfig()
Get the DB credentials.
static getStdErr()
Load console error output interface.
elgg_echo($message_key, array $args=[], $language="")
Given a message key, returns an appropriately translated full-text string.
Definition: languages.php:21
getDb()
Get a Database wrapper for performing queries without booting Elgg.
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
string project
Definition: conf.py:46
static start()
Start and boot the core.
Configuration exception.
const REFERRER
Definition: constants.php:42
bootCore()
Bootstrap the Elgg engine, loads plugins, and calls initial system events.
static getResponseTransport()
Build a transport for sending responses.
Thrown when one of the gatekeepers prevents access.
static getMigrationSettings()
Returns configuration array for database migrations.
static upgrade()
Elgg upgrade script.
static isCli()
Is application running in CLI.
__construct(ServiceProvider $services)
Constructor.
global $CONFIG
static respond(ResponseBuilder $builder)
Build and send a response.
$url
Definition: default.php:33
$installer
static route(HttpRequest $request)
Route a request.
Handle system and PHP errors.
static getRequest()
Build request object.
elgg global
Pointer to the global context.
Definition: elgglib.js:12
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:1131
Redirect response builder.
getRedirectUrl()
Get preferred redirect URL.
static getStdIn()
Load console input interface.
Load, boot, and implement a front controller for an Elgg application.
Definition: Application.php:50
$content
Set robots.txt action.
Definition: set_robots.php:6
const ELGG_HTTP_INTERNAL_SERVER_ERROR
Definition: constants.php:106
static elggDir()
Returns a directory that points to the root of Elgg, but not necessarily the install root...
class
Definition: placeholder.php:21
getHeaders()
Returns additional response headers.
static setGlobalConfig(Application $application)
Make the global $CONFIG a reference to this application&#39;s config service.
static requireSetupFileOnce($file)
Require a library/plugin file once and capture returned anonymous functions.
Handles application boot sequence.
Definition: BootHandler.php:13
Response builder.
Definition: OkResponse.php:10
var elgg
Definition: elgglib.js:4
static setInstance(Application $application=null)
Set the global Application instance.
Definition: Application.php:99
elgg_get_login_url(array $query=[], $fragment= '')
Returns site&#39;s login URL Triggers a &#39;login_url&#39;, &#39;site&#39; plugin hook that can be used by plugins to al...
Definition: users.php:277
static migrate()
Runs database migrations.
getForwardURL()
Returns redirect URL.
static getInstance()
Get the global Application instance.
Definition: Application.php:83