$a = \Friendica\BaseObject::getApp();
-$a->runFrontend();
+$a->runFrontend($dice->create(\Friendica\App\Module::class), $dice->create(\Friendica\App\Router::class), $dice->create(\Friendica\Core\Config\PConfiguration::class));
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\Hook;
+use Friendica\Core\Config\PConfiguration;
use Friendica\Core\L10n\L10n;
use Friendica\Core\System;
use Friendica\Core\Theme;
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;
*/
class App
{
- public $module_class = null;
+ /** @deprecated 2019.09 - use App\Arguments->getQueryString() */
public $query_string = '';
public $page = [];
public $profile;
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;
/**
* @var bool true, if the call is from an backend node (f.e. worker)
+ *
+ * @deprecated 2019.09 - use App\Module->isBackend() instead
*/
private $isBackend;
*/
private $l10n;
+ /**
+ * @var App\Arguments
+ */
+ private $args;
+
+ /**
+ * @var App\Module
+ */
+ private $moduleClass;
+
/**
* Returns the current config cache of this node
*
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 <head> tag of every page.
* Inclusion is done in App->initHead().
*
* @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)
+ 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)
{
$this->database = $database;
$this->config = $config;
$this->profiler = $profiler;
$this->logger = $logger;
$this->l10n = $l10n;
+ $this->args = $args;
+ $this->isBackend = $module->isBackend();
$this->profiler->reset();
. $this->getBasePath() . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
. $this->getBasePath());
- 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);
+ $this->cmd = $args->getCommand();
+ $this->argv = $args->getArgv();
+ $this->argc = $args->getArgc();
+ $this->query_string = $args->getQueryString();
// Detect mobile devices
$mobile_detect = new MobileDetect();
*/
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);
* @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)
{
* - Infinite scroll data
* - head.tpl template
*/
- public function initHead()
+ private function initHead(App\Module $module, PConfiguration $pconfig)
{
- $interval = ((local_user()) ? Core\PConfig::get(local_user(), 'system', 'update_interval') : 40000);
+ $interval = ((local_user()) ? $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) {
}
// Default title: current module called
- if (empty($this->page['title']) && $this->module) {
- $this->page['title'] = ucfirst($this->module);
+ if (empty($this->page['title']) && $module->getName()) {
+ $this->page['title'] = ucfirst($module->getName());
}
// Prepend the sitename to the page title
* - Registered footer scripts (through App->registerFooterScript())
* - footer.tpl template
*/
- public function initFooter()
+ private function initFooter()
{
// If you're just visiting, let javascript take you home
if (!empty($_SESSION['visitor_home'])) {
$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
*
*/
public function isMaxLoadReached()
{
- if ($this->isBackend()) {
+ if ($this->isBackend) {
$process = 'backend';
$maxsysload = intval($this->config->get('system', 'maxloadavg'));
if ($maxsysload < 1) {
}
/**
- * 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
+ * @deprecated use Arguments->get() instead
*
- * @return mixed returns the value of the argument
+ * @see App\Arguments
*/
public function getArgumentValue($position, $default = '')
{
- if (array_key_exists($position, $this->argv)) {
- return $this->argv[$position];
- }
-
- return $default;
+ return $this->args->get($position, $default);
}
/**
* 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()
+ public function runFrontend(App\Module $module, App\Router $router, PConfiguration $pconfig)
{
+ $moduleName = $module->getName();
+
try {
// Missing DB connection: ERROR
if ($this->getMode()->has(App\Mode::LOCALCONFIGPRESENT) && !$this->getMode()->has(App\Mode::DBAVAILABLE)) {
// Max Load Average reached: ERROR
if ($this->isMaxProcessesReached() || $this->isMaxLoadReached()) {
header('Retry-After: 120');
- header('Refresh: 120; url=' . $this->getBaseURL() . "/" . $this->query_string);
+ header('Refresh: 120; url=' . $this->baseURL->get() . "/" . $this->args->getQueryString());
throw new HTTPException\ServiceUnavailableException('The node is currently overloaded. Please try again later.');
}
if (!$this->getMode()->isInstall()) {
// Force SSL redirection
if ($this->baseURL->checkRedirectHttps()) {
- System::externalRedirect($this->getBaseURL() . '/' . $this->query_string);
+ System::externalRedirect($this->baseURL->get() . '/' . $this->args->getQueryString());
}
Core\Session::init();
}
// Exclude the backend processes from the session management
- if (!$this->isBackend()) {
+ if (!$module->isBackend()) {
$stamp1 = microtime(true);
session_start();
$this->profiler->saveTimestamp($stamp1, 'parser', Core\System::callstack());
// 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
if (!empty($_GET['owt']) && $this->getMode()->isNormal()) {
$token = $_GET['owt'];
- $this->query_string = Model\Profile::stripQueryParam($this->query_string, 'owt');
Model\Profile::openWebAuthInit($token);
}
- Module\Login::sessionAuth();
+ Login::sessionAuth();
if (empty($_SESSION['authenticated'])) {
header('X-Account-Management-Status: none');
// in install mode, any url loads install module
// but we need "view" module for stylesheet
- if ($this->getMode()->isInstall() && $this->module !== 'install') {
+ if ($this->getMode()->isInstall() && $moduleName !== 'install') {
$this->internalRedirect('install');
- } elseif (!$this->getMode()->isInstall() && !$this->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $this->module !== 'maintenance') {
+ } elseif (!$this->getMode()->isInstall() && !$this->getMode()->has(App\Mode::MAINTENANCEDISABLED) && $moduleName !== 'maintenance') {
$this->internalRedirect('maintenance');
} else {
$this->checkURL();
];
// Compatibility with the Android Diaspora client
- if ($this->module == 'stream') {
+ if ($moduleName == 'stream') {
$this->internalRedirect('network?order=post');
}
- if ($this->module == 'conversations') {
+ if ($moduleName == 'conversations') {
$this->internalRedirect('message');
}
- if ($this->module == 'commented') {
+ if ($moduleName == 'commented') {
$this->internalRedirect('network?order=comment');
}
- if ($this->module == 'liked') {
+ if ($moduleName == 'liked') {
$this->internalRedirect('network?order=comment');
}
- if ($this->module == 'activity') {
+ if ($moduleName == 'activity') {
$this->internalRedirect('network?conv=1');
}
- if (($this->module == 'status_messages') && ($this->cmd == 'status_messages/new')) {
+ if (($moduleName == 'status_messages') && ($this->args->getCommand() == 'status_messages/new')) {
$this->internalRedirect('bookmarklet');
}
- if (($this->module == 'user') && ($this->cmd == 'user/edit')) {
+ if (($moduleName == 'user') && ($this->args->getCommand() == 'user/edit')) {
$this->internalRedirect('settings');
}
- if (($this->module == 'tag_followings') && ($this->cmd == 'tag_followings/manage')) {
+ if (($moduleName == 'tag_followings') && ($this->args->getCommand() == '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'] = $this->module;
-
- $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']);
+ $this->page['page_title'] = $moduleName;
- // 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;
- }
+ // 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);
- if (function_exists(str_replace('-', '_', $this->getCurrentTheme()) . '_init')) {
- $func = str_replace('-', '_', $this->getCurrentTheme()) . '_init';
- $func($this);
- }
+ // Let the module run it's internal process (init, get, post, ...)
+ $module->run($this->l10n, $this, $this->logger, $this->getCurrentTheme(), $_SERVER, $_POST);
- 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) {
- Module\Special\HTTPException::rawContent($e);
+ ModuleHTTPException::rawContent($e);
}
$content = '';
try {
+ $moduleClass = $module->getClassName();
+
$arr = ['content' => $content];
- Core\Hook::callAll($this->module . '_mod_content', $arr);
+ Core\Hook::callAll($moduleClass . '_mod_content', $arr);
$content = $arr['content'];
- $arr = ['content' => call_user_func([$this->module_class, 'content'])];
- Core\Hook::callAll($this->module . '_mod_aftercontent', $arr);
+ $arr = ['content' => call_user_func([$moduleClass, 'content'])];
+ Core\Hook::callAll($moduleClass . '_mod_aftercontent', $arr);
$content .= $arr['content'];
} catch(HTTPException $e) {
- $content = Module\Special\HTTPException::content($e);
+ $content = ModuleHTTPException::content($e);
}
// initialise content region
* all the module functions have executed so that all
* theme choices made by the modules can take effect.
*/
- $this->initHead();
+ $this->initHead($module, $pconfig);
/* Build the page ending -- this is stuff that goes right before
* the closing </body> tag
}
// Add the navigation (menu) template
- if ($this->module != 'install' && $this->module != 'maintenance') {
+ if ($moduleName != 'install' && $moduleName != 'maintenance') {
$this->page['htmlhead'] .= Core\Renderer::replaceMacros(Core\Renderer::getMarkupTemplate('nav_head.tpl'), []);
$this->page['nav'] = Content\Nav::build($this);
}
--- /dev/null
+<?php
+
+namespace Friendica\App;
+
+/**
+ * Determine all arguments of the current call, including
+ * - The whole querystring (except the pagename/q parameter)
+ * - The command
+ * - The arguments (C-Style based)
+ * - The count of arguments
+ */
+class Arguments
+{
+ /**
+ * @var string The complete query string
+ */
+ private $queryString;
+ /**
+ * @var string The current Friendica command
+ */
+ private $command;
+ /**
+ * @var array The arguments of the current execution
+ */
+ private $argv;
+ /**
+ * @var int The count of arguments
+ */
+ private $argc;
+
+ public function __construct(string $queryString = '', string $command = '', array $argv = [Module::DEFAULT], int $argc = 1)
+ {
+ $this->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
*/
private $mode;
- /**
- * @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)
+ public function __construct(int $mode = 0)
{
- $this->basepath = $basepath->getPath();
- $this->database = $database;
- $this->configCache = $configCache;
- $this->mode = 0;
+ $this->mode = $mode;
}
/**
* - App::MODE_MAINTENANCE: The maintenance mode has been set
* - App::MODE_NORMAL : Normal run with all features enabled
*
- * @param string $basePath the Basepath of the Application
- *
- * @return Mode returns itself
+ * @return Mode returns the determined mode
*
* @throws \Exception
*/
- public function determine($basePath = null)
+ public function determine(BasePath $basepath, Database $database, ConfigCache $configCache)
{
- if (!empty($basePath)) {
- $this->basepath = $basePath;
- }
+ $mode = 0;
- $this->mode = 0;
+ $basepathName = $basepath->getPath();
- 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;
+ if (!file_exists($basepathName . '/config/local.config.php')
+ && !file_exists($basepathName . '/config/local.ini.php')
+ && !file_exists($basepathName . '/.htconfig.php')) {
+ return new Mode($mode);
}
- $this->mode |= Mode::LOCALCONFIGPRESENT;
+ $mode |= Mode::LOCALCONFIGPRESENT;
- if (!$this->database->connected()) {
- return $this;
+ if (!$database->connected()) {
+ return new Mode($mode);
}
- $this->mode |= Mode::DBAVAILABLE;
+ $mode |= Mode::DBAVAILABLE;
- if ($this->database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
- return $this;
+ if ($database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
+ return new Mode($mode);
}
- $this->mode |= Mode::DBCONFIGAVAILABLE;
+ $mode |= Mode::DBCONFIGAVAILABLE;
- if (!empty($this->configCache->get('system', 'maintenance')) ||
+ if (!empty($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($this->database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
- return $this;
+ !empty($database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
+ return new Mode($mode);
}
- $this->mode |= Mode::MAINTENANCEDISABLED;
+ $mode |= Mode::MAINTENANCEDISABLED;
- return $this;
+ return new Mode($mode);
}
/**
--- /dev/null
+<?php
+
+namespace Friendica\App;
+
+use Friendica\App;
+use Friendica\BaseObject;
+use Friendica\Core;
+use Friendica\LegacyModule;
+use Friendica\Module\Home;
+use Friendica\Module\PageNotFound;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Holds the common context of the current, loaded module
+ */
+class Module
+{
+ const DEFAULT = 'home';
+ const DEFAULT_CLASS = Home::class;
+
+ /**
+ * @var string The module name
+ */
+ private $module;
+
+ /**
+ * @var BaseObject The module class
+ */
+ private $module_class;
+
+ /**
+ * @var bool true, if the module is a backend module
+ */
+ private $isBackend;
+
+ /**
+ * @var bool true, if the loaded addon is private, so we have to print out not allowed
+ */
+ private $printNotAllowedAddon;
+
+ /**
+ * A list of modules, which are backend methods
+ *
+ * @var array
+ */
+ const BACKEND_MODULES = [
+ '_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',
+ ];
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->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);
+ }
+}
use Friendica\Database\DBStructure;
use Friendica\Object\Image;
use Friendica\Util\Network;
-use Friendica\Util\Profiler;
use Friendica\Util\Strings;
/**
/**
* Checking the Database connection and if it is available for the current installation
*
- * @param ConfigCache $configCache The configuration cache
- * @param Profiler $profiler The profiler of this app
+ * @param Database $dba
*
* @return bool true if the check was successful, otherwise false
* @throws Exception
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
*
self::checkSetting($configCache, $_POST, 'database', 'database', '');
// If we cannot connect to the database, return to the previous step
- if (!self::$installer->checkDB($configCache, $a->getProfiler())) {
+ if (!self::$installer->checkDB($a->getDBA())) {
self::$currentWizardStep = self::DATABASE_CONFIG;
}
self::checkSetting($configCache, $_POST, 'config', 'admin_email', '');
// If we cannot connect to the database, return to the Database config wizard
- if (!self::$installer->checkDB($configCache, $a->getProfiler())) {
+ if (!self::$installer->checkDB($a->getDBA())) {
self::$currentWizardStep = self::DATABASE_CONFIG;
return;
}
*
*/
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' => [
$_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'],
],
['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,
* Same as:
* $baseURL = new Util\BaseURL($configuration, $_SERVER);
*/
- Util\BaseURL::class => [
+ Util\BaseURL::class => [
'constructParams' => [
$_SERVER,
],
* $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],
+ ],
+ ],
];
--- /dev/null
+<?php
+
+namespace Friendica\Test\src\App;
+
+use Friendica\App;
+use PHPUnit\Framework\TestCase;
+
+class ArgumentsTest extends TestCase
+{
+ private function assertArguments(array $assert, App\Arguments $arguments)
+ {
+ $this->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);
+ }
+}
$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($this->basePathMock, $this->databaseMock, $this->configCacheMock);
+ $mode = new Mode();
$this->assertTrue($mode->isInstall());
$this->assertFalse($mode->isNormal());
}
public function testWithoutConfig()
{
- $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
+ $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
$this->assertTrue($this->root->hasChild('config/local.config.php'));
$this->assertFalse($this->root->hasChild('config/local.config.php'));
- $mode->determine();
+ $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
$this->assertTrue($mode->isInstall());
$this->assertFalse($mode->isNormal());
public function testWithoutDatabase()
{
+ $this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
+
$this->databaseMock->shouldReceive('connected')->andReturn(false)->once();
- $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
- $mode->determine();
+ $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
$this->assertFalse($mode->isNormal());
$this->assertTrue($mode->isInstall());
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($this->basePathMock, $this->databaseMock, $this->configCacheMock);
- $mode->determine();
+ $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
$this->assertFalse($mode->isNormal());
$this->assertTrue($mode->isInstall());
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($this->basePathMock, $this->databaseMock, $this->configCacheMock);
- $mode->determine();
+ $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
$this->assertFalse($mode->isNormal());
$this->assertFalse($mode->isInstall());
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();
->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])
->andReturn(['v' => null])->once();
- $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
- $mode->determine();
+ $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
$this->assertTrue($mode->isNormal());
$this->assertFalse($mode->isInstall());
*/
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();
->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])
->andReturn(['v' => '0'])->once();
- $mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
- $mode->determine();
+ $mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
$this->assertTrue($mode->isNormal());
$this->assertFalse($mode->isInstall());
$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);
+ }
}
--- /dev/null
+<?php
+
+namespace Friendica\Test\src\App;
+
+use Friendica\App;
+use Friendica\Core\Config\Configuration;
+use Friendica\LegacyModule;
+use Friendica\Module\PageNotFound;
+use Friendica\Module\WellKnown\HostMeta;
+use Friendica\Test\DatabaseTest;
+
+class ModuleTest extends DatabaseTest
+{
+ private function assertModule(array $assert, App\Module $module)
+ {
+ $this->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);
+ }
+}