Elgg  Version 2.3
Application.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
7 
21 class Application {
22 
23  const GET_PATH_KEY = '__elgg_uri';
24  const REWRITE_TEST_TOKEN = '__testing_rewrite';
25  const REWRITE_TEST_OUTPUT = 'success';
26 
30  private $services;
31 
35  private $engine_dir;
36 
40  private static $testing_app;
41 
50  private static $public_services = [
51  //'config' => true,
52  'menus' => true,
53  'table_columns' => true,
54  ];
55 
63  public static $_instance;
64 
72  public function __construct(ServiceProvider $services) {
73  $this->services = $services;
74 
80  if (!isset($GLOBALS['START_MICROTIME'])) {
81  $GLOBALS['START_MICROTIME'] = microtime(true);
82  }
83 
84  $services->timer->begin([]);
85 
93  if (!isset($GLOBALS['_ELGG'])) {
94  $GLOBALS['_ELGG'] = new \stdClass();
95  }
96 
97  $this->engine_dir = __DIR__ . '/../..';
98  }
99 
108  public function loadSettings() {
109  $this->services->config->loadSettingsFile();
110  }
111 
121  public function loadCore() {
122  if (function_exists('elgg')) {
123  return;
124  }
125 
126  $lib_dir = self::elggDir()->chroot("engine/lib");
127 
128  // load the rest of the library files from engine/lib/
129  // All on separate lines to make diffs easy to read + make it apparent how much
130  // we're actually loading on every page (Hint: it's too much).
131  $lib_files = array(
132  // Needs to be loaded first to correctly bootstrap
133  'autoloader.php',
134  'elgglib.php',
135 
136  // The order of these doesn't matter, so keep them alphabetical
137  'access.php',
138  'actions.php',
139  'admin.php',
140  'annotations.php',
141  'cache.php',
142  'comments.php',
143  'configuration.php',
144  'cron.php',
145  'database.php',
146  'entities.php',
147  'extender.php',
148  'filestore.php',
149  'friends.php',
150  'group.php',
151  'input.php',
152  'languages.php',
153  'mb_wrapper.php',
154  'memcache.php',
155  'metadata.php',
156  'metastrings.php',
157  'navigation.php',
158  'notification.php',
159  'objects.php',
160  'output.php',
161  'pagehandler.php',
162  'pageowner.php',
163  'pam.php',
164  'plugins.php',
165  'private_settings.php',
166  'relationships.php',
167  'river.php',
168  'sessions.php',
169  'sites.php',
170  'statistics.php',
171  'system_log.php',
172  'tags.php',
173  'user_settings.php',
174  'users.php',
175  'upgrade.php',
176  'views.php',
177  'widgets.php',
178 
179  // backward compatibility
180  'deprecated-1.9.php',
181  'deprecated-1.10.php',
182  'deprecated-1.11.php',
183  'deprecated-1.12.php',
184  'deprecated-2.1.php',
185  );
186 
187  // isolate global scope
188  call_user_func(function () use ($lib_dir, $lib_files) {
189 
190  $setups = array();
191 
192  // include library files, capturing setup functions
193  foreach ($lib_files as $file) {
194  $setup = (require_once $lib_dir->getPath($file));
195 
196  if ($setup instanceof \Closure) {
197  $setups[$file] = $setup;
198  }
199  }
200 
201  // store instance to be returned by elgg()
202  self::$_instance = $this;
203 
204  // set up autoloading and DIC
205  _elgg_services($this->services);
206 
207  $events = $this->services->events;
208  $hooks = $this->services->hooks;
209 
210  // run setups
211  foreach ($setups as $func) {
212  $func($events, $hooks);
213  }
214  });
215  }
216 
222  public static function start() {
223  $app = self::create();
224  $app->bootCore();
225  return $app;
226  }
227 
241  public function bootCore() {
242 
243  $config = $this->services->config;
244 
245  if ($this->isTestingApplication()) {
246  throw new \RuntimeException('Unit tests should not call ' . __METHOD__);
247  }
248 
249  if ($config->getVolatile('boot_complete')) {
250  return;
251  }
252 
253  $this->loadSettings();
254 
255  $config->set('boot_complete', false);
256 
257  // This will be overridden by the DB value but may be needed before the upgrade script can be run.
258  $config->set('default_limit', 10);
259 
260  // in case not loaded already
261  $this->loadCore();
262 
263  $events = $this->services->events;
264 
265  // Connect to database, load language files, load configuration, init session
266  // Plugins can't use this event because they haven't been loaded yet.
267  $events->trigger('boot', 'system');
268 
269  // Load the plugins that are active
270  $this->services->plugins->load();
271 
272  $root = Directory\Local::root();
273  if ($root->getPath() != self::elggDir()->getPath()) {
274  // Elgg is installed as a composer dep, so try to treat the root directory
275  // as a custom plugin that is always loaded last and can't be disabled...
276  if (!elgg_get_config('system_cache_loaded')) {
277  // configure view locations for the custom plugin (not Elgg core)
278  $viewsFile = $root->getFile('views.php');
279  if ($viewsFile->exists()) {
280  $viewsSpec = $viewsFile->includeFile();
281  if (is_array($viewsSpec)) {
282  _elgg_services()->views->mergeViewsSpec($viewsSpec);
283  }
284  }
285 
286  // find views for the custom plugin (not Elgg core)
287  _elgg_services()->views->registerPluginViews($root->getPath());
288  }
289 
290  if (!elgg_get_config('i18n_loaded_from_cache')) {
291  _elgg_services()->translator->registerPluginTranslations($root->getPath());
292  }
293 
294  // This is root directory start.php, not elgg/engine/start.php
295  $root_start = $root->getPath("start.php");
296  if (is_file($root_start)) {
297  require $root_start;
298  }
299  }
300 
301 
302  // @todo move loading plugins into a single boot function that replaces 'boot', 'system' event
303  // and then move this code in there.
304  // This validates the view type - first opportunity to do it is after plugins load.
305  $viewtype = elgg_get_viewtype();
306  if (!elgg_is_registered_viewtype($viewtype)) {
307  elgg_set_viewtype('default');
308  }
309 
310  $this->allowPathRewrite();
311 
312  // Allows registering handlers strictly before all init, system handlers
313  $events->trigger('plugins_boot', 'system');
314 
315  // Complete the boot process for both engine and plugins
316  $events->trigger('init', 'system');
317 
318  $config->set('boot_complete', true);
319 
320  // System loaded and ready
321  $events->trigger('ready', 'system');
322  }
323 
336  public function getDb() {
337  $this->loadSettings();
338  return $this->services->publicDb;
339  }
340 
348  public function __get($name) {
349  if (isset(self::$public_services[$name])) {
350  return $this->services->{$name};
351  }
352  trigger_error("Undefined property: " . __CLASS__ . ":\${$name}");
353  }
354 
361  private static function create() {
362  if (self::$_instance === null) {
363  // we need to register for shutdown before Symfony registers the
364  // session_write_close() function. https://github.com/Elgg/Elgg/issues/9243
365  register_shutdown_function(function () {
366  // There are cases where we may exit before this function is defined
367  if (function_exists('_elgg_shutdown_hook')) {
369  }
370  });
371 
372  self::$_instance = new self(new Di\ServiceProvider(new Config()));
373  }
374 
375  return self::$_instance;
376  }
377 
383  public static function index() {
384  return self::create()->run();
385  }
386 
392  public function run() {
393  $path = $this->setupPath();
394 
395  // allow testing from the upgrade page before the site is upgraded.
396  if (isset($_GET[self::REWRITE_TEST_TOKEN])) {
397  if (false !== strpos($path, self::REWRITE_TEST_TOKEN)) {
398  echo self::REWRITE_TEST_OUTPUT;
399  }
400  return true;
401  }
402 
403  if (php_sapi_name() === 'cli-server') {
404  $www_root = "http://{$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']}/";
405  $this->services->config->set('wwwroot', $www_root);
406  }
407 
408  if (0 === strpos($path, '/cache/')) {
409  (new Application\CacheHandler($this, $this->services->config, $_SERVER))->handleRequest($path);
410  return true;
411  }
412 
413  if (0 === strpos($path, '/serve-file/')) {
414  $this->services->serveFileHandler->getResponse($this->services->request)->send();
415  return true;
416  }
417 
418  if ($path === '/rewrite.php') {
419  require Directory\Local::root()->getPath("install.php");
420  return true;
421  }
422 
423  if (php_sapi_name() === 'cli-server') {
424  // The CLI server routes ALL requests here (even existing files), so we have to check for these.
425  if ($path !== '/' && Directory\Local::root()->isFile($path)) {
426  // serve the requested resource as-is.
427  return false;
428  }
429  }
430 
431  $this->bootCore();
432 
433  // TODO use formal Response object instead
434  header("Content-Type: text/html;charset=utf-8");
435 
436  if (!$this->services->router->route($this->services->request)) {
437  forward('', '404');
438  }
439  }
440 
449  public static function getDataPath() {
450  $app = self::create();
451  $app->services->config->loadSettingsFile();
452 
453  if ($GLOBALS['_ELGG']->dataroot_in_settings) {
454  return $app->services->config->getVolatile('dataroot');
455  }
456 
457  $dataroot = $app->services->datalist->get('dataroot');
458  if (!$dataroot) {
459  throw new \InstallationException('The datalists table lacks a value for "dataroot".');
460  }
461  $dataroot = rtrim($dataroot, '/\\') . DIRECTORY_SEPARATOR;
462  $app->services->config->set('dataroot', $dataroot);
463  return $dataroot;
464  }
465 
472  public static function elggDir() /*: Directory*/ {
473  return Directory\Local::fromPath(realpath(__DIR__ . '/../../..'));
474  }
475 
481  public static function install() {
482  ini_set('display_errors', 1);
483  $installer = new \ElggInstaller();
484  $step = get_input('step', 'welcome');
485  $installer->run($step);
486  }
487 
502  public static function upgrade() {
503  // we want to know if an error occurs
504  ini_set('display_errors', 1);
505 
506  define('UPGRADING', 'upgrading');
507 
508  self::start();
509 
510  $site_url = elgg_get_config('url');
511  $site_host = parse_url($site_url, PHP_URL_HOST) . '/';
512 
513  // turn any full in-site URLs into absolute paths
514  $forward_url = elgg_normalize_site_url(get_input('forward', '/admin', false));
515  $forward_url = str_replace(array($site_url, $site_host), '/', $forward_url);
516 
517  if (strpos($forward_url, '/') !== 0) {
518  $forward_url = '/' . $forward_url;
519  }
520 
521  if (get_input('upgrade') == 'upgrade') {
522 
523  $upgrader = _elgg_services()->upgrades;
524  $result = $upgrader->run();
525  if ($result['failure'] == true) {
526  register_error($result['reason']);
528  }
529  } else {
530  $rewriteTester = new \ElggRewriteTester();
531  $url = elgg_get_site_url() . "__testing_rewrite?__testing_rewrite=1";
532  if (!$rewriteTester->runRewriteTest($url)) {
533  // see if there is a problem accessing the site at all
534  // due to ip restrictions for example
535  if (!$rewriteTester->runLocalhostAccessTest()) {
536  // note: translation may not be available until after upgrade
537  $msg = elgg_echo("installation:htaccess:localhost:connectionfailed");
538  if ($msg === "installation:htaccess:localhost:connectionfailed") {
539  $msg = "Elgg cannot connect to itself to test rewrite rules properly. Check "
540  . "that curl is working and there are no IP restrictions preventing "
541  . "localhost connections.";
542  }
543  echo $msg;
544  exit;
545  }
546 
547  // note: translation may not be available until after upgrade
548  $msg = elgg_echo("installation:htaccess:needs_upgrade");
549  if ($msg === "installation:htaccess:needs_upgrade") {
550  $msg = "You must update your .htaccess file so that the path is injected "
551  . "into the GET parameter __elgg_uri (you can use install/config/htaccess.dist as a guide).";
552  }
553  echo $msg;
554  exit;
555  }
556 
557  $vars = array(
558  'forward' => $forward_url
559  );
560 
561  // reset cache to have latest translations available during upgrade
563 
564  echo elgg_view_page(elgg_echo('upgrading'), '', 'upgrade', $vars);
565  exit;
566  }
567 
569  }
570 
576  private function setupPath() {
577  if (!isset($_GET[self::GET_PATH_KEY]) || is_array($_GET[self::GET_PATH_KEY])) {
578  if (php_sapi_name() === 'cli-server') {
579  $_GET[self::GET_PATH_KEY] = (string)parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
580  } else {
581  $_GET[self::GET_PATH_KEY] = '/';
582  }
583  }
584 
585  // normalize
586  $_GET[self::GET_PATH_KEY] = '/' . trim($_GET[self::GET_PATH_KEY], '/');
587 
588  return $_GET[self::GET_PATH_KEY];
589  }
590 
596  private function allowPathRewrite() {
597  $request = $this->services->request;
598  $new = $this->services->router->allowRewrite($request);
599  if ($new === $request) {
600  return;
601  }
602 
603  $this->services->setValue('request', $new);
605  }
606 
613  public static function setTestingApplication($testing = true) {
614  self::$testing_app = $testing;
615  }
616 
621  public static function isTestingApplication() {
622  return (bool) self::$testing_app;
623  }
624 }
static index()
Elgg&#39;s front controller.
A simple directory abstraction.
Definition: Directory.php:13
elgg_get_config($name, $site_guid=0)
Get an Elgg configuration value.
run()
Routes the request, booting core if not yet booted.
elgg_reset_system_cache()
Reset the system cache by deleting the caches.
Definition: cache.php:29
loadSettings()
Load settings.php.
static install()
Renders a web UI for installing Elgg.
if(!$entity->delete()) $forward_url
Definition: delete.php:37
if(!array_key_exists($filename, $text_files)) $file
__get($name)
Get an undefined property.
if($guid==elgg_get_logged_in_user_guid()) $name
Definition: delete.php:21
elgg_echo($message_key, $args=array(), $language="")
Given a message key, returns an appropriately translated full-text string.
Definition: languages.php:21
$installer
static setTestingApplication($testing=true)
Flag this application as running for testing (PHPUnit)
elgg_normalize_site_url($unsafe_url)
From untrusted input, get a site URL safe for forwarding.
Definition: output.php:326
$path
Definition: details.php:88
Access to configuration values.
Definition: Config.php:11
elgg_is_registered_viewtype($viewtype)
Checks if $viewtype is registered.
Definition: views.php:153
static isTestingApplication()
Checks if the application is running in PHPUnit.
elgg parse_url
Parse a URL into its parts.
Definition: elgglib.js:450
elgg forward
Meant to mimic the php forward() function by simply redirecting the user to another page...
Definition: elgglib.js:425
static getDataPath()
Determine the Elgg data directory with trailing slash, save it to config, and return it...
$url
Definition: exceptions.php:24
$vars['entity']
getDb()
Get a Database wrapper for performing queries without booting Elgg.
_elgg_shutdown_hook()
Emits a shutdown:system event upon PHP shutdown, but before database connections are dropped...
Definition: elgglib.php:1568
elgg_get_viewtype()
Return the current view type.
Definition: views.php:95
static start()
Replacement for loading engine/start.php.
loadCore()
Load all Elgg procedural code and wire up boot events, but don&#39;t boot.
Save menu items.
bootCore()
Bootstrap the Elgg engine, loads plugins, and calls initial system events.
static static $_instance
Reference to the loaded Application returned by elgg()
Definition: Application.php:53
get_input($variable, $default=null, $filter_result=true)
Get some input from variables passed submitted through GET or POST.
Definition: input.php:27
static upgrade()
Elgg upgrade script.
$site_url
Definition: header_logo.php:8
if(is_array($custom_items)) $new
Definition: save.php:63
__construct(ServiceProvider $services)
Constructor.
Definition: Application.php:72
elgg_set_viewtype($viewtype="")
Manually set the viewtype.
Definition: views.php:74
elgg echo
Translates a string.
Definition: languages.js:48
elgg require
Throw an error if the required package isn&#39;t present.
Definition: elgglib.js:164
$dataroot
elgg_get_site_url($site_guid=0)
Get the URL for the current (or specified) site.
clearfix elgg elgg elgg elgg page header
Definition: admin.css.php:127
_elgg_services(\Elgg\Di\ServiceProvider $services=null)
Get the global service provider.
Definition: autoloader.php:17
elgg register_error
Wrapper function for system_messages.
Definition: elgglib.js:399
static elggDir()
Returns a directory that points to the root of Elgg, but not necessarily the install root...
define(function(require){var $=require('jquery');$(document).on('change', '#elgg-river-selector', function(){var url=window.location.href;if(window.location.search.length){url=url.substring(0, url.indexOf('?'));}url+= '?'+$(this).val();window.location.href=url;});})
Initiates page reload when river selector value changes core/river/filter.
exit
Definition: autoloader.php:34
http free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:5
elgg_view_page($title, $body, $page_shell= 'default', $vars=array())
Assembles and outputs a full page.
Definition: views.php:447
_elgg_set_initial_context(\Elgg\Http\Request $request)
Set an initial context if using index.php front controller.
Definition: pageowner.php:273