X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FApp.php;h=8b2d50512b80b695f3b770db337e5e395e7e5791;hb=e9511b4f34189e3839f9c54f98835dc1374fc316;hp=db35c45c195c86f80cf8a1ced679789658ade159;hpb=be6b2295349d4d3e399b58f845efc5014e299275;p=friendica.git diff --git a/src/App.php b/src/App.php index db35c45c19..8b2d50512b 100644 --- a/src/App.php +++ b/src/App.php @@ -8,8 +8,19 @@ use Detection\MobileDetect; use DOMDocument; use DOMXPath; use Exception; +use Friendica\Core\Config\Cache\ConfigCache; +use Friendica\Core\Config\Configuration; +use Friendica\Core\Hook; +use Friendica\Core\Theme; +use Friendica\Database\Database; use Friendica\Database\DBA; -use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Model\Profile; +use Friendica\Network\HTTPException; +use Friendica\Util\BaseURL; +use Friendica\Util\Config\ConfigFileLoader; +use Friendica\Util\HTTPSignature; +use Friendica\Util\Profiler; +use Friendica\Util\Strings; use Psr\Log\LoggerInterface; /** @@ -27,10 +38,8 @@ use Psr\Log\LoggerInterface; */ class App { - public $module_loaded = false; public $module_class = null; public $query_string = ''; - public $config = []; public $page = []; public $profile; public $profile_uid; @@ -41,7 +50,6 @@ class App public $page_contact; public $content; public $data = []; - public $error = false; public $cmd = ''; public $argv; public $argc; @@ -51,8 +59,6 @@ class App public $identities; public $is_mobile = false; public $is_tablet = false; - public $performance = []; - public $callstack = []; public $theme_info = []; public $category; // Allow themes to control internal parameters @@ -73,19 +79,14 @@ class App private $mode; /** - * @var string The App base path + * @var App\Router */ - private $basePath; + private $router; /** - * @var string The App URL path + * @var BaseURL */ - private $urlPath; - - /** - * @var bool true, if the call is from the Friendica APP, otherwise false - */ - private $isFriendicaApp; + private $baseURL; /** * @var bool true, if the call is from an backend node (f.e. worker) @@ -108,10 +109,104 @@ class App public $mobileDetect; /** - * @var LoggerInterface The current logger of this App + * @var Configuration The config + */ + private $config; + + /** + * @var LoggerInterface The logger */ private $logger; + /** + * @var Profiler The profiler of this app + */ + private $profiler; + + /** + * @var Database The Friendica database connection + */ + private $database; + + /** + * Returns the current config cache of this node + * + * @return ConfigCache + */ + public function getConfigCache() + { + return $this->config->getCache(); + } + + /** + * Returns the current config of this node + * + * @return Configuration + */ + public function getConfig() + { + return $this->config; + } + + /** + * The basepath of this app + * + * @return string + */ + public function getBasePath() + { + // Don't use the basepath of the config table for basepath (it should always be the config-file one) + return $this->config->getCache()->get('system', 'basepath'); + } + + /** + * The Logger of this app + * + * @return LoggerInterface + */ + public function getLogger() + { + return $this->logger; + } + + /** + * The profiler of this app + * + * @return Profiler + */ + public function getProfiler() + { + return $this->profiler; + } + + /** + * Returns the Mode of the Application + * + * @return App\Mode The Application Mode + */ + public function getMode() + { + return $this->mode; + } + + /** + * Returns the router of the Application + * + * @return App\Router + */ + public function getRouter() + { + return $this->router; + } + + /** + * @return Database + */ + public function getDatabase() + { + return $this->database; + } + /** * Register a stylesheet file path to be included in the tag of every page. * Inclusion is done in App->initHead(). @@ -120,13 +215,14 @@ class App * @see initHead() * * @param string $path - * @throws InternalServerErrorException */ public function registerStylesheet($path) { - $url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path); + if (mb_strpos($path, $this->getBasePath() . DIRECTORY_SEPARATOR) === 0) { + $path = mb_substr($path, mb_strlen($this->getBasePath() . DIRECTORY_SEPARATOR)); + } - $this->stylesheets[] = trim($url, '/'); + $this->stylesheets[] = trim($path, '/'); } /** @@ -137,7 +233,6 @@ class App * @see initFooter() * * @param string $path - * @throws InternalServerErrorException */ public function registerFooterScript($path) { @@ -146,56 +241,35 @@ class App $this->footerScripts[] = trim($url, '/'); } - public $process_id; public $queue; - private $scheme; - private $hostname; /** * @brief App constructor. * - * @param string $basePath Path to the app base folder - * @param LoggerInterface $logger Logger of this application + * @param Database $database The Friendica Database + * @param Configuration $config The Configuration + * @param App\Mode $mode The mode of this Friendica app + * @param App\Router $router The router of this Friendica app + * @param BaseURL $baseURL The full base URL of this Friendica app + * @param LoggerInterface $logger The current app logger + * @param Profiler $profiler The profiler of this application * @param bool $isBackend Whether it is used for backend or frontend (Default true=backend) * * @throws Exception if the Basepath is not usable */ - public function __construct($basePath, LoggerInterface $logger, $isBackend = true) + public function __construct(Database $database, Configuration $config, App\Mode $mode, App\Router $router, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, $isBackend = true) { - $this->logger = $logger; - - if (!static::isDirectoryUsable($basePath, false)) { - throw new Exception('Basepath ' . $basePath . ' isn\'t usable.'); - } - BaseObject::setApp($this); - $this->basePath = rtrim($basePath, DIRECTORY_SEPARATOR); - $this->checkBackend($isBackend); - $this->checkFriendicaApp(); - - $this->performance['start'] = microtime(true); - $this->performance['database'] = 0; - $this->performance['database_write'] = 0; - $this->performance['cache'] = 0; - $this->performance['cache_write'] = 0; - $this->performance['network'] = 0; - $this->performance['file'] = 0; - $this->performance['rendering'] = 0; - $this->performance['parser'] = 0; - $this->performance['marktime'] = 0; - $this->performance['markstart'] = microtime(true); - - $this->callstack['database'] = []; - $this->callstack['database_write'] = []; - $this->callstack['cache'] = []; - $this->callstack['cache_write'] = []; - $this->callstack['network'] = []; - $this->callstack['file'] = []; - $this->callstack['rendering'] = []; - $this->callstack['parser'] = []; - - $this->mode = new App\Mode($basePath); + $this->database = $database; + $this->config = $config; + $this->mode = $mode; + $this->router = $router; + $this->baseURL = $baseURL; + $this->profiler = $profiler; + $this->logger = $logger; + + $this->profiler->reset(); $this->reload(); @@ -204,26 +278,6 @@ class App // This has to be quite large to deal with embedded private photos ini_set('pcre.backtrack_limit', 500000); - $this->scheme = 'http'; - - if (!empty($_SERVER['HTTPS']) || - !empty($_SERVER['HTTP_FORWARDED']) && preg_match('/proto=https/', $_SERVER['HTTP_FORWARDED']) || - !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || - !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on' || - !empty($_SERVER['FRONT_END_HTTPS']) && $_SERVER['FRONT_END_HTTPS'] == 'on' || - !empty($_SERVER['SERVER_PORT']) && (intval($_SERVER['SERVER_PORT']) == 443) // XXX: reasonable assumption, but isn't this hardcoding too much? - ) { - $this->scheme = 'https'; - } - - if (!empty($_SERVER['SERVER_NAME'])) { - $this->hostname = $_SERVER['SERVER_NAME']; - - if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) { - $this->hostname .= ':' . $_SERVER['SERVER_PORT']; - } - } - set_include_path( get_include_path() . PATH_SEPARATOR . $this->getBasePath() . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR @@ -282,6 +336,8 @@ class App $this->module = 'home'; } + $this->isBackend = $isBackend || $this->checkBackend($this->module); + // Detect mobile devices $mobile_detect = new MobileDetect(); @@ -296,199 +352,29 @@ class App Core\Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine'); } - /** - * Returns the Mode of the Application - * - * @return App\Mode The Application Mode - * - * @throws InternalServerErrorException when the mode isn't created - */ - public function getMode() - { - if (empty($this->mode)) { - throw new InternalServerErrorException('Mode of the Application is not defined'); - } - - return $this->mode; - } - - /** - * Returns the Logger of the Application - * - * @return LoggerInterface The Logger - * @throws InternalServerErrorException when the logger isn't created - */ - public function getLogger() - { - if (empty($this->logger)) { - throw new InternalServerErrorException('Logger of the Application is not defined'); - } - - return $this->logger; - } - /** * Reloads the whole app instance */ public function reload() { - // The order of the following calls is important to ensure proper initialization - $this->loadConfigFiles(); - - $this->loadDatabase(); - $this->getMode()->determine($this->getBasePath()); - $this->determineURLPath(); + if ($this->getMode()->has(App\Mode::DBAVAILABLE)) { + $loader = new ConfigFileLoader($this->getBasePath(), $this->getMode()); + $this->config->getCache()->load($loader->loadCoreConfig('addon'), true); - Core\Config::load(); + $this->profiler->update( + $this->config->get('system', 'profiler', false), + $this->config->get('rendertime', 'callstack', false)); - if ($this->getMode()->has(App\Mode::DBAVAILABLE)) { Core\Hook::loadHooks(); - - $this->loadAddonConfig(); + $loader = new ConfigFileLoader($this->getBasePath(), $this->mode); + Core\Hook::callAll('load_config', $loader); } $this->loadDefaultTimezone(); Core\L10n::init(); - - $this->process_id = Core\System::processID('log'); - - Core\Logger::setLogger($this->logger); - } - - /** - * Load the configuration files - * - * First loads the default value for all the configuration keys, then the legacy configuration files, then the - * expected local.config.php - */ - private function loadConfigFiles() - { - $this->loadConfigFile($this->getBasePath() . '/config/defaults.config.php'); - $this->loadConfigFile($this->getBasePath() . '/config/settings.config.php'); - - // Legacy .htconfig.php support - if (file_exists($this->getBasePath() . '/.htpreconfig.php')) { - $a = $this; - include $this->getBasePath() . '/.htpreconfig.php'; - } - - // Legacy .htconfig.php support - if (file_exists($this->getBasePath() . '/.htconfig.php')) { - $a = $this; - - include $this->getBasePath() . '/.htconfig.php'; - - Core\Config::setConfigValue('database', 'hostname', $db_host); - Core\Config::setConfigValue('database', 'username', $db_user); - Core\Config::setConfigValue('database', 'password', $db_pass); - Core\Config::setConfigValue('database', 'database', $db_data); - $charset = Core\Config::getConfigValue('system', 'db_charset'); - if (isset($charset)) { - Core\Config::setConfigValue('database', 'charset', $charset); - } - - unset($db_host, $db_user, $db_pass, $db_data); - - if (isset($default_timezone)) { - Core\Config::setConfigValue('system', 'default_timezone', $default_timezone); - unset($default_timezone); - } - - if (isset($pidfile)) { - Core\Config::setConfigValue('system', 'pidfile', $pidfile); - unset($pidfile); - } - - if (isset($lang)) { - Core\Config::setConfigValue('system', 'language', $lang); - unset($lang); - } - } - - if (file_exists($this->getBasePath() . '/config/local.config.php')) { - $this->loadConfigFile($this->getBasePath() . '/config/local.config.php', true); - } elseif (file_exists($this->getBasePath() . '/config/local.ini.php')) { - $this->loadINIConfigFile($this->getBasePath() . '/config/local.ini.php', true); - } - } - - /** - * Tries to load the specified legacy configuration file into the App->config array. - * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config. - * - * @deprecated since version 2018.12 - * @param string $filepath - * @param bool $overwrite Force value overwrite if the config key already exists - * @throws Exception - */ - public function loadINIConfigFile($filepath, $overwrite = false) - { - if (!file_exists($filepath)) { - throw new Exception('Error parsing non-existent INI config file ' . $filepath); - } - - $contents = include($filepath); - - $config = parse_ini_string($contents, true, INI_SCANNER_TYPED); - - if ($config === false) { - throw new Exception('Error parsing INI config file ' . $filepath); - } - - Core\Config::loadConfigArray($config, $overwrite); - } - - /** - * Tries to load the specified configuration file into the App->config array. - * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config. - * - * The config format is PHP array and the template for configuration files is the following: - * - * [ - * 'key' => 'value', - * ], - * ]; - * - * @param string $filepath - * @param bool $overwrite Force value overwrite if the config key already exists - * @throws Exception - */ - public function loadConfigFile($filepath, $overwrite = false) - { - if (!file_exists($filepath)) { - throw new Exception('Error loading non-existent config file ' . $filepath); - } - - $config = include($filepath); - - if (!is_array($config)) { - throw new Exception('Error loading config file ' . $filepath); - } - - Core\Config::loadConfigArray($config, $overwrite); - } - - /** - * Loads addons configuration files - * - * First loads all activated addons default configuration through the load_config hook, then load the local.config.php - * again to overwrite potential local addon configuration. - */ - private function loadAddonConfig() - { - // Loads addons default config - Core\Hook::callAll('load_config'); - - // Load the local addon config file to overwritten default addon config values - if (file_exists($this->getBasePath() . '/config/addon.config.php')) { - $this->loadConfigFile($this->getBasePath() . '/config/addon.config.php', true); - } elseif (file_exists($this->getBasePath() . '/config/addon.ini.php')) { - $this->loadINIConfigFile($this->getBasePath() . '/config/addon.ini.php', true); - } } /** @@ -500,8 +386,8 @@ class App */ private function loadDefaultTimezone() { - if (Core\Config::getConfigValue('system', 'default_timezone')) { - $this->timezone = Core\Config::getConfigValue('system', 'default_timezone'); + if ($this->config->get('system', 'default_timezone')) { + $this->timezone = $this->config->get('system', 'default_timezone'); } else { global $default_timezone; $this->timezone = !empty($default_timezone) ? $default_timezone : 'UTC'; @@ -513,178 +399,26 @@ class App } /** - * Figure out if we are running at the top of a domain or in a sub-directory and adjust accordingly - */ - private function determineURLPath() - { - /* Relative script path to the web server root - * Not all of those $_SERVER properties can be present, so we do by inverse priority order - */ - $relative_script_path = ''; - $relative_script_path = defaults($_SERVER, 'REDIRECT_URL' , $relative_script_path); - $relative_script_path = defaults($_SERVER, 'REDIRECT_URI' , $relative_script_path); - $relative_script_path = defaults($_SERVER, 'REDIRECT_SCRIPT_URL', $relative_script_path); - $relative_script_path = defaults($_SERVER, 'SCRIPT_URL' , $relative_script_path); - $relative_script_path = defaults($_SERVER, 'REQUEST_URI' , $relative_script_path); - - $this->urlPath = Core\Config::getConfigValue('system', 'urlpath'); - - /* $relative_script_path gives /relative/path/to/friendica/module/parameter - * QUERY_STRING gives pagename=module/parameter - * - * To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING - */ - if (!empty($relative_script_path)) { - // Module - if (!empty($_SERVER['QUERY_STRING'])) { - $path = trim(rdirname($relative_script_path, substr_count(trim($_SERVER['QUERY_STRING'], '/'), '/') + 1), '/'); - } else { - // Root page - $path = trim($relative_script_path, '/'); - } - - if ($path && $path != $this->urlPath) { - $this->urlPath = $path; - } - } - } - - public function loadDatabase() - { - if (DBA::connected()) { - return; - } - - $db_host = Core\Config::getConfigValue('database', 'hostname'); - $db_user = Core\Config::getConfigValue('database', 'username'); - $db_pass = Core\Config::getConfigValue('database', 'password'); - $db_data = Core\Config::getConfigValue('database', 'database'); - $charset = Core\Config::getConfigValue('database', 'charset'); - - // Use environment variables for mysql if they are set beforehand - if (!empty(getenv('MYSQL_HOST')) - && !empty(getenv('MYSQL_USERNAME') || !empty(getenv('MYSQL_USER'))) - && getenv('MYSQL_PASSWORD') !== false - && !empty(getenv('MYSQL_DATABASE'))) - { - $db_host = getenv('MYSQL_HOST'); - if (!empty(getenv('MYSQL_PORT'))) { - $db_host .= ':' . getenv('MYSQL_PORT'); - } - if (!empty(getenv('MYSQL_USERNAME'))) { - $db_user = getenv('MYSQL_USERNAME'); - } else { - $db_user = getenv('MYSQL_USER'); - } - $db_pass = (string) getenv('MYSQL_PASSWORD'); - $db_data = getenv('MYSQL_DATABASE'); - } - - $stamp1 = microtime(true); - - if (DBA::connect($db_host, $db_user, $db_pass, $db_data, $charset)) { - // Loads DB_UPDATE_VERSION constant - Database\DBStructure::definition(false); - } - - unset($db_host, $db_user, $db_pass, $db_data, $charset); - - $this->saveTimestamp($stamp1, 'network'); - } - - /** - * @brief Returns the base filesystem path of the App - * - * It first checks for the internal variable, then for DOCUMENT_ROOT and - * finally for PWD - * + * Returns the scheme of the current call * @return string - * @throws InternalServerErrorException - */ - public function getBasePath() - { - $basepath = $this->basePath; - - if (!$basepath) { - $basepath = Core\Config::get('system', 'basepath'); - } - - if (!$basepath && !empty($_SERVER['DOCUMENT_ROOT'])) { - $basepath = $_SERVER['DOCUMENT_ROOT']; - } - - if (!$basepath && !empty($_SERVER['PWD'])) { - $basepath = $_SERVER['PWD']; - } - - return self::getRealPath($basepath); - } - - /** - * @brief Returns a normalized file path - * - * This is a wrapper for the "realpath" function. - * That function cannot detect the real path when some folders aren't readable. - * Since this could happen with some hosters we need to handle this. * - * @param string $path The path that is about to be normalized - * @return string normalized path - when possible + * @deprecated 2019.06 - use BaseURL->getScheme() instead */ - public static function getRealPath($path) - { - $normalized = realpath($path); - - if (!is_bool($normalized)) { - return $normalized; - } else { - return $path; - } - } - public function getScheme() { - return $this->scheme; + return $this->baseURL->getScheme(); } /** - * @brief Retrieves the Friendica instance base URL + * Retrieves the Friendica instance base URL * - * This function assembles the base URL from multiple parts: - * - Protocol is determined either by the request or a combination of - * system.ssl_policy and the $ssl parameter. - * - Host name is determined either by system.hostname or inferred from request - * - Path is inferred from SCRIPT_NAME + * @param bool $ssl Whether to append http or https under BaseURL::SSL_POLICY_SELFSIGN * - * Note: $ssl parameter value doesn't directly correlate with the resulting protocol - * - * @param bool $ssl Whether to append http or https under SSL_POLICY_SELFSIGN * @return string Friendica server base URL - * @throws InternalServerErrorException */ public function getBaseURL($ssl = false) { - $scheme = $this->scheme; - - if (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_FULL) { - $scheme = 'https'; - } - - // Basically, we have $ssl = true on any links which can only be seen by a logged in user - // (and also the login link). Anything seen by an outsider will have it turned off. - - if (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) { - if ($ssl) { - $scheme = 'https'; - } else { - $scheme = 'http'; - } - } - - if (Core\Config::get('config', 'hostname') != '') { - $this->hostname = Core\Config::get('config', 'hostname'); - } - - return $scheme . '://' . $this->hostname . (!empty($this->getURLPath()) ? '/' . $this->getURLPath() : '' ); + return $this->baseURL->get($ssl); } /** @@ -693,55 +427,36 @@ class App * Clears the baseurl cache to prevent inconsistencies * * @param string $url - * @throws InternalServerErrorException + * + * @deprecated 2019.06 - use BaseURL->saveByURL($url) instead */ public function setBaseURL($url) { - $parsed = @parse_url($url); - $hostname = ''; - - if (!empty($parsed)) { - if (!empty($parsed['scheme'])) { - $this->scheme = $parsed['scheme']; - } - - if (!empty($parsed['host'])) { - $hostname = $parsed['host']; - } - - if (!empty($parsed['port'])) { - $hostname .= ':' . $parsed['port']; - } - if (!empty($parsed['path'])) { - $this->urlPath = trim($parsed['path'], '\\/'); - } - - if (file_exists($this->getBasePath() . '/.htpreconfig.php')) { - include $this->getBasePath() . '/.htpreconfig.php'; - } - - if (Core\Config::get('config', 'hostname') != '') { - $this->hostname = Core\Config::get('config', 'hostname'); - } - - if (!isset($this->hostname) || ($this->hostname == '')) { - $this->hostname = $hostname; - } - } + $this->baseURL->saveByURL($url); } + /** + * Returns the current hostname + * + * @return string + * + * @deprecated 2019.06 - use BaseURL->getHostname() instead + */ public function getHostName() { - if (Core\Config::get('config', 'hostname') != '') { - $this->hostname = Core\Config::get('config', 'hostname'); - } - - return $this->hostname; + return $this->baseURL->getHostname(); } + /** + * Returns the sub-path of the full URL + * + * @return string + * + * @deprecated 2019.06 - use BaseURL->getUrlPath() instead + */ public function getURLPath() { - return $this->urlPath; + return $this->baseURL->getUrlPath(); } /** @@ -767,14 +482,14 @@ class App $interval = 40000; } - // compose the page title from the sitename and the - // current module called - if (!$this->module == '') { - $this->page['title'] = Core\Config::getConfigValue('config', 'sitename') . ' (' . $this->module . ')'; - } else { - $this->page['title'] = Core\Config::getConfigValue('config', 'sitename'); + // Default title: current module called + if (empty($this->page['title']) && $this->module) { + $this->page['title'] = ucfirst($this->module); } + // Prepend the sitename to the page title + $this->page['title'] = $this->config->get('config', 'sitename', '') . (!empty($this->page['title']) ? ' | ' . $this->page['title'] : ''); + if (!empty(Core\Renderer::$theme['stylesheet'])) { $stylesheet = Core\Renderer::$theme['stylesheet']; } else { @@ -783,12 +498,12 @@ class App $this->registerStylesheet($stylesheet); - $shortcut_icon = Core\Config::get('system', 'shortcut_icon'); + $shortcut_icon = $this->config->get('system', 'shortcut_icon'); if ($shortcut_icon == '') { $shortcut_icon = 'images/friendica-32.png'; } - $touch_icon = Core\Config::get('system', 'touch_icon'); + $touch_icon = $this->config->get('system', 'touch_icon'); if ($touch_icon == '') { $touch_icon = 'images/friendica-128.png'; } @@ -801,16 +516,13 @@ class App * being first */ $this->page['htmlhead'] = Core\Renderer::replaceMacros($tpl, [ - '$baseurl' => $this->getBaseURL(), '$local_user' => local_user(), '$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION, '$delitem' => Core\L10n::t('Delete this item?'), - '$showmore' => Core\L10n::t('show more'), - '$showfewer' => Core\L10n::t('show fewer'), '$update_interval' => $interval, '$shortcut_icon' => $shortcut_icon, '$touch_icon' => $touch_icon, - '$block_public' => intval(Core\Config::get('system', 'block_public')), + '$block_public' => intval($this->config->get('system', 'block_public')), '$stylesheets' => $this->stylesheets, ]) . $this->page['htmlhead']; } @@ -856,7 +568,6 @@ class App $tpl = Core\Renderer::getMarkupTemplate('footer.tpl'); $this->page['footer'] = Core\Renderer::replaceMacros($tpl, [ - '$baseurl' => $this->getBaseURL(), '$footerScripts' => $this->footerScripts, ]) . $this->page['footer']; } @@ -867,7 +578,7 @@ class App * @param string $origURL * * @return string The cleaned url - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function removeBaseURL($origURL) { @@ -884,46 +595,11 @@ class App } } - /** - * Saves a timestamp for a value - f.e. a call - * Necessary for profiling Friendica - * - * @param int $timestamp the Timestamp - * @param string $value A value to profile - */ - public function saveTimestamp($timestamp, $value) - { - $profiler = Core\Config::getConfigValue('system', 'profiler'); - - if (!isset($profiler) || !$profiler) { - return; - } - - $duration = (float) (microtime(true) - $timestamp); - - if (!isset($this->performance[$value])) { - // Prevent ugly E_NOTICE - $this->performance[$value] = 0; - } - - $this->performance[$value] += (float) $duration; - $this->performance['marktime'] += (float) $duration; - - $callstack = Core\System::callstack(); - - if (!isset($this->callstack[$value][$callstack])) { - // Prevent ugly E_NOTICE - $this->callstack[$value][$callstack] = 0; - } - - $this->callstack[$value][$callstack] += (float) $duration; - } - /** * Returns the current UserAgent as a String * * @return string the UserAgent as a String - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function getUserAgent() { @@ -935,48 +611,32 @@ class App $this->getBaseURL(); } - /** - * Checks, if the call is from the Friendica App - * - * Reason: - * The friendica client has problems with the GUID in the notify. this is some workaround - */ - private function checkFriendicaApp() - { - // Friendica-Client - $this->isFriendicaApp = isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] == 'Apache-HttpClient/UNAVAILABLE (java 1.4)'; - } - - /** - * Is the call via the Friendica app? (not a "normale" call) - * - * @return bool true if it's from the Friendica app - */ - public function isFriendicaApp() - { - return $this->isFriendicaApp; - } - /** * @brief Checks if the site is called via a backend process * * This isn't a perfect solution. But we need this check very early. * So we cannot wait until the modules are loaded. * - * @param string $backend true, if the backend flag was set during App initialization - * + * @param string $module + * @return bool */ - private function checkBackend($backend) { + private function checkBackend($module) { static $backends = [ '_well_known', 'api', 'dfrn_notify', + 'feed', 'fetch', + 'followers', + 'following', 'hcard', 'hostxrd', + 'inbox', + 'manifest', 'nodeinfo', 'noscrape', - 'p', + 'objects', + 'outbox', 'poco', 'post', 'proxy', @@ -990,7 +650,7 @@ class App ]; // Check if current module is in backend or backend flag is set - $this->isBackend = (in_array($this->module, $backends) || $backend || $this->isBackend); + return in_array($module, $backends); } /** @@ -1018,13 +678,13 @@ class App * if ($this->is_backend()) { $process = 'backend'; - $max_processes = Core\Config::get('system', 'max_processes_backend'); + $max_processes = $this->config->get('system', 'max_processes_backend'); if (intval($max_processes) == 0) { $max_processes = 5; } } else { $process = 'frontend'; - $max_processes = Core\Config::get('system', 'max_processes_frontend'); + $max_processes = $this->config->get('system', 'max_processes_frontend'); if (intval($max_processes) == 0) { $max_processes = 20; } @@ -1047,11 +707,11 @@ class App * @brief Checks if the minimal memory is reached * * @return bool Is the memory limit reached? - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function isMinMemoryReached() { - $min_memory = Core\Config::get('system', 'min_memory', 0); + $min_memory = $this->config->get('system', 'min_memory', 0); if ($min_memory == 0) { return false; } @@ -1092,19 +752,19 @@ class App * @brief Checks if the maximum load is reached * * @return bool Is the load reached? - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function isMaxLoadReached() { if ($this->isBackend()) { $process = 'backend'; - $maxsysload = intval(Core\Config::get('system', 'maxloadavg')); + $maxsysload = intval($this->config->get('system', 'maxloadavg')); if ($maxsysload < 1) { $maxsysload = 50; } } else { $process = 'frontend'; - $maxsysload = intval(Core\Config::get('system', 'maxloadavg_frontend')); + $maxsysload = intval($this->config->get('system', 'maxloadavg_frontend')); if ($maxsysload < 1) { $maxsysload = 50; } @@ -1125,7 +785,7 @@ class App * * @param string $command The command to execute * @param array $args Arguments to pass to the command ( [ 'key' => value, 'key2' => value2, ... ] - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function proc_run($command, $args) { @@ -1133,7 +793,7 @@ class App return; } - $cmdline = Core\Config::getConfigValue('config', 'php_path', 'php') . ' ' . escapeshellarg($command); + $cmdline = $this->config->get('config', 'php_path', 'php') . ' ' . escapeshellarg($command); foreach ($args as $key => $value) { if (!is_null($value) && is_bool($value) && !$value) { @@ -1162,133 +822,17 @@ class App proc_close($resource); } - /** - * @brief Returns the system user that is executing the script - * - * This mostly returns something like "www-data". - * - * @return string system username - */ - private static function getSystemUser() - { - if (!function_exists('posix_getpwuid') || !function_exists('posix_geteuid')) { - return ''; - } - - $processUser = posix_getpwuid(posix_geteuid()); - return $processUser['name']; - } - - /** - * @brief Checks if a given directory is usable for the system - * - * @param $directory - * @param bool $check_writable - * @return boolean the directory is usable - * @throws Exception - */ - public static function isDirectoryUsable($directory, $check_writable = true) - { - if ($directory == '') { - Core\Logger::log('Directory is empty. This shouldn\'t happen.', Core\Logger::DEBUG); - return false; - } - - if (!file_exists($directory)) { - Core\Logger::log('Path "' . $directory . '" does not exist for user ' . self::getSystemUser(), Core\Logger::DEBUG); - return false; - } - - if (is_file($directory)) { - Core\Logger::log('Path "' . $directory . '" is a file for user ' . self::getSystemUser(), Core\Logger::DEBUG); - return false; - } - - if (!is_dir($directory)) { - Core\Logger::log('Path "' . $directory . '" is not a directory for user ' . self::getSystemUser(), Core\Logger::DEBUG); - return false; - } - - if ($check_writable && !is_writable($directory)) { - Core\Logger::log('Path "' . $directory . '" is not writable for user ' . self::getSystemUser(), Core\Logger::DEBUG); - return false; - } - - return true; - } - - /** - * Retrieves a value from the user config cache - * - * @param int $uid User Id - * @param string $cat Config category - * @param string $k Config key - * @param mixed $default Default value if key isn't set - * - * @return string The value of the config entry - */ - public function getPConfigValue($uid, $cat, $k, $default = null) - { - $return = $default; - - if (isset($this->config[$uid][$cat][$k])) { - $return = $this->config[$uid][$cat][$k]; - } - - return $return; - } - - /** - * Sets a value in the user config cache - * - * Accepts raw output from the pconfig table - * - * @param int $uid User Id - * @param string $cat Config category - * @param string $k Config key - * @param mixed $v Value to set - */ - public function setPConfigValue($uid, $cat, $k, $v) - { - // Only arrays are serialized in database, so we have to unserialize sparingly - $value = is_string($v) && preg_match("|^a:[0-9]+:{.*}$|s", $v) ? unserialize($v) : $v; - - if (!isset($this->config[$uid]) || !is_array($this->config[$uid])) { - $this->config[$uid] = []; - } - - if (!isset($this->config[$uid][$cat]) || !is_array($this->config[$uid][$cat])) { - $this->config[$uid][$cat] = []; - } - - $this->config[$uid][$cat][$k] = $value; - } - - /** - * Deletes a value from the user config cache - * - * @param int $uid User Id - * @param string $cat Config category - * @param string $k Config key - */ - public function deletePConfigValue($uid, $cat, $k) - { - if (isset($this->config[$uid][$cat][$k])) { - unset($this->config[$uid][$cat][$k]); - } - } - /** * Generates the site's default sender email address * * @return string - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function getSenderEmailAddress() { - $sender_email = Core\Config::get('config', 'sender_email'); + $sender_email = $this->config->get('config', 'sender_email'); if (empty($sender_email)) { - $hostname = $this->getHostName(); + $hostname = $this->baseURL->getHostname(); if (strpos($hostname, ':')) { $hostname = substr($hostname, 0, strpos($hostname, ':')); } @@ -1303,7 +847,7 @@ class App * Returns the current theme name. * * @return string the name of the current theme - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function getCurrentTheme() { @@ -1330,7 +874,7 @@ class App */ private function computeCurrentTheme() { - $system_theme = Core\Config::get('system', 'theme'); + $system_theme = $this->config->get('system', 'theme'); if (!$system_theme) { throw new Exception(Core\L10n::t('No system theme config value set.')); } @@ -1338,8 +882,6 @@ class App // Sane default $this->currentTheme = $system_theme; - $allowed_themes = explode(',', Core\Config::get('system', 'allowed_themes', $system_theme)); - $page_theme = null; // Find the theme that belongs to the user whose stuff we are looking at if ($this->profile_uid && ($this->profile_uid != local_user())) { @@ -1355,7 +897,7 @@ class App // Specific mobile theme override if (($this->is_mobile || $this->is_tablet) && Core\Session::get('show-mobile', true)) { - $system_mobile_theme = Core\Config::get('system', 'mobile-theme'); + $system_mobile_theme = $this->config->get('system', 'mobile-theme'); $user_mobile_theme = Core\Session::get('mobile-theme', $system_mobile_theme); // --- means same mobile theme as desktop @@ -1370,8 +912,9 @@ class App $theme_name = $user_theme; } + $theme_name = Strings::sanitizeFilePathItem($theme_name); if ($theme_name - && in_array($theme_name, $allowed_themes) + && in_array($theme_name, Theme::getAllowedList()) && (file_exists('view/theme/' . $theme_name . '/style.css') || file_exists('view/theme/' . $theme_name . '/style.php')) ) { @@ -1385,7 +928,7 @@ class App * Provide a sane default if nothing is chosen or the specified theme does not exist. * * @return string - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function getCurrentThemeStylesheetPath() { @@ -1426,7 +969,7 @@ class App */ public function checkURL() { - $url = Core\Config::get('system', 'url'); + $url = $this->config->get('system', 'url'); // if the url isn't set or the stored url is radically different // than the currently visited url, store the current value accordingly. @@ -1434,8 +977,8 @@ class App // and www.example.com vs example.com. // We will only change the url to an ip address if there is no existing setting - if (empty($url) || (!Util\Strings::compareLink($url, $this->getBaseURL())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $this->getHostName()))) { - Core\Config::set('system', 'url', $this->getBaseURL()); + if (empty($url) || (!Util\Strings::compareLink($url, $this->getBaseURL())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $this->baseURL->getHostname()))) { + $this->config->set('system', 'url', $this->getBaseURL()); } } @@ -1451,7 +994,9 @@ class App { // Missing DB connection: ERROR if ($this->getMode()->has(App\Mode::LOCALCONFIGPRESENT) && !$this->getMode()->has(App\Mode::DBAVAILABLE)) { - Core\System::httpExit(500, ['title' => 'Error 500 - Internal Server Error', 'description' => 'Apologies but the website is unavailable at the moment.']); + Module\Special\HTTPException::rawContent( + new HTTPException\InternalServerErrorException('Apologies but the website is unavailable at the moment.') + ); } // Max Load Average reached: ERROR @@ -1459,19 +1004,14 @@ class App header('Retry-After: 120'); header('Refresh: 120; url=' . $this->getBaseURL() . "/" . $this->query_string); - Core\System::httpExit(503, ['title' => 'Error 503 - Service Temporarily Unavailable', 'description' => 'Core\System is currently overloaded. Please try again later.']); - } - - if (strstr($this->query_string, '.well-known/host-meta') && ($this->query_string != '.well-known/host-meta')) { - Core\System::httpExit(404); + Module\Special\HTTPException::rawContent( + new HTTPException\ServiceUnavailableException('The node is currently overloaded. Please try again later.') + ); } if (!$this->getMode()->isInstall()) { // Force SSL redirection - if (Core\Config::get('system', 'force_ssl') && ($this->getScheme() == "http") - && intval(Core\Config::get('system', 'ssl_policy')) == SSL_POLICY_FULL - && strpos($this->getBaseURL(), 'https://') === 0 - && $_SERVER['REQUEST_METHOD'] == 'GET') { + if ($this->baseURL->checkRedirectHttps()) { header('HTTP/1.1 302 Moved Temporarily'); header('Location: ' . $this->getBaseURL() . '/' . $this->query_string); exit(); @@ -1485,7 +1025,7 @@ class App if (!$this->isBackend()) { $stamp1 = microtime(true); session_start(); - $this->saveTimestamp($stamp1, 'parser'); + $this->profiler->saveTimestamp($stamp1, 'parser', Core\System::callstack()); Core\L10n::setSessionVariable(); Core\L10n::setLangFromSession(); } else { @@ -1493,6 +1033,13 @@ class App Core\Worker::executeIfIdle(); } + if ($this->getMode()->isNormal()) { + $requester = HTTPSignature::getSigner('', $_SERVER); + if (!empty($requester)) { + Profile::addVisitorCookieForHandle($requester); + } + } + // ZRL if (!empty($_GET['zrl']) && $this->getMode()->isNormal()) { $this->query_string = Model\Profile::stripZrls($this->query_string); @@ -1501,16 +1048,19 @@ class App // Valid profile links contain a path with "/profile/" and no query parameters if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == "") && strstr(parse_url($_GET['zrl'], PHP_URL_PATH), "/profile/")) { - if (defaults($_SESSION, "visitor_home", "") != $_GET["zrl"]) { - $_SESSION['my_url'] = $_GET['zrl']; - $_SESSION['authenticated'] = 0; + if (Core\Session::get('visitor_home') != $_GET["zrl"]) { + Core\Session::set('my_url', $_GET['zrl']); + Core\Session::set('authenticated', 0); } + Model\Profile::zrlInit($this); } else { // Someone came with an invalid parameter, maybe as a DDoS attempt // We simply stop processing here Core\Logger::log("Invalid ZRL parameter " . $_GET['zrl'], Core\Logger::DEBUG); - Core\System::httpExit(403, ['title' => '403 Forbidden']); + Module\Special\HTTPException::rawContent( + new HTTPException\ForbiddenException() + ); } } } @@ -1527,9 +1077,9 @@ class App header('X-Account-Management-Status: none'); } - $_SESSION['sysmsg'] = defaults($_SESSION, 'sysmsg' , []); - $_SESSION['sysmsg_info'] = defaults($_SESSION, 'sysmsg_info' , []); - $_SESSION['last_updated'] = defaults($_SESSION, 'last_updated', []); + $_SESSION['sysmsg'] = Core\Session::get('sysmsg', []); + $_SESSION['sysmsg_info'] = Core\Session::get('sysmsg_info', []); + $_SESSION['last_updated'] = Core\Session::get('last_updated', []); /* * check_config() is responsible for running update scripts. These automatically @@ -1539,13 +1089,13 @@ class App // in install mode, any url loads install module // but we need "view" module for stylesheet - if ($this->getMode()->isInstall() && $this->module != 'view') { - $this->module = 'install'; - } elseif (!$this->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $this->module != 'view') { - $this->module = 'maintenance'; + if ($this->getMode()->isInstall() && $this->module !== 'install') { + $this->internalRedirect('install'); + } elseif (!$this->getMode()->isInstall() && !$this->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $this->module !== 'maintenance') { + $this->internalRedirect('maintenance'); } else { $this->checkURL(); - Core\Update::check(false); + Core\Update::check($this->getBasePath(), false, $this->getMode()); Core\Addon::loadAddons(); Core\Hook::loadHooks(); } @@ -1563,110 +1113,110 @@ class App 'title' => '' ]; - if (strlen($this->module)) { - // Compatibility with the Android Diaspora client - if ($this->module == 'stream') { - $this->internalRedirect('network?f=&order=post'); - } + // Compatibility with the Android Diaspora client + if ($this->module == 'stream') { + $this->internalRedirect('network?order=post'); + } - if ($this->module == 'conversations') { - $this->internalRedirect('message'); - } + if ($this->module == 'conversations') { + $this->internalRedirect('message'); + } - if ($this->module == 'commented') { - $this->internalRedirect('network?f=&order=comment'); - } + if ($this->module == 'commented') { + $this->internalRedirect('network?order=comment'); + } - if ($this->module == 'liked') { - $this->internalRedirect('network?f=&order=comment'); - } + if ($this->module == 'liked') { + $this->internalRedirect('network?order=comment'); + } - if ($this->module == 'activity') { - $this->internalRedirect('network/?f=&conv=1'); - } + if ($this->module == 'activity') { + $this->internalRedirect('network?conv=1'); + } - if (($this->module == 'status_messages') && ($this->cmd == 'status_messages/new')) { - $this->internalRedirect('bookmarklet'); - } + if (($this->module == 'status_messages') && ($this->cmd == 'status_messages/new')) { + $this->internalRedirect('bookmarklet'); + } - if (($this->module == 'user') && ($this->cmd == 'user/edit')) { - $this->internalRedirect('settings'); - } + if (($this->module == 'user') && ($this->cmd == 'user/edit')) { + $this->internalRedirect('settings'); + } - if (($this->module == 'tag_followings') && ($this->cmd == 'tag_followings/manage')) { - $this->internalRedirect('search'); - } + if (($this->module == 'tag_followings') && ($this->cmd == 'tag_followings/manage')) { + $this->internalRedirect('search'); + } - // Compatibility with the Firefox App - if (($this->module == "users") && ($this->cmd == "users/sign_in")) { - $this->module = "login"; - } + // Compatibility with the Firefox App + if (($this->module == "users") && ($this->cmd == "users/sign_in")) { + $this->module = "login"; + } - $privateapps = Core\Config::get('config', 'private_addons', false); - if (Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) { - //Check if module is an app and if public access to apps is allowed or not - if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) { - info(Core\L10n::t("You must be logged in to use addons. ")); - } else { - include_once "addon/{$this->module}/{$this->module}.php"; - if (function_exists($this->module . '_module')) { - LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php"); - $this->module_class = 'Friendica\\LegacyModule'; - $this->module_loaded = true; - } - } - } + /* + * ROUTING + * + * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the + * post() and/or content() static methods can be respectively called to produce a data change or an output. + */ - // Controller class routing - if (! $this->module_loaded && class_exists('Friendica\\Module\\' . ucfirst($this->module))) { - $this->module_class = 'Friendica\\Module\\' . ucfirst($this->module); - $this->module_loaded = true; - } + // First we try explicit routes defined in App\Router + $this->router->collectRoutes(); - /* If not, next look for a 'standard' program module in the 'mod' directory - * We emulate a Module class through the LegacyModule class - */ - if (! $this->module_loaded && file_exists("mod/{$this->module}.php")) { - LegacyModule::setModuleFile("mod/{$this->module}.php"); - $this->module_class = 'Friendica\\LegacyModule'; - $this->module_loaded = true; - } + $data = $this->router->getRouteCollector(); + Hook::callAll('route_collection', $data); - /* The URL provided does not resolve to a valid module. - * - * On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'. - * We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic - - * we are going to trap this and redirect back to the requested page. As long as you don't have a critical error on your page - * this will often succeed and eventually do the right thing. - * - * Otherwise we are going to emit a 404 not found. - */ - if (! $this->module_loaded) { - // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit. - if (!empty($_SERVER['QUERY_STRING']) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) { - exit(); - } + $this->module_class = $this->router->getModuleClass($this->cmd); - if (!empty($_SERVER['QUERY_STRING']) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) { - Core\Logger::log('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']); - $this->internalRedirect($_SERVER['REQUEST_URI']); + // Then we try addon-provided modules that we wrap in the LegacyModule class + if (!$this->module_class && Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) { + //Check if module is an app and if public access to apps is allowed or not + $privateapps = $this->config->get('config', 'private_addons', false); + if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) { + info(Core\L10n::t("You must be logged in to use addons. ")); + } else { + include_once "addon/{$this->module}/{$this->module}.php"; + if (function_exists($this->module . '_module')) { + LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php"); + $this->module_class = LegacyModule::class; } + } + } - Core\Logger::log('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' . $_SERVER['QUERY_STRING'], Core\Logger::DEBUG); + /* Finally, we look for a 'standard' program module in the 'mod' directory + * We emulate a Module class through the LegacyModule class + */ + if (!$this->module_class && file_exists("mod/{$this->module}.php")) { + LegacyModule::setModuleFile("mod/{$this->module}.php"); + $this->module_class = LegacyModule::class; + } - header($_SERVER["SERVER_PROTOCOL"] . ' 404 ' . Core\L10n::t('Not Found')); - $tpl = Core\Renderer::getMarkupTemplate("404.tpl"); - $this->page['content'] = Core\Renderer::replaceMacros($tpl, [ - '$message' => Core\L10n::t('Page not found.') - ]); + /* The URL provided does not resolve to a valid module. + * + * On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'. + * We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic - + * we are going to trap this and redirect back to the requested page. As long as you don't have a critical error on your page + * this will often succeed and eventually do the right thing. + * + * Otherwise we are going to emit a 404 not found. + */ + if (!$this->module_class) { + // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit. + if (!empty($_SERVER['QUERY_STRING']) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) { + exit(); } - } - $content = ''; + if (!empty($_SERVER['QUERY_STRING']) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && isset($dreamhost_error_hack)) { + Core\Logger::log('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']); + $this->internalRedirect($_SERVER['REQUEST_URI']); + } + + Core\Logger::log('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' . $_SERVER['QUERY_STRING'], Core\Logger::DEBUG); + + $this->module_class = Module\PageNotFound::class; + } // Initialize module that can set the current theme in the init() method, either directly or via App->profile_uid - if ($this->module_loaded) { - $this->page['page_title'] = $this->module; + $this->page['page_title'] = $this->module; + try { $placeholder = ''; Core\Hook::callAll($this->module . '_mod_init', $placeholder); @@ -1675,41 +1225,41 @@ class App // "rawContent" is especially meant for technical endpoints. // This endpoint doesn't need any theme initialization or other comparable stuff. - if (!$this->error) { - call_user_func([$this->module_class, 'rawContent']); - } - } + call_user_func([$this->module_class, 'rawContent']); - // Load current theme info after module has been initialized as theme could have been set in module - $theme_info_file = 'view/theme/' . $this->getCurrentTheme() . '/theme.php'; - if (file_exists($theme_info_file)) { - require_once $theme_info_file; - } + // Load current theme info after module has been initialized as theme could have been set in module + $theme_info_file = 'view/theme/' . $this->getCurrentTheme() . '/theme.php'; + if (file_exists($theme_info_file)) { + require_once $theme_info_file; + } - if (function_exists(str_replace('-', '_', $this->getCurrentTheme()) . '_init')) { - $func = str_replace('-', '_', $this->getCurrentTheme()) . '_init'; - $func($this); - } + if (function_exists(str_replace('-', '_', $this->getCurrentTheme()) . '_init')) { + $func = str_replace('-', '_', $this->getCurrentTheme()) . '_init'; + $func($this); + } - if ($this->module_loaded) { - if (! $this->error && $_SERVER['REQUEST_METHOD'] === 'POST') { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { Core\Hook::callAll($this->module . '_mod_post', $_POST); call_user_func([$this->module_class, 'post']); } - if (! $this->error) { - Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder); - call_user_func([$this->module_class, 'afterpost']); - } + Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder); + call_user_func([$this->module_class, 'afterpost']); + } catch(HTTPException $e) { + Module\Special\HTTPException::rawContent($e); + } - if (! $this->error) { - $arr = ['content' => $content]; - Core\Hook::callAll($this->module . '_mod_content', $arr); - $content = $arr['content']; - $arr = ['content' => call_user_func([$this->module_class, 'content'])]; - Core\Hook::callAll($this->module . '_mod_aftercontent', $arr); - $content .= $arr['content']; - } + $content = ''; + + try { + $arr = ['content' => $content]; + Core\Hook::callAll($this->module . '_mod_content', $arr); + $content = $arr['content']; + $arr = ['content' => call_user_func([$this->module_class, 'content'])]; + Core\Hook::callAll($this->module . '_mod_aftercontent', $arr); + $content .= $arr['content']; + } catch(HTTPException $e) { + $content = Module\Special\HTTPException::content($e); } // initialise content region @@ -1733,16 +1283,10 @@ class App */ $this->initFooter(); - /* now that we've been through the module content, see if the page reported - * a permission problem and if so, a 403 response would seem to be in order. - */ - if (stristr(implode("", $_SESSION['sysmsg']), Core\L10n::t('Permission denied'))) { - header($_SERVER["SERVER_PROTOCOL"] . ' 403 ' . Core\L10n::t('Permission denied.')); + if (!$this->isAjax()) { + Core\Hook::callAll('page_end', $this->page['content']); } - // Report anything which needs to be communicated in the notification area (before the main body) - Core\Hook::callAll('page_end', $this->page['content']); - // Add the navigation (menu) template if ($this->module != 'install' && $this->module != 'maintenance') { $this->page['htmlhead'] .= Core\Renderer::replaceMacros(Core\Renderer::getMarkupTemplate('nav_head.tpl'), []); @@ -1787,7 +1331,7 @@ class App header("X-Friendica-Version: " . FRIENDICA_VERSION); header("Content-type: text/html; charset=utf-8"); - if (Core\Config::get('system', 'hsts') && (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_FULL)) { + if ($this->config->get('system', 'hsts') && ($this->baseURL->getSSLPolicy() == BaseUrl::SSL_POLICY_FULL)) { header("Strict-Transport-Security: max-age=31536000"); } @@ -1830,12 +1374,12 @@ class App * @param string $toUrl The destination URL (Default is empty, which is the default page of the Friendica node) * @param bool $ssl if true, base URL will try to get called with https:// (works just for relative paths) * - * @throws InternalServerErrorException In Case the given URL is not relative to the Friendica node + * @throws HTTPException\InternalServerErrorException In Case the given URL is not relative to the Friendica node */ public function internalRedirect($toUrl = '', $ssl = false) { if (!empty(parse_url($toUrl, PHP_URL_SCHEME))) { - throw new InternalServerErrorException("'$toUrl is not a relative path, please use System::externalRedirectTo"); + throw new HTTPException\InternalServerErrorException("'$toUrl is not a relative path, please use System::externalRedirectTo"); } $redirectTo = $this->getBaseURL($ssl) . '/' . ltrim($toUrl, '/'); @@ -1847,7 +1391,7 @@ class App * Should only be used if it isn't clear if the URL is either internal or external * * @param string $toUrl The target URL - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public function redirect($toUrl) {