From fcb1a78352fe4db9c895e1b29164574030afe13c Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sun, 11 Aug 2019 18:01:11 -0400 Subject: [PATCH] Revert "Introduce Arguments / Module class" --- index.php | 6 +- src/App.php | 331 ++++++++++++++++++++++++-------- src/App/Arguments.php | 194 ------------------- src/App/Mode.php | 70 ++++--- src/App/Module.php | 279 --------------------------- src/Core/Installer.php | 4 +- src/Model/Profile.php | 23 +++ src/Module/Install.php | 4 +- static/dependencies.config.php | 52 ++--- tests/src/App/ArgumentsTest.php | 242 ----------------------- tests/src/App/ModeTest.php | 47 ++--- tests/src/App/ModuleTest.php | 189 ------------------ 12 files changed, 358 insertions(+), 1083 deletions(-) delete mode 100644 src/App/Arguments.php delete mode 100644 src/App/Module.php delete mode 100644 tests/src/App/ArgumentsTest.php delete mode 100644 tests/src/App/ModuleTest.php diff --git a/index.php b/index.php index 1e6439e03..50e553bcd 100644 --- a/index.php +++ b/index.php @@ -18,8 +18,4 @@ $dice = (new Dice())->addRules(include __DIR__ . '/static/dependencies.config.ph $a = \Friendica\BaseObject::getApp(); -$a->runFrontend( - $dice->create(\Friendica\App\Module::class), - $dice->create(\Friendica\App\Router::class), - $dice->create(\Friendica\Core\Config\PConfiguration::class) -); +$a->runFrontend(); diff --git a/src/App.php b/src/App.php index 98605da2a..bf9e1296c 100644 --- a/src/App.php +++ b/src/App.php @@ -8,11 +8,9 @@ use Detection\MobileDetect; use DOMDocument; use DOMXPath; use Exception; -use Friendica\App\Arguments; -use Friendica\App\Module; use Friendica\Core\Config\Cache\ConfigCache; use Friendica\Core\Config\Configuration; -use Friendica\Core\Config\PConfiguration; +use Friendica\Core\Hook; use Friendica\Core\L10n\L10n; use Friendica\Core\System; use Friendica\Core\Theme; @@ -20,7 +18,6 @@ use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\Model\Profile; use Friendica\Module\Login; -use Friendica\Module\Special\HTTPException as ModuleHTTPException; use Friendica\Network\HTTPException; use Friendica\Util\BaseURL; use Friendica\Util\ConfigFileLoader; @@ -44,7 +41,7 @@ use Psr\Log\LoggerInterface; */ class App { - /** @deprecated 2019.09 - use App\Arguments->getQueryString() */ + public $module_class = null; public $query_string = ''; public $page = []; public $profile; @@ -56,13 +53,9 @@ class App public $page_contact; public $content; public $data = []; - /** @deprecated 2019.09 - use App\Arguments->getCommand() */ public $cmd = ''; - /** @deprecated 2019.09 - use App\Arguments->getArgv() or Arguments->get() */ public $argv; - /** @deprecated 2019.09 - use App\Arguments->getArgc() */ public $argc; - /** @deprecated 2019.09 - Use App\Module->getName() instead */ public $module; public $timezone; public $interactive = true; @@ -100,8 +93,6 @@ class App /** * @var bool true, if the call is from an backend node (f.e. worker) - * - * @deprecated 2019.09 - use App\Module->isBackend() instead */ private $isBackend; @@ -145,16 +136,6 @@ class App */ private $l10n; - /** - * @var App\Arguments - */ - private $args; - - /** - * @var App\Module - */ - private $moduleClass; - /** * Returns the current config cache of this node * @@ -216,16 +197,6 @@ class App return $this->mode; } - /** - * Returns the Database of the Application - * - * @return Database - */ - public function getDBA() - { - return $this->database; - } - /** * Register a stylesheet file path to be included in the tag of every page. * Inclusion is done in App->initHead(). @@ -276,7 +247,7 @@ class App * * @throws Exception if the Basepath is not usable */ - public function __construct(Database $database, Configuration $config, App\Mode $mode, App\Router $router, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, Module $module) + public function __construct(Database $database, Configuration $config, App\Mode $mode, App\Router $router, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n) { $this->database = $database; $this->config = $config; @@ -286,8 +257,6 @@ class App $this->profiler = $profiler; $this->logger = $logger; $this->l10n = $l10n; - $this->args = $args; - $this->isBackend = $module->isBackend(); $this->profiler->reset(); @@ -304,10 +273,59 @@ class App . $this->getBasePath() . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR . $this->getBasePath()); - $this->cmd = $args->getCommand(); - $this->argv = $args->getArgv(); - $this->argc = $args->getArgc(); - $this->query_string = $args->getQueryString(); + if (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'pagename=') === 0) { + $this->query_string = substr($_SERVER['QUERY_STRING'], 9); + } elseif (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'q=') === 0) { + $this->query_string = substr($_SERVER['QUERY_STRING'], 2); + } + + // removing trailing / - maybe a nginx problem + $this->query_string = ltrim($this->query_string, '/'); + + if (!empty($_GET['pagename'])) { + $this->cmd = trim($_GET['pagename'], '/\\'); + } elseif (!empty($_GET['q'])) { + $this->cmd = trim($_GET['q'], '/\\'); + } + + // fix query_string + $this->query_string = str_replace($this->cmd . '&', $this->cmd . '?', $this->query_string); + + // unix style "homedir" + if (substr($this->cmd, 0, 1) === '~') { + $this->cmd = 'profile/' . substr($this->cmd, 1); + } + + // Diaspora style profile url + if (substr($this->cmd, 0, 2) === 'u/') { + $this->cmd = 'profile/' . substr($this->cmd, 2); + } + + /* + * Break the URL path into C style argc/argv style arguments for our + * modules. Given "http://example.com/module/arg1/arg2", $this->argc + * will be 3 (integer) and $this->argv will contain: + * [0] => 'module' + * [1] => 'arg1' + * [2] => 'arg2' + * + * + * There will always be one argument. If provided a naked domain + * URL, $this->argv[0] is set to "home". + */ + + $this->argv = explode('/', $this->cmd); + $this->argc = count($this->argv); + if ((array_key_exists('0', $this->argv)) && strlen($this->argv[0])) { + $this->module = str_replace('.', '_', $this->argv[0]); + $this->module = str_replace('-', '_', $this->module); + } else { + $this->argc = 1; + $this->argv = ['home']; + $this->module = 'home'; + } + + $this->isBackend = $this->isBackend || $this->checkBackend($this->module); // Detect mobile devices $mobile_detect = new MobileDetect(); @@ -328,6 +346,10 @@ class App */ public function reload() { + $this->isBackend = basename($_SERVER['PHP_SELF'], '.php') !== 'index'; + + $this->getMode()->determine($this->getBasePath()); + if ($this->getMode()->has(App\Mode::DBAVAILABLE)) { $this->profiler->update($this->config); @@ -377,8 +399,6 @@ class App * @param bool $ssl Whether to append http or https under BaseURL::SSL_POLICY_SELFSIGN * * @return string Friendica server base URL - * - * @deprecated 2019.09 - use BaseUrl->get($ssl) instead */ public function getBaseURL($ssl = false) { @@ -433,9 +453,9 @@ class App * - Infinite scroll data * - head.tpl template */ - private function initHead(App\Module $module, PConfiguration $pconfig) + public function initHead() { - $interval = ((local_user()) ? $pconfig->get(local_user(), 'system', 'update_interval') : 40000); + $interval = ((local_user()) ? Core\PConfig::get(local_user(), 'system', 'update_interval') : 40000); // If the update is 'deactivated' set it to the highest integer number (~24 days) if ($interval < 0) { @@ -447,8 +467,8 @@ class App } // Default title: current module called - if (empty($this->page['title']) && $module->getName()) { - $this->page['title'] = ucfirst($module->getName()); + if (empty($this->page['title']) && $this->module) { + $this->page['title'] = ucfirst($this->module); } // Prepend the sitename to the page title @@ -500,7 +520,7 @@ class App * - Registered footer scripts (through App->registerFooterScript()) * - footer.tpl template */ - private function initFooter() + public function initFooter() { // If you're just visiting, let javascript take you home if (!empty($_SESSION['visitor_home'])) { @@ -575,6 +595,58 @@ class App $this->getBaseURL(); } + /** + * @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 $module + * @return bool + */ + private function checkBackend($module) { + static $backends = [ + '_well_known', + 'api', + 'dfrn_notify', + 'feed', + 'fetch', + 'followers', + 'following', + 'hcard', + 'hostxrd', + 'inbox', + 'manifest', + 'nodeinfo', + 'noscrape', + 'objects', + 'outbox', + 'poco', + 'post', + 'proxy', + 'pubsub', + 'pubsubhubbub', + 'receive', + 'rsd_xml', + 'salmon', + 'statistics_json', + 'xrd', + ]; + + // Check if current module is in backend or backend flag is set + return in_array($module, $backends); + } + + /** + * Returns true, if the call is from a backend node (f.e. from a worker) + * + * @return bool Is it a known backend? + */ + public function isBackend() + { + return $this->isBackend; + } + /** * @brief Checks if the maximum number of database processes is reached * @@ -668,7 +740,7 @@ class App */ public function isMaxLoadReached() { - if ($this->isBackend) { + if ($this->isBackend()) { $process = 'backend'; $maxsysload = intval($this->config->get('system', 'maxloadavg')); if ($maxsysload < 1) { @@ -858,13 +930,21 @@ class App } /** - * @deprecated use Arguments->get() instead + * Returns the value of a argv key + * TODO there are a lot of $a->argv usages in combination with defaults() which can be replaced with this method * - * @see App\Arguments + * @param int $position the position of the argument + * @param mixed $default the default value if not found + * + * @return mixed returns the value of the argument */ public function getArgumentValue($position, $default = '') { - return $this->args->get($position, $default); + if (array_key_exists($position, $this->argv)) { + return $this->argv[$position]; + } + + return $default; } /** @@ -893,13 +973,9 @@ class App * request and a representation of the response. * * This probably should change to limit the size of this monster method. - * - * @param App\Module $module The determined module */ - public function runFrontend(App\Module $module, App\Router $router, PConfiguration $pconfig) + public function runFrontend() { - $moduleName = $module->getName(); - try { // Missing DB connection: ERROR if ($this->getMode()->has(App\Mode::LOCALCONFIGPRESENT) && !$this->getMode()->has(App\Mode::DBAVAILABLE)) { @@ -909,7 +985,7 @@ class App // Max Load Average reached: ERROR if ($this->isMaxProcessesReached() || $this->isMaxLoadReached()) { header('Retry-After: 120'); - header('Refresh: 120; url=' . $this->baseURL->get() . "/" . $this->args->getQueryString()); + header('Refresh: 120; url=' . $this->getBaseURL() . "/" . $this->query_string); throw new HTTPException\ServiceUnavailableException('The node is currently overloaded. Please try again later.'); } @@ -917,7 +993,7 @@ class App if (!$this->getMode()->isInstall()) { // Force SSL redirection if ($this->baseURL->checkRedirectHttps()) { - System::externalRedirect($this->baseURL->get() . '/' . $this->args->getQueryString()); + System::externalRedirect($this->getBaseURL() . '/' . $this->query_string); } Core\Session::init(); @@ -925,7 +1001,7 @@ class App } // Exclude the backend processes from the session management - if (!$module->isBackend()) { + if (!$this->isBackend()) { $stamp1 = microtime(true); session_start(); $this->profiler->saveTimestamp($stamp1, 'parser', Core\System::callstack()); @@ -945,6 +1021,7 @@ class App // ZRL if (!empty($_GET['zrl']) && $this->getMode()->isNormal()) { + $this->query_string = Model\Profile::stripZrls($this->query_string); if (!local_user()) { // Only continue when the given profile link seems valid // Valid profile links contain a path with "/profile/" and no query parameters @@ -967,10 +1044,11 @@ class App if (!empty($_GET['owt']) && $this->getMode()->isNormal()) { $token = $_GET['owt']; + $this->query_string = Model\Profile::stripQueryParam($this->query_string, 'owt'); Model\Profile::openWebAuthInit($token); } - Login::sessionAuth(); + Module\Login::sessionAuth(); if (empty($_SESSION['authenticated'])) { header('X-Account-Management-Status: none'); @@ -988,9 +1066,9 @@ class App // in install mode, any url loads install module // but we need "view" module for stylesheet - if ($this->getMode()->isInstall() && $moduleName !== 'install') { + if ($this->getMode()->isInstall() && $this->module !== 'install') { $this->internalRedirect('install'); - } elseif (!$this->getMode()->isInstall() && !$this->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $moduleName !== 'maintenance') { + } elseif (!$this->getMode()->isInstall() && !$this->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $this->module !== 'maintenance') { $this->internalRedirect('maintenance'); } else { $this->checkURL(); @@ -1013,65 +1091,152 @@ class App ]; // Compatibility with the Android Diaspora client - if ($moduleName == 'stream') { + if ($this->module == 'stream') { $this->internalRedirect('network?order=post'); } - if ($moduleName == 'conversations') { + if ($this->module == 'conversations') { $this->internalRedirect('message'); } - if ($moduleName == 'commented') { + if ($this->module == 'commented') { $this->internalRedirect('network?order=comment'); } - if ($moduleName == 'liked') { + if ($this->module == 'liked') { $this->internalRedirect('network?order=comment'); } - if ($moduleName == 'activity') { + if ($this->module == 'activity') { $this->internalRedirect('network?conv=1'); } - if (($moduleName == 'status_messages') && ($this->args->getCommand() == 'status_messages/new')) { + if (($this->module == 'status_messages') && ($this->cmd == 'status_messages/new')) { $this->internalRedirect('bookmarklet'); } - if (($moduleName == 'user') && ($this->args->getCommand() == 'user/edit')) { + if (($this->module == 'user') && ($this->cmd == 'user/edit')) { $this->internalRedirect('settings'); } - if (($moduleName == 'tag_followings') && ($this->args->getCommand() == 'tag_followings/manage')) { + 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"; + } + + /* + * 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. + */ + + // First we try explicit routes defined in App\Router + $this->router->collectRoutes(); + + $data = $this->router->getRouteCollector(); + Hook::callAll('route_collection', $data); + + $this->module_class = $this->router->getModuleClass($this->cmd); + + // 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($this->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; + } + } + } + + /* 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; + } + + /* 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(); + } + + 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 - $this->page['page_title'] = $moduleName; + $this->page['page_title'] = $this->module; - // determine the module class and save it to the module instance - // @todo there's an implicit dependency due SESSION::start(), so it has to be called here (yet) - $module = $module->determineClass($this->args, $router, $this->config); + $placeholder = ''; - // Let the module run it's internal process (init, get, post, ...) - $module->run($this->l10n, $this, $this->logger, $this->getCurrentTheme(), $_SERVER, $_POST); + Core\Hook::callAll($this->module . '_mod_init', $placeholder); + call_user_func([$this->module_class, 'init']); + + // "rawContent" is especially meant for technical endpoints. + // This endpoint doesn't need any theme initialization or other comparable stuff. + 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; + } + + if (function_exists(str_replace('-', '_', $this->getCurrentTheme()) . '_init')) { + $func = str_replace('-', '_', $this->getCurrentTheme()) . '_init'; + $func($this); + } + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + Core\Hook::callAll($this->module . '_mod_post', $_POST); + call_user_func([$this->module_class, 'post']); + } + + Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder); + call_user_func([$this->module_class, 'afterpost']); } catch(HTTPException $e) { - ModuleHTTPException::rawContent($e); + Module\Special\HTTPException::rawContent($e); } $content = ''; try { - $moduleClass = $module->getClassName(); - $arr = ['content' => $content]; - Core\Hook::callAll($moduleClass . '_mod_content', $arr); + Core\Hook::callAll($this->module . '_mod_content', $arr); $content = $arr['content']; - $arr = ['content' => call_user_func([$moduleClass, 'content'])]; - Core\Hook::callAll($moduleClass . '_mod_aftercontent', $arr); + $arr = ['content' => call_user_func([$this->module_class, 'content'])]; + Core\Hook::callAll($this->module . '_mod_aftercontent', $arr); $content .= $arr['content']; } catch(HTTPException $e) { - $content = ModuleHTTPException::content($e); + $content = Module\Special\HTTPException::content($e); } // initialise content region @@ -1088,7 +1253,7 @@ class App * all the module functions have executed so that all * theme choices made by the modules can take effect. */ - $this->initHead($module, $pconfig); + $this->initHead(); /* Build the page ending -- this is stuff that goes right before * the closing tag @@ -1100,7 +1265,7 @@ class App } // Add the navigation (menu) template - if ($moduleName != 'install' && $moduleName != 'maintenance') { + if ($this->module != 'install' && $this->module != 'maintenance') { $this->page['htmlhead'] .= Core\Renderer::replaceMacros(Core\Renderer::getMarkupTemplate('nav_head.tpl'), []); $this->page['nav'] = Content\Nav::build($this); } diff --git a/src/App/Arguments.php b/src/App/Arguments.php deleted file mode 100644 index 8047186a0..000000000 --- a/src/App/Arguments.php +++ /dev/null @@ -1,194 +0,0 @@ -queryString = $queryString; - $this->command = $command; - $this->argv = $argv; - $this->argc = $argc; - } - - /** - * @return string The whole query string of this call - */ - public function getQueryString() - { - return $this->queryString; - } - - /** - * @return string The whole command of this call - */ - public function getCommand() - { - return $this->command; - } - - /** - * @return array All arguments of this call - */ - public function getArgv() - { - return $this->argv; - } - - /** - * @return int The count of arguments of this call - */ - public function getArgc() - { - return $this->argc; - } - - /** - * Returns the value of a argv key - * @todo there are a lot of $a->argv usages in combination with defaults() which can be replaced with this method - * - * @param int $position the position of the argument - * @param mixed $default the default value if not found - * - * @return mixed returns the value of the argument - */ - public function get(int $position, $default = '') - { - return $this->has($position) ? $this->argv[$position] : $default; - } - - /** - * @param int $position - * - * @return bool if the argument position exists - */ - public function has(int $position) - { - return array_key_exists($position, $this->argv); - } - - /** - * Determine the arguments of the current call - * - * @param array $server The $_SERVER variable - * @param array $get The $_GET variable - * - * @return Arguments The determined arguments - */ - public function determine(array $server, array $get) - { - $queryString = ''; - - if (!empty($server['QUERY_STRING']) && strpos($server['QUERY_STRING'], 'pagename=') === 0) { - $queryString = substr($server['QUERY_STRING'], 9); - } elseif (!empty($server['QUERY_STRING']) && strpos($server['QUERY_STRING'], 'q=') === 0) { - $queryString = substr($server['QUERY_STRING'], 2); - } - - // eventually strip ZRL - $queryString = $this->stripZRLs($queryString); - - // eventually strip OWT - $queryString = $this->stripQueryParam($queryString, 'owt'); - - // removing trailing / - maybe a nginx problem - $queryString = ltrim($queryString, '/'); - - if (!empty($get['pagename'])) { - $command = trim($get['pagename'], '/\\'); - } elseif (!empty($get['q'])) { - $command = trim($get['q'], '/\\'); - } else { - $command = Module::DEFAULT; - } - - - // fix query_string - if (!empty($command)) { - $queryString = str_replace( - $command . '&', - $command . '?', - $queryString - ); - } - - // unix style "homedir" - if (substr($command, 0, 1) === '~') { - $command = 'profile/' . substr($command, 1); - } - - // Diaspora style profile url - if (substr($command, 0, 2) === 'u/') { - $command = 'profile/' . substr($command, 2); - } - - /* - * Break the URL path into C style argc/argv style arguments for our - * modules. Given "http://example.com/module/arg1/arg2", $this->argc - * will be 3 (integer) and $this->argv will contain: - * [0] => 'module' - * [1] => 'arg1' - * [2] => 'arg2' - * - * - * There will always be one argument. If provided a naked domain - * URL, $this->argv[0] is set to "home". - */ - - $argv = explode('/', $command); - $argc = count($argv); - - - return new Arguments($queryString, $command, $argv, $argc); - } - - /** - * Strip zrl parameter from a string. - * - * @param string $queryString The input string. - * - * @return string The zrl. - */ - public function stripZRLs(string $queryString) - { - return preg_replace('/[?&]zrl=(.*?)(&|$)/ism', '$2', $queryString); - } - - /** - * Strip query parameter from a string. - * - * @param string $queryString The input string. - * @param string $param - * - * @return string The query parameter. - */ - public function stripQueryParam(string $queryString, string $param) - { - return preg_replace('/[?&]' . $param . '=(.*?)(&|$)/ism', '$2', $queryString); - } -} \ No newline at end of file diff --git a/src/App/Mode.php b/src/App/Mode.php index 166d47a17..6cb79b6ed 100644 --- a/src/App/Mode.php +++ b/src/App/Mode.php @@ -24,9 +24,27 @@ class Mode */ private $mode; - public function __construct(int $mode = 0) + /** + * @var string the basepath of the application + */ + private $basepath; + + /** + * @var Database + */ + private $database; + + /** + * @var ConfigCache + */ + private $configCache; + + public function __construct(BasePath $basepath, Database $database, ConfigCache $configCache) { - $this->mode = $mode; + $this->basepath = $basepath->getPath(); + $this->database = $database; + $this->configCache = $configCache; + $this->mode = 0; } /** @@ -36,46 +54,50 @@ class Mode * - App::MODE_MAINTENANCE: The maintenance mode has been set * - App::MODE_NORMAL : Normal run with all features enabled * - * @return Mode returns the determined mode + * @param string $basePath the Basepath of the Application + * + * @return Mode returns itself * * @throws \Exception */ - public function determine(BasePath $basepath, Database $database, ConfigCache $configCache) + public function determine($basePath = null) { - $mode = 0; - - $basepathName = $basepath->getPath(); - - if (!file_exists($basepathName . '/config/local.config.php') - && !file_exists($basepathName . '/config/local.ini.php') - && !file_exists($basepathName . '/.htconfig.php')) { - return new Mode($mode); + if (!empty($basePath)) { + $this->basepath = $basePath; } - $mode |= Mode::LOCALCONFIGPRESENT; + $this->mode = 0; - if (!$database->connected()) { - return new Mode($mode); + if (!file_exists($this->basepath . '/config/local.config.php') + && !file_exists($this->basepath . '/config/local.ini.php') + && !file_exists($this->basepath . '/.htconfig.php')) { + return $this; } - $mode |= Mode::DBAVAILABLE; + $this->mode |= Mode::LOCALCONFIGPRESENT; - if ($database->fetchFirst("SHOW TABLES LIKE 'config'") === false) { - return new Mode($mode); + if (!$this->database->connected()) { + return $this; } - $mode |= Mode::DBCONFIGAVAILABLE; + $this->mode |= Mode::DBAVAILABLE; - if (!empty($configCache->get('system', 'maintenance')) || + if ($this->database->fetchFirst("SHOW TABLES LIKE 'config'") === false) { + return $this; + } + + $this->mode |= Mode::DBCONFIGAVAILABLE; + + if (!empty($this->configCache->get('system', 'maintenance')) || // Don't use Config or Configuration here because we're possibly BEFORE initializing the Configuration, // so this could lead to a dependency circle - !empty($database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) { - return new Mode($mode); + !empty($this->database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) { + return $this; } - $mode |= Mode::MAINTENANCEDISABLED; + $this->mode |= Mode::MAINTENANCEDISABLED; - return new Mode($mode); + return $this; } /** diff --git a/src/App/Module.php b/src/App/Module.php deleted file mode 100644 index 63be2f651..000000000 --- a/src/App/Module.php +++ /dev/null @@ -1,279 +0,0 @@ -module; - } - - /** - * @return string The base class name - */ - public function getClassName() - { - return $this->module_class; - } - - /** - * @return bool - */ - public function isBackend() - { - return $this->isBackend; - } - - public function __construct(string $module = self::DEFAULT, string $moduleClass = self::DEFAULT_CLASS, bool $isBackend = false, bool $printNotAllowedAddon = false) - { - $this->module = $module; - $this->module_class = $moduleClass; - $this->isBackend = $isBackend; - $this->printNotAllowedAddon = $printNotAllowedAddon; - } - - /** - * Determines the current module based on the App arguments and the server variable - * - * @param Arguments $args The Friendica arguments - * @param array $server The $_SERVER variable - * - * @return Module The module with the determined module - */ - public function determineModule(Arguments $args, array $server) - { - if ($args->getArgc() > 0) { - $module = str_replace('.', '_', $args->get(0)); - $module = str_replace('-', '_', $module); - } else { - $module = self::DEFAULT; - } - - // Compatibility with the Firefox App - if (($module == "users") && ($args->getCommand() == "users/sign_in")) { - $module = "login"; - } - - $isBackend = $this->checkBackend($module, $server); - - return new Module($module, $this->module_class, $isBackend, $this->printNotAllowedAddon); - } - - /** - * Determine the class of the current module - * - * @param Arguments $args The Friendica execution arguments - * @param Router $router The Friendica routing instance - * @param Core\Config\Configuration $config The Friendica Configuration - * - * @return Module The determined module of this call - * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - public function determineClass(Arguments $args, Router $router, Core\Config\Configuration $config) - { - $printNotAllowedAddon = false; - - /** - * 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. - **/ - - // First we try explicit routes defined in App\Router - $router->collectRoutes(); - - $data = $router->getRouteCollector(); - Core\Hook::callAll('route_collection', $data); - - $module_class = $router->getModuleClass($args->getCommand()); - - // Then we try addon-provided modules that we wrap in the LegacyModule class - if (!$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 = $config->get('config', 'private_addons', false); - if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) { - $printNotAllowedAddon = true; - } else { - include_once "addon/{$this->module}/{$this->module}.php"; - if (function_exists($this->module . '_module')) { - LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php"); - $module_class = LegacyModule::class; - } - } - } - - /* Finally, we look for a 'standard' program module in the 'mod' directory - * We emulate a Module class through the LegacyModule class - */ - if (!$module_class && file_exists("mod/{$this->module}.php")) { - LegacyModule::setModuleFile("mod/{$this->module}.php"); - $module_class = LegacyModule::class; - } - - $module_class = !isset($module_class) ? PageNotFound::class : $module_class; - - return new Module($this->module, $module_class, $this->isBackend, $printNotAllowedAddon); - } - - /** - * Run the determined module class and calls all hooks applied to - * - * @param Core\L10n\L10n $l10n The L10n instance - * @param App $app The whole Friendica app (for method arguments) - * @param LoggerInterface $logger The Friendica logger - * @param string $currentTheme The chosen theme - * @param array $server The $_SERVER variable - * @param array $post The $_POST variables - * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - public function run(Core\L10n\L10n $l10n, App $app, LoggerInterface $logger, string $currentTheme, array $server, array $post) - { - if ($this->printNotAllowedAddon) { - info($l10n->t("You must be logged in to use addons. ")); - } - - /* 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 === PageNotFound::class) { - $queryString = $server['QUERY_STRING']; - // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit. - if (!empty($queryString) && preg_match('/{[0-9]}/', $queryString) !== 0) { - exit(); - } - - if (!empty($queryString) && ($queryString === 'q=internal_error.html') && isset($dreamhost_error_hack)) { - $logger->info('index.php: dreamhost_error_hack invoked.', ['Original URI' => $server['REQUEST_URI']]); - $app->internalRedirect($server['REQUEST_URI']); - } - - $logger->debug('index.php: page not found.', ['request_uri' => $server['REQUEST_URI'], 'address' => $server['REMOTE_ADDR'], 'query' => $server['QUERY_STRING']]); - } - - $placeholder = ''; - - Core\Hook::callAll($this->module . '_mod_init', $placeholder); - - call_user_func([$this->module_class, 'init']); - - // "rawContent" is especially meant for technical endpoints. - // This endpoint doesn't need any theme initialization or other comparable stuff. - 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/' . $currentTheme . '/theme.php'; - if (file_exists($theme_info_file)) { - require_once $theme_info_file; - } - - if (function_exists(str_replace('-', '_', $currentTheme) . '_init')) { - $func = str_replace('-', '_', $currentTheme) . '_init'; - $func($app); - } - - if ($server['REQUEST_METHOD'] === 'POST') { - Core\Hook::callAll($this->module . '_mod_post', $post); - call_user_func([$this->module_class, 'post']); - } - - Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder); - call_user_func([$this->module_class, 'afterpost']); - } - - /** - * @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 $module The determined module - * @param array $server The $_SERVER variable - * - * @return bool True, if the current module is called at backend - */ - private function checkBackend($module, array $server) - { - // Check if current module is in backend or backend flag is set - return basename(($server['PHP_SELF'] ?? ''), '.php') !== 'index' && - in_array($module, Module::BACKEND_MODULES); - } -} diff --git a/src/Core/Installer.php b/src/Core/Installer.php index 16d7bf0aa..8dcf776a7 100644 --- a/src/Core/Installer.php +++ b/src/Core/Installer.php @@ -11,6 +11,7 @@ use Friendica\Database\Database; use Friendica\Database\DBStructure; use Friendica\Object\Image; use Friendica\Util\Network; +use Friendica\Util\Profiler; use Friendica\Util\Strings; /** @@ -590,7 +591,8 @@ class Installer /** * Checking the Database connection and if it is available for the current installation * - * @param Database $dba + * @param ConfigCache $configCache The configuration cache + * @param Profiler $profiler The profiler of this app * * @return bool true if the check was successful, otherwise false * @throws Exception diff --git a/src/Model/Profile.php b/src/Model/Profile.php index b82d47d31..f215af1af 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -1224,6 +1224,29 @@ class Profile return $uid; } + /** + * Strip zrl parameter from a string. + * + * @param string $s The input string. + * @return string The zrl. + */ + public static function stripZrls($s) + { + return preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $s); + } + + /** + * Strip query parameter from a string. + * + * @param string $s The input string. + * @param $param + * @return string The query parameter. + */ + public static function stripQueryParam($s, $param) + { + return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism', '$2', $s); + } + /** * search for Profiles * diff --git a/src/Module/Install.php b/src/Module/Install.php index 3b8ebb471..a7427ead0 100644 --- a/src/Module/Install.php +++ b/src/Module/Install.php @@ -111,7 +111,7 @@ class Install extends BaseModule self::checkSetting($configCache, $_POST, 'database', 'database', ''); // If we cannot connect to the database, return to the previous step - if (!self::$installer->checkDB($a->getDBA())) { + if (!self::$installer->checkDB($configCache, $a->getProfiler())) { self::$currentWizardStep = self::DATABASE_CONFIG; } @@ -135,7 +135,7 @@ class Install extends BaseModule self::checkSetting($configCache, $_POST, 'config', 'admin_email', ''); // If we cannot connect to the database, return to the Database config wizard - if (!self::$installer->checkDB($a->getDBA())) { + if (!self::$installer->checkDB($configCache, $a->getProfiler())) { self::$currentWizardStep = self::DATABASE_CONFIG; return; } diff --git a/static/dependencies.config.php b/static/dependencies.config.php index 5d0b0b97b..1d0908f32 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -27,14 +27,14 @@ use Psr\Log\LoggerInterface; * */ return [ - '*' => [ + '*' => [ // marks all class result as shared for other creations, so there's just // one instance for the whole execution 'shared' => true, ], - '$basepath' => [ - 'instanceOf' => Util\BasePath::class, - 'call' => [ + '$basepath' => [ + 'instanceOf' => Util\BasePath::class, + 'call' => [ ['getPath', [], Dice::CHAIN_CALL], ], 'constructParams' => [ @@ -42,14 +42,14 @@ return [ $_SERVER ] ], - Util\BasePath::class => [ + Util\BasePath::class => [ 'constructParams' => [ dirname(__FILE__, 2), $_SERVER ] ], - Util\ConfigFileLoader::class => [ - 'shared' => true, + Util\ConfigFileLoader::class => [ + 'shared' => true, 'constructParams' => [ [Dice::INSTANCE => '$basepath'], ], @@ -60,24 +60,24 @@ return [ ['createCache', [], Dice::CHAIN_CALL], ], ], - App\Mode::class => [ - 'call' => [ + App\Mode::class => [ + 'call' => [ ['determine', [], Dice::CHAIN_CALL], ], ], - Config\Configuration::class => [ + Config\Configuration::class => [ 'instanceOf' => Factory\ConfigFactory::class, - 'call' => [ + 'call' => [ ['createConfig', [], Dice::CHAIN_CALL], ], ], - Config\PConfiguration::class => [ + Config\PConfiguration::class => [ 'instanceOf' => Factory\ConfigFactory::class, - 'call' => [ + 'call' => [ ['createPConfig', [], Dice::CHAIN_CALL], ] ], - Database::class => [ + Database::class => [ 'constructParams' => [ [DICE::INSTANCE => \Psr\Log\NullLogger::class], $_SERVER, @@ -89,7 +89,7 @@ return [ * Same as: * $baseURL = new Util\BaseURL($configuration, $_SERVER); */ - Util\BaseURL::class => [ + Util\BaseURL::class => [ 'constructParams' => [ $_SERVER, ], @@ -106,46 +106,34 @@ return [ * $app = $dice->create(App::class, [], ['$channel' => 'index']); * and is automatically passed as an argument with the same name */ - LoggerInterface::class => [ + LoggerInterface::class => [ 'instanceOf' => Factory\LoggerFactory::class, 'call' => [ ['create', [], Dice::CHAIN_CALL], ], ], - '$devLogger' => [ + '$devLogger' => [ 'instanceOf' => Factory\LoggerFactory::class, 'call' => [ ['createDev', [], Dice::CHAIN_CALL], ] ], - Cache\ICache::class => [ + Cache\ICache::class => [ 'instanceOf' => Factory\CacheFactory::class, 'call' => [ ['create', [], Dice::CHAIN_CALL], ], ], - Cache\IMemoryCache::class => [ + Cache\IMemoryCache::class => [ 'instanceOf' => Factory\CacheFactory::class, 'call' => [ ['create', [], Dice::CHAIN_CALL], ], ], - ILock::class => [ + ILock::class => [ 'instanceOf' => Factory\LockFactory::class, 'call' => [ ['create', [], Dice::CHAIN_CALL], ], ], - App\Arguments::class => [ - 'instanceOf' => App\Arguments::class, - 'call' => [ - ['determine', [$_SERVER, $_GET], Dice::CHAIN_CALL], - ], - ], - App\Module::class => [ - 'instanceOf' => App\Module::class, - 'call' => [ - ['determineModule', [$_SERVER], Dice::CHAIN_CALL], - ], - ], ]; diff --git a/tests/src/App/ArgumentsTest.php b/tests/src/App/ArgumentsTest.php deleted file mode 100644 index ee0419955..000000000 --- a/tests/src/App/ArgumentsTest.php +++ /dev/null @@ -1,242 +0,0 @@ -assertEquals($assert['queryString'], $arguments->getQueryString()); - $this->assertEquals($assert['command'], $arguments->getCommand()); - $this->assertEquals($assert['argv'], $arguments->getArgv()); - $this->assertEquals($assert['argc'], $arguments->getArgc()); - $this->assertCount($assert['argc'], $arguments->getArgv()); - } - - /** - * Test the default argument without any determinations - */ - public function testDefault() - { - $arguments = new App\Arguments(); - - $this->assertArguments([ - 'queryString' => '', - 'command' => '', - 'argv' => ['home'], - 'argc' => 1, - ], - $arguments); - } - - public function dataArguments() - { - return [ - 'withPagename' => [ - 'assert' => [ - 'queryString' => 'profile/test/it?arg1=value1&arg2=value2', - 'command' => 'profile/test/it', - 'argv' => ['profile', 'test', 'it'], - 'argc' => 3, - ], - 'server' => [ - 'QUERY_STRING' => 'pagename=profile/test/it?arg1=value1&arg2=value2', - ], - 'get' => [ - 'pagename' => 'profile/test/it', - ], - ], - 'withQ' => [ - 'assert' => [ - 'queryString' => 'profile/test/it?arg1=value1&arg2=value2', - 'command' => 'profile/test/it', - 'argv' => ['profile', 'test', 'it'], - 'argc' => 3, - ], - 'server' => [ - 'QUERY_STRING' => 'q=profile/test/it?arg1=value1&arg2=value2', - ], - 'get' => [ - 'q' => 'profile/test/it', - ], - ], - 'withWrongDelimiter' => [ - 'assert' => [ - 'queryString' => 'profile/test/it?arg1=value1&arg2=value2', - 'command' => 'profile/test/it', - 'argv' => ['profile', 'test', 'it'], - 'argc' => 3, - ], - 'server' => [ - 'QUERY_STRING' => 'pagename=profile/test/it&arg1=value1&arg2=value2', - ], - 'get' => [ - 'pagename' => 'profile/test/it', - ], - ], - 'withUnixHomeDir' => [ - 'assert' => [ - 'queryString' => '~test/it?arg1=value1&arg2=value2', - 'command' => 'profile/test/it', - 'argv' => ['profile', 'test', 'it'], - 'argc' => 3, - ], - 'server' => [ - 'QUERY_STRING' => 'pagename=~test/it?arg1=value1&arg2=value2', - ], - 'get' => [ - 'pagename' => '~test/it', - ], - ], - 'withDiasporaHomeDir' => [ - 'assert' => [ - 'queryString' => 'u/test/it?arg1=value1&arg2=value2', - 'command' => 'profile/test/it', - 'argv' => ['profile', 'test', 'it'], - 'argc' => 3, - ], - 'server' => [ - 'QUERY_STRING' => 'pagename=u/test/it?arg1=value1&arg2=value2', - ], - 'get' => [ - 'pagename' => 'u/test/it', - ], - ], - 'withTrailingSlash' => [ - 'assert' => [ - 'queryString' => 'profile/test/it?arg1=value1&arg2=value2/', - 'command' => 'profile/test/it', - 'argv' => ['profile', 'test', 'it'], - 'argc' => 3, - ], - 'server' => [ - 'QUERY_STRING' => 'pagename=profile/test/it?arg1=value1&arg2=value2/', - ], - 'get' => [ - 'pagename' => 'profile/test/it', - ], - ], - 'withWrongQueryString' => [ - 'assert' => [ - // empty query string?! - 'queryString' => '', - 'command' => 'profile/test/it', - 'argv' => ['profile', 'test', 'it'], - 'argc' => 3, - ], - 'server' => [ - 'QUERY_STRING' => 'wrong=profile/test/it?arg1=value1&arg2=value2/', - ], - 'get' => [ - 'pagename' => 'profile/test/it', - ], - ], - 'withMissingPageName' => [ - 'assert' => [ - 'queryString' => 'notvalid/it?arg1=value1&arg2=value2/', - 'command' => App\Module::DEFAULT, - 'argv' => [App\Module::DEFAULT], - 'argc' => 1, - ], - 'server' => [ - 'QUERY_STRING' => 'pagename=notvalid/it?arg1=value1&arg2=value2/', - ], - 'get' => [ - ], - ], - ]; - } - - /** - * Test all variants of argument determination - * - * @dataProvider dataArguments - */ - public function testDetermine(array $assert, array $server, array $get) - { - $arguments = (new App\Arguments()) - ->determine($server, $get); - - $this->assertArguments($assert, $arguments); - } - - /** - * Test if the get/has methods are working for the determined arguments - * - * @dataProvider dataArguments - */ - public function testGetHas(array $assert, array $server, array $get) - { - $arguments = (new App\Arguments()) - ->determine($server, $get); - - for ($i = 0; $i < $arguments->getArgc(); $i++) { - $this->assertTrue($arguments->has($i)); - $this->assertEquals($assert['argv'][$i], $arguments->get($i)); - } - - $this->assertFalse($arguments->has($arguments->getArgc())); - $this->assertEmpty($arguments->get($arguments->getArgc())); - $this->assertEquals('default', $arguments->get($arguments->getArgc(), 'default')); - } - - public function dataStripped() - { - return [ - 'strippedZRLFirst' => [ - 'assert' => '?arg1=value1', - 'input' => '?zrl=nope&arg1=value1', - ], - 'strippedZRLLast' => [ - 'assert' => '?arg1=value1', - 'input' => '?arg1=value1&zrl=nope', - ], - 'strippedZTLMiddle' => [ - 'assert' => '?arg1=value1&arg2=value2', - 'input' => '?arg1=value1&zrl=nope&arg2=value2', - ], - 'strippedOWTFirst' => [ - 'assert' => '?arg1=value1', - 'input' => '?owt=test&arg1=value1', - ], - 'strippedOWTLast' => [ - 'assert' => '?arg1=value1', - 'input' => '?arg1=value1&owt=test', - ], - 'strippedOWTMiddle' => [ - 'assert' => '?arg1=value1&arg2=value2', - 'input' => '?arg1=value1&owt=test&arg2=value2', - ], - ]; - } - - /** - * Test the ZRL and OWT stripping - * - * @dataProvider dataStripped - */ - public function testStrippedQueries(string $assert, string $input) - { - $command = 'test/it'; - - $arguments = (new App\Arguments()) - ->determine(['QUERY_STRING' => 'q=' . $command . $input,], ['pagename' => $command]); - - $this->assertEquals($command . $assert, $arguments->getQueryString()); - } - - /** - * Test that arguments are immutable - */ - public function testImmutable() - { - $argument = new App\Arguments(); - - $argNew = $argument->determine([], []); - - $this->assertNotSame($argument, $argNew); - } -} diff --git a/tests/src/App/ModeTest.php b/tests/src/App/ModeTest.php index 30d0dc531..06aad1066 100644 --- a/tests/src/App/ModeTest.php +++ b/tests/src/App/ModeTest.php @@ -38,20 +38,22 @@ class ModeTest extends MockedTest $this->setUpVfsDir(); $this->basePathMock = \Mockery::mock(BasePath::class); + $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once(); + $this->databaseMock = \Mockery::mock(Database::class); $this->configCacheMock = \Mockery::mock(Config\Cache\ConfigCache::class); } public function testItEmpty() { - $mode = new Mode(); + $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock); $this->assertTrue($mode->isInstall()); $this->assertFalse($mode->isNormal()); } public function testWithoutConfig() { - $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once(); + $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock); $this->assertTrue($this->root->hasChild('config/local.config.php')); @@ -59,7 +61,7 @@ class ModeTest extends MockedTest $this->assertFalse($this->root->hasChild('config/local.config.php')); - $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode->determine(); $this->assertTrue($mode->isInstall()); $this->assertFalse($mode->isNormal()); @@ -69,11 +71,10 @@ class ModeTest extends MockedTest public function testWithoutDatabase() { - $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once(); - $this->databaseMock->shouldReceive('connected')->andReturn(false)->once(); - $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode->determine(); $this->assertFalse($mode->isNormal()); $this->assertTrue($mode->isInstall()); @@ -84,13 +85,12 @@ class ModeTest extends MockedTest public function testWithoutDatabaseSetup() { - $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once(); - $this->databaseMock->shouldReceive('connected')->andReturn(true)->once(); $this->databaseMock->shouldReceive('fetchFirst') ->with('SHOW TABLES LIKE \'config\'')->andReturn(false)->once(); - $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode->determine(); $this->assertFalse($mode->isNormal()); $this->assertTrue($mode->isInstall()); @@ -100,15 +100,14 @@ class ModeTest extends MockedTest public function testWithMaintenanceMode() { - $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once(); - $this->databaseMock->shouldReceive('connected')->andReturn(true)->once(); $this->databaseMock->shouldReceive('fetchFirst') ->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once(); $this->configCacheMock->shouldReceive('get')->with('system', 'maintenance') ->andReturn(true)->once(); - $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode->determine(); $this->assertFalse($mode->isNormal()); $this->assertFalse($mode->isInstall()); @@ -119,8 +118,6 @@ class ModeTest extends MockedTest public function testNormalMode() { - $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once(); - $this->databaseMock->shouldReceive('connected')->andReturn(true)->once(); $this->databaseMock->shouldReceive('fetchFirst') ->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once(); @@ -130,7 +127,8 @@ class ModeTest extends MockedTest ->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance']) ->andReturn(['v' => null])->once(); - $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode->determine(); $this->assertTrue($mode->isNormal()); $this->assertFalse($mode->isInstall()); @@ -144,8 +142,6 @@ class ModeTest extends MockedTest */ public function testDisabledMaintenance() { - $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once(); - $this->databaseMock->shouldReceive('connected')->andReturn(true)->once(); $this->databaseMock->shouldReceive('fetchFirst') ->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once(); @@ -155,7 +151,8 @@ class ModeTest extends MockedTest ->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance']) ->andReturn(['v' => '0'])->once(); - $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock); + $mode->determine(); $this->assertTrue($mode->isNormal()); $this->assertFalse($mode->isInstall()); @@ -163,18 +160,4 @@ class ModeTest extends MockedTest $this->assertTrue($mode->has(Mode::DBCONFIGAVAILABLE)); $this->assertTrue($mode->has(Mode::MAINTENANCEDISABLED)); } - - /** - * Test that modes are immutable - */ - public function testImmutable() - { - $this->basePathMock->shouldReceive('getPath')->andReturn(null)->once(); - - $mode = new Mode(); - - $modeNew = $mode->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock); - - $this->assertNotSame($modeNew, $mode); - } } diff --git a/tests/src/App/ModuleTest.php b/tests/src/App/ModuleTest.php deleted file mode 100644 index 309e94bba..000000000 --- a/tests/src/App/ModuleTest.php +++ /dev/null @@ -1,189 +0,0 @@ -assertEquals($assert['isBackend'], $module->isBackend()); - $this->assertEquals($assert['name'], $module->getName()); - $this->assertEquals($assert['class'], $module->getClassName()); - } - - /** - * Test the default module mode - */ - public function testDefault() - { - $module = new App\Module(); - - $this->assertModule([ - 'isBackend' => false, - 'name' => App\Module::DEFAULT, - 'class' => App\Module::DEFAULT_CLASS, - ], $module); - } - - public function dataModuleName() - { - return [ - 'default' => [ - 'assert' => [ - 'isBackend' => false, - 'name' => 'network', - 'class' => App\Module::DEFAULT_CLASS, - ], - 'args' => new App\Arguments('network/data/in', - 'network/data/in', - ['network', 'data', 'in'], - 3), - 'server' => [], - ], - 'withStrikeAndPoint' => [ - 'assert' => [ - 'isBackend' => false, - 'name' => 'with_strike_and_point', - 'class' => App\Module::DEFAULT_CLASS, - ], - 'args' => new App\Arguments('with-strike.and-point/data/in', - 'with-strike.and-point/data/in', - ['with-strike.and-point', 'data', 'in'], - 3), - 'server' => [], - ], - 'withNothing' => [ - 'assert' => [ - 'isBackend' => false, - 'name' => App\Module::DEFAULT, - 'class' => App\Module::DEFAULT_CLASS, - ], - 'args' => new App\Arguments(), - 'server' => [] - ], - 'withIndex' => [ - 'assert' => [ - 'isBackend' => false, - 'name' => App\Module::DEFAULT, - 'class' => App\Module::DEFAULT_CLASS, - ], - 'args' => new App\Arguments(), - 'server' => ['PHP_SELF' => 'index.php'] - ], - 'withIndexButBackendMod' => [ - 'assert' => [ - 'isBackend' => false, - 'name' => App\Module::BACKEND_MODULES[0], - 'class' => App\Module::DEFAULT_CLASS, - ], - 'args' => new App\Arguments(App\Module::BACKEND_MODULES[0] . '/data/in', - App\Module::BACKEND_MODULES[0] . '/data/in', - [App\Module::BACKEND_MODULES[0], 'data', 'in'], - 3), - 'server' => ['PHP_SELF' => 'index.php'] - ], - 'withNotIndexAndBackendMod' => [ - 'assert' => [ - 'isBackend' => true, - 'name' => App\Module::BACKEND_MODULES[0], - 'class' => App\Module::DEFAULT_CLASS, - ], - 'args' => new App\Arguments(App\Module::BACKEND_MODULES[0] . '/data/in', - App\Module::BACKEND_MODULES[0] . '/data/in', - [App\Module::BACKEND_MODULES[0], 'data', 'in'], - 3), - 'server' => ['PHP_SELF' => 'daemon.php'] - ], - 'withFirefoxApp' => [ - 'assert' => [ - 'isBackend' => false, - 'name' => 'login', - 'class' => App\Module::DEFAULT_CLASS, - ], - 'args' => new App\Arguments('users/sign_in', - 'users/sign_in', - ['users', 'sign_in'], - 3), - 'server' => ['PHP_SELF' => 'index.php'], - ], - ]; - } - - /** - * Test the module name and backend determination - * - * @dataProvider dataModuleName - */ - public function testModuleName(array $assert, App\Arguments $args, array $server) - { - $module = (new App\Module())->determineModule($args, $server); - - $this->assertModule($assert, $module); - } - - public function dataModuleClass() - { - return [ - 'default' => [ - 'assert' => App\Module::DEFAULT_CLASS, - 'name' => App\Module::DEFAULT, - 'command' => App\Module::DEFAULT, - 'privAdd' => false, - ], - 'legacy' => [ - 'assert' => LegacyModule::class, - // API is one of the last modules to switch from legacy to new BaseModule - // so this should be a stable test case until we completely switch ;-) - 'name' => 'api', - 'command' => 'api/test/it', - 'privAdd' => false, - ], - 'new' => [ - 'assert' => HostMeta::class, - 'not_required', - 'command' => '.well-known/host-meta', - 'privAdd' => false, - ], - '404' => [ - 'assert' => PageNotFound::class, - 'name' => 'invalid', - 'command' => 'invalid', - 'privAdd' => false, - ] - ]; - } - - /** - * Test the determination of the module class - * - * @dataProvider dataModuleClass - */ - public function testModuleClass($assert, string $name, string $command, bool $privAdd) - { - $config = \Mockery::mock(Configuration::class); - $config->shouldReceive('get')->with('config', 'private_addons', false)->andReturn($privAdd)->atMost()->once(); - - $module = (new App\Module($name))->determineClass(new App\Arguments('', $command), new App\Router(), $config); - - $this->assertEquals($assert, $module->getClassName()); - } - - /** - * Test that modules are immutable - */ - public function testImmutable() - { - $module = new App\Module(); - - $moduleNew = $module->determineModule(new App\Arguments(), []); - - $this->assertNotSame($moduleNew, $module); - } -}