Introduce Arguments / Module class
- move from App - add tests for Arguments/Module class
This commit is contained in:
parent
3480891277
commit
2c5ba7fc15
|
@ -18,4 +18,4 @@ $dice = (new Dice())->addRules(include __DIR__ . '/static/dependencies.config.ph
|
||||||
|
|
||||||
$a = \Friendica\BaseObject::getApp();
|
$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));
|
||||||
|
|
331
src/App.php
331
src/App.php
|
@ -8,9 +8,11 @@ use Detection\MobileDetect;
|
||||||
use DOMDocument;
|
use DOMDocument;
|
||||||
use DOMXPath;
|
use DOMXPath;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Friendica\App\Arguments;
|
||||||
|
use Friendica\App\Module;
|
||||||
use Friendica\Core\Config\Cache\ConfigCache;
|
use Friendica\Core\Config\Cache\ConfigCache;
|
||||||
use Friendica\Core\Config\Configuration;
|
use Friendica\Core\Config\Configuration;
|
||||||
use Friendica\Core\Hook;
|
use Friendica\Core\Config\PConfiguration;
|
||||||
use Friendica\Core\L10n\L10n;
|
use Friendica\Core\L10n\L10n;
|
||||||
use Friendica\Core\System;
|
use Friendica\Core\System;
|
||||||
use Friendica\Core\Theme;
|
use Friendica\Core\Theme;
|
||||||
|
@ -18,6 +20,7 @@ use Friendica\Database\Database;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
use Friendica\Model\Profile;
|
use Friendica\Model\Profile;
|
||||||
use Friendica\Module\Login;
|
use Friendica\Module\Login;
|
||||||
|
use Friendica\Module\Special\HTTPException as ModuleHTTPException;
|
||||||
use Friendica\Network\HTTPException;
|
use Friendica\Network\HTTPException;
|
||||||
use Friendica\Util\BaseURL;
|
use Friendica\Util\BaseURL;
|
||||||
use Friendica\Util\ConfigFileLoader;
|
use Friendica\Util\ConfigFileLoader;
|
||||||
|
@ -41,7 +44,7 @@ use Psr\Log\LoggerInterface;
|
||||||
*/
|
*/
|
||||||
class App
|
class App
|
||||||
{
|
{
|
||||||
public $module_class = null;
|
/** @deprecated 2019.09 - use App\Arguments->getQueryString() */
|
||||||
public $query_string = '';
|
public $query_string = '';
|
||||||
public $page = [];
|
public $page = [];
|
||||||
public $profile;
|
public $profile;
|
||||||
|
@ -53,9 +56,13 @@ class App
|
||||||
public $page_contact;
|
public $page_contact;
|
||||||
public $content;
|
public $content;
|
||||||
public $data = [];
|
public $data = [];
|
||||||
|
/** @deprecated 2019.09 - use App\Arguments->getCommand() */
|
||||||
public $cmd = '';
|
public $cmd = '';
|
||||||
|
/** @deprecated 2019.09 - use App\Arguments->getArgv() or Arguments->get() */
|
||||||
public $argv;
|
public $argv;
|
||||||
|
/** @deprecated 2019.09 - use App\Arguments->getArgc() */
|
||||||
public $argc;
|
public $argc;
|
||||||
|
/** @deprecated 2019.09 - Use App\Module->getName() instead */
|
||||||
public $module;
|
public $module;
|
||||||
public $timezone;
|
public $timezone;
|
||||||
public $interactive = true;
|
public $interactive = true;
|
||||||
|
@ -93,6 +100,8 @@ class App
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool true, if the call is from an backend node (f.e. worker)
|
* @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 $isBackend;
|
||||||
|
|
||||||
|
@ -136,6 +145,16 @@ class App
|
||||||
*/
|
*/
|
||||||
private $l10n;
|
private $l10n;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var App\Arguments
|
||||||
|
*/
|
||||||
|
private $args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var App\Module
|
||||||
|
*/
|
||||||
|
private $moduleClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current config cache of this node
|
* Returns the current config cache of this node
|
||||||
*
|
*
|
||||||
|
@ -197,6 +216,16 @@ class App
|
||||||
return $this->mode;
|
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.
|
* Register a stylesheet file path to be included in the <head> tag of every page.
|
||||||
* Inclusion is done in App->initHead().
|
* Inclusion is done in App->initHead().
|
||||||
|
@ -247,7 +276,7 @@ class App
|
||||||
*
|
*
|
||||||
* @throws Exception if the Basepath is not usable
|
* @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->database = $database;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
@ -257,6 +286,8 @@ class App
|
||||||
$this->profiler = $profiler;
|
$this->profiler = $profiler;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->l10n = $l10n;
|
$this->l10n = $l10n;
|
||||||
|
$this->args = $args;
|
||||||
|
$this->isBackend = $module->isBackend();
|
||||||
|
|
||||||
$this->profiler->reset();
|
$this->profiler->reset();
|
||||||
|
|
||||||
|
@ -273,59 +304,10 @@ class App
|
||||||
. $this->getBasePath() . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
|
. $this->getBasePath() . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
|
||||||
. $this->getBasePath());
|
. $this->getBasePath());
|
||||||
|
|
||||||
if (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'pagename=') === 0) {
|
$this->cmd = $args->getCommand();
|
||||||
$this->query_string = substr($_SERVER['QUERY_STRING'], 9);
|
$this->argv = $args->getArgv();
|
||||||
} elseif (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'q=') === 0) {
|
$this->argc = $args->getArgc();
|
||||||
$this->query_string = substr($_SERVER['QUERY_STRING'], 2);
|
$this->query_string = $args->getQueryString();
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
// Detect mobile devices
|
||||||
$mobile_detect = new MobileDetect();
|
$mobile_detect = new MobileDetect();
|
||||||
|
@ -346,10 +328,6 @@ class App
|
||||||
*/
|
*/
|
||||||
public function reload()
|
public function reload()
|
||||||
{
|
{
|
||||||
$this->isBackend = basename($_SERVER['PHP_SELF'], '.php') !== 'index';
|
|
||||||
|
|
||||||
$this->getMode()->determine($this->getBasePath());
|
|
||||||
|
|
||||||
if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
|
if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
|
||||||
$this->profiler->update($this->config);
|
$this->profiler->update($this->config);
|
||||||
|
|
||||||
|
@ -399,6 +377,8 @@ class App
|
||||||
* @param bool $ssl Whether to append http or https under BaseURL::SSL_POLICY_SELFSIGN
|
* @param bool $ssl Whether to append http or https under BaseURL::SSL_POLICY_SELFSIGN
|
||||||
*
|
*
|
||||||
* @return string Friendica server base URL
|
* @return string Friendica server base URL
|
||||||
|
*
|
||||||
|
* @deprecated 2019.09 - use BaseUrl->get($ssl) instead
|
||||||
*/
|
*/
|
||||||
public function getBaseURL($ssl = false)
|
public function getBaseURL($ssl = false)
|
||||||
{
|
{
|
||||||
|
@ -453,9 +433,9 @@ class App
|
||||||
* - Infinite scroll data
|
* - Infinite scroll data
|
||||||
* - head.tpl template
|
* - 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 the update is 'deactivated' set it to the highest integer number (~24 days)
|
||||||
if ($interval < 0) {
|
if ($interval < 0) {
|
||||||
|
@ -467,8 +447,8 @@ class App
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default title: current module called
|
// Default title: current module called
|
||||||
if (empty($this->page['title']) && $this->module) {
|
if (empty($this->page['title']) && $module->getName()) {
|
||||||
$this->page['title'] = ucfirst($this->module);
|
$this->page['title'] = ucfirst($module->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepend the sitename to the page title
|
// Prepend the sitename to the page title
|
||||||
|
@ -520,7 +500,7 @@ class App
|
||||||
* - Registered footer scripts (through App->registerFooterScript())
|
* - Registered footer scripts (through App->registerFooterScript())
|
||||||
* - footer.tpl template
|
* - footer.tpl template
|
||||||
*/
|
*/
|
||||||
public function initFooter()
|
private function initFooter()
|
||||||
{
|
{
|
||||||
// If you're just visiting, let javascript take you home
|
// If you're just visiting, let javascript take you home
|
||||||
if (!empty($_SESSION['visitor_home'])) {
|
if (!empty($_SESSION['visitor_home'])) {
|
||||||
|
@ -595,58 +575,6 @@ class App
|
||||||
$this->getBaseURL();
|
$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
|
* @brief Checks if the maximum number of database processes is reached
|
||||||
*
|
*
|
||||||
|
@ -740,7 +668,7 @@ class App
|
||||||
*/
|
*/
|
||||||
public function isMaxLoadReached()
|
public function isMaxLoadReached()
|
||||||
{
|
{
|
||||||
if ($this->isBackend()) {
|
if ($this->isBackend) {
|
||||||
$process = 'backend';
|
$process = 'backend';
|
||||||
$maxsysload = intval($this->config->get('system', 'maxloadavg'));
|
$maxsysload = intval($this->config->get('system', 'maxloadavg'));
|
||||||
if ($maxsysload < 1) {
|
if ($maxsysload < 1) {
|
||||||
|
@ -930,21 +858,13 @@ class App
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of a argv key
|
* @deprecated use Arguments->get() instead
|
||||||
* 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
|
* @see App\Arguments
|
||||||
* @param mixed $default the default value if not found
|
|
||||||
*
|
|
||||||
* @return mixed returns the value of the argument
|
|
||||||
*/
|
*/
|
||||||
public function getArgumentValue($position, $default = '')
|
public function getArgumentValue($position, $default = '')
|
||||||
{
|
{
|
||||||
if (array_key_exists($position, $this->argv)) {
|
return $this->args->get($position, $default);
|
||||||
return $this->argv[$position];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -973,9 +893,13 @@ class App
|
||||||
* request and a representation of the response.
|
* request and a representation of the response.
|
||||||
*
|
*
|
||||||
* This probably should change to limit the size of this monster method.
|
* 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 {
|
try {
|
||||||
// Missing DB connection: ERROR
|
// Missing DB connection: ERROR
|
||||||
if ($this->getMode()->has(App\Mode::LOCALCONFIGPRESENT) && !$this->getMode()->has(App\Mode::DBAVAILABLE)) {
|
if ($this->getMode()->has(App\Mode::LOCALCONFIGPRESENT) && !$this->getMode()->has(App\Mode::DBAVAILABLE)) {
|
||||||
|
@ -985,7 +909,7 @@ class App
|
||||||
// Max Load Average reached: ERROR
|
// Max Load Average reached: ERROR
|
||||||
if ($this->isMaxProcessesReached() || $this->isMaxLoadReached()) {
|
if ($this->isMaxProcessesReached() || $this->isMaxLoadReached()) {
|
||||||
header('Retry-After: 120');
|
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.');
|
throw new HTTPException\ServiceUnavailableException('The node is currently overloaded. Please try again later.');
|
||||||
}
|
}
|
||||||
|
@ -993,7 +917,7 @@ class App
|
||||||
if (!$this->getMode()->isInstall()) {
|
if (!$this->getMode()->isInstall()) {
|
||||||
// Force SSL redirection
|
// Force SSL redirection
|
||||||
if ($this->baseURL->checkRedirectHttps()) {
|
if ($this->baseURL->checkRedirectHttps()) {
|
||||||
System::externalRedirect($this->getBaseURL() . '/' . $this->query_string);
|
System::externalRedirect($this->baseURL->get() . '/' . $this->args->getQueryString());
|
||||||
}
|
}
|
||||||
|
|
||||||
Core\Session::init();
|
Core\Session::init();
|
||||||
|
@ -1001,7 +925,7 @@ class App
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude the backend processes from the session management
|
// Exclude the backend processes from the session management
|
||||||
if (!$this->isBackend()) {
|
if (!$module->isBackend()) {
|
||||||
$stamp1 = microtime(true);
|
$stamp1 = microtime(true);
|
||||||
session_start();
|
session_start();
|
||||||
$this->profiler->saveTimestamp($stamp1, 'parser', Core\System::callstack());
|
$this->profiler->saveTimestamp($stamp1, 'parser', Core\System::callstack());
|
||||||
|
@ -1021,7 +945,6 @@ class App
|
||||||
|
|
||||||
// ZRL
|
// ZRL
|
||||||
if (!empty($_GET['zrl']) && $this->getMode()->isNormal()) {
|
if (!empty($_GET['zrl']) && $this->getMode()->isNormal()) {
|
||||||
$this->query_string = Model\Profile::stripZrls($this->query_string);
|
|
||||||
if (!local_user()) {
|
if (!local_user()) {
|
||||||
// Only continue when the given profile link seems valid
|
// Only continue when the given profile link seems valid
|
||||||
// Valid profile links contain a path with "/profile/" and no query parameters
|
// Valid profile links contain a path with "/profile/" and no query parameters
|
||||||
|
@ -1044,11 +967,10 @@ class App
|
||||||
|
|
||||||
if (!empty($_GET['owt']) && $this->getMode()->isNormal()) {
|
if (!empty($_GET['owt']) && $this->getMode()->isNormal()) {
|
||||||
$token = $_GET['owt'];
|
$token = $_GET['owt'];
|
||||||
$this->query_string = Model\Profile::stripQueryParam($this->query_string, 'owt');
|
|
||||||
Model\Profile::openWebAuthInit($token);
|
Model\Profile::openWebAuthInit($token);
|
||||||
}
|
}
|
||||||
|
|
||||||
Module\Login::sessionAuth();
|
Login::sessionAuth();
|
||||||
|
|
||||||
if (empty($_SESSION['authenticated'])) {
|
if (empty($_SESSION['authenticated'])) {
|
||||||
header('X-Account-Management-Status: none');
|
header('X-Account-Management-Status: none');
|
||||||
|
@ -1066,9 +988,9 @@ class App
|
||||||
|
|
||||||
// in install mode, any url loads install module
|
// in install mode, any url loads install module
|
||||||
// but we need "view" module for stylesheet
|
// but we need "view" module for stylesheet
|
||||||
if ($this->getMode()->isInstall() && $this->module !== 'install') {
|
if ($this->getMode()->isInstall() && $moduleName !== 'install') {
|
||||||
$this->internalRedirect('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');
|
$this->internalRedirect('maintenance');
|
||||||
} else {
|
} else {
|
||||||
$this->checkURL();
|
$this->checkURL();
|
||||||
|
@ -1091,152 +1013,65 @@ class App
|
||||||
];
|
];
|
||||||
|
|
||||||
// Compatibility with the Android Diaspora client
|
// Compatibility with the Android Diaspora client
|
||||||
if ($this->module == 'stream') {
|
if ($moduleName == 'stream') {
|
||||||
$this->internalRedirect('network?order=post');
|
$this->internalRedirect('network?order=post');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->module == 'conversations') {
|
if ($moduleName == 'conversations') {
|
||||||
$this->internalRedirect('message');
|
$this->internalRedirect('message');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->module == 'commented') {
|
if ($moduleName == 'commented') {
|
||||||
$this->internalRedirect('network?order=comment');
|
$this->internalRedirect('network?order=comment');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->module == 'liked') {
|
if ($moduleName == 'liked') {
|
||||||
$this->internalRedirect('network?order=comment');
|
$this->internalRedirect('network?order=comment');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->module == 'activity') {
|
if ($moduleName == 'activity') {
|
||||||
$this->internalRedirect('network?conv=1');
|
$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');
|
$this->internalRedirect('bookmarklet');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($this->module == 'user') && ($this->cmd == 'user/edit')) {
|
if (($moduleName == 'user') && ($this->args->getCommand() == 'user/edit')) {
|
||||||
$this->internalRedirect('settings');
|
$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');
|
$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
|
// 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;
|
$this->page['page_title'] = $moduleName;
|
||||||
|
|
||||||
$placeholder = '';
|
// 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);
|
||||||
|
|
||||||
Core\Hook::callAll($this->module . '_mod_init', $placeholder);
|
// Let the module run it's internal process (init, get, post, ...)
|
||||||
|
$module->run($this->l10n, $this, $this->logger, $this->getCurrentTheme(), $_SERVER, $_POST);
|
||||||
|
|
||||||
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) {
|
} catch(HTTPException $e) {
|
||||||
Module\Special\HTTPException::rawContent($e);
|
ModuleHTTPException::rawContent($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = '';
|
$content = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$moduleClass = $module->getClassName();
|
||||||
|
|
||||||
$arr = ['content' => $content];
|
$arr = ['content' => $content];
|
||||||
Core\Hook::callAll($this->module . '_mod_content', $arr);
|
Core\Hook::callAll($moduleClass . '_mod_content', $arr);
|
||||||
$content = $arr['content'];
|
$content = $arr['content'];
|
||||||
$arr = ['content' => call_user_func([$this->module_class, 'content'])];
|
$arr = ['content' => call_user_func([$moduleClass, 'content'])];
|
||||||
Core\Hook::callAll($this->module . '_mod_aftercontent', $arr);
|
Core\Hook::callAll($moduleClass . '_mod_aftercontent', $arr);
|
||||||
$content .= $arr['content'];
|
$content .= $arr['content'];
|
||||||
} catch(HTTPException $e) {
|
} catch(HTTPException $e) {
|
||||||
$content = Module\Special\HTTPException::content($e);
|
$content = ModuleHTTPException::content($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialise content region
|
// initialise content region
|
||||||
|
@ -1253,7 +1088,7 @@ class App
|
||||||
* all the module functions have executed so that all
|
* all the module functions have executed so that all
|
||||||
* theme choices made by the modules can take effect.
|
* 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
|
/* Build the page ending -- this is stuff that goes right before
|
||||||
* the closing </body> tag
|
* the closing </body> tag
|
||||||
|
@ -1265,7 +1100,7 @@ class App
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the navigation (menu) template
|
// 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['htmlhead'] .= Core\Renderer::replaceMacros(Core\Renderer::getMarkupTemplate('nav_head.tpl'), []);
|
||||||
$this->page['nav'] = Content\Nav::build($this);
|
$this->page['nav'] = Content\Nav::build($this);
|
||||||
}
|
}
|
||||||
|
|
194
src/App/Arguments.php
Normal file
194
src/App/Arguments.php
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,27 +24,9 @@ class Mode
|
||||||
*/
|
*/
|
||||||
private $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->basepath = $basepath->getPath();
|
$this->mode = $mode;
|
||||||
$this->database = $database;
|
|
||||||
$this->configCache = $configCache;
|
|
||||||
$this->mode = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,50 +36,46 @@ class Mode
|
||||||
* - App::MODE_MAINTENANCE: The maintenance mode has been set
|
* - App::MODE_MAINTENANCE: The maintenance mode has been set
|
||||||
* - App::MODE_NORMAL : Normal run with all features enabled
|
* - App::MODE_NORMAL : Normal run with all features enabled
|
||||||
*
|
*
|
||||||
* @param string $basePath the Basepath of the Application
|
* @return Mode returns the determined mode
|
||||||
*
|
|
||||||
* @return Mode returns itself
|
|
||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function determine($basePath = null)
|
public function determine(BasePath $basepath, Database $database, ConfigCache $configCache)
|
||||||
{
|
{
|
||||||
if (!empty($basePath)) {
|
$mode = 0;
|
||||||
$this->basepath = $basePath;
|
|
||||||
|
$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);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->mode = 0;
|
$mode |= Mode::LOCALCONFIGPRESENT;
|
||||||
|
|
||||||
if (!file_exists($this->basepath . '/config/local.config.php')
|
if (!$database->connected()) {
|
||||||
&& !file_exists($this->basepath . '/config/local.ini.php')
|
return new Mode($mode);
|
||||||
&& !file_exists($this->basepath . '/.htconfig.php')) {
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->mode |= Mode::LOCALCONFIGPRESENT;
|
$mode |= Mode::DBAVAILABLE;
|
||||||
|
|
||||||
if (!$this->database->connected()) {
|
if ($database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
|
||||||
return $this;
|
return new Mode($mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->mode |= Mode::DBAVAILABLE;
|
$mode |= Mode::DBCONFIGAVAILABLE;
|
||||||
|
|
||||||
if ($this->database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
|
if (!empty($configCache->get('system', 'maintenance')) ||
|
||||||
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,
|
// Don't use Config or Configuration here because we're possibly BEFORE initializing the Configuration,
|
||||||
// so this could lead to a dependency circle
|
// so this could lead to a dependency circle
|
||||||
!empty($this->database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
|
!empty($database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
|
||||||
return $this;
|
return new Mode($mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->mode |= Mode::MAINTENANCEDISABLED;
|
$mode |= Mode::MAINTENANCEDISABLED;
|
||||||
|
|
||||||
return $this;
|
return new Mode($mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
280
src/App/Module.php
Normal file
280
src/App/Module.php
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ use Friendica\Database\Database;
|
||||||
use Friendica\Database\DBStructure;
|
use Friendica\Database\DBStructure;
|
||||||
use Friendica\Object\Image;
|
use Friendica\Object\Image;
|
||||||
use Friendica\Util\Network;
|
use Friendica\Util\Network;
|
||||||
use Friendica\Util\Profiler;
|
|
||||||
use Friendica\Util\Strings;
|
use Friendica\Util\Strings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -591,8 +590,7 @@ class Installer
|
||||||
/**
|
/**
|
||||||
* Checking the Database connection and if it is available for the current installation
|
* Checking the Database connection and if it is available for the current installation
|
||||||
*
|
*
|
||||||
* @param ConfigCache $configCache The configuration cache
|
* @param Database $dba
|
||||||
* @param Profiler $profiler The profiler of this app
|
|
||||||
*
|
*
|
||||||
* @return bool true if the check was successful, otherwise false
|
* @return bool true if the check was successful, otherwise false
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
|
|
|
@ -1224,29 +1224,6 @@ class Profile
|
||||||
return $uid;
|
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
|
* search for Profiles
|
||||||
*
|
*
|
||||||
|
|
|
@ -111,7 +111,7 @@ class Install extends BaseModule
|
||||||
self::checkSetting($configCache, $_POST, 'database', 'database', '');
|
self::checkSetting($configCache, $_POST, 'database', 'database', '');
|
||||||
|
|
||||||
// If we cannot connect to the database, return to the previous step
|
// 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::$currentWizardStep = self::DATABASE_CONFIG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ class Install extends BaseModule
|
||||||
self::checkSetting($configCache, $_POST, 'config', 'admin_email', '');
|
self::checkSetting($configCache, $_POST, 'config', 'admin_email', '');
|
||||||
|
|
||||||
// If we cannot connect to the database, return to the Database config wizard
|
// 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;
|
self::$currentWizardStep = self::DATABASE_CONFIG;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,4 +136,16 @@ return [
|
||||||
['create', [], Dice::CHAIN_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],
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
242
tests/src/App/ArgumentsTest.php
Normal file
242
tests/src/App/ArgumentsTest.php
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,22 +38,20 @@ class ModeTest extends MockedTest
|
||||||
$this->setUpVfsDir();
|
$this->setUpVfsDir();
|
||||||
|
|
||||||
$this->basePathMock = \Mockery::mock(BasePath::class);
|
$this->basePathMock = \Mockery::mock(BasePath::class);
|
||||||
$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
|
|
||||||
|
|
||||||
$this->databaseMock = \Mockery::mock(Database::class);
|
$this->databaseMock = \Mockery::mock(Database::class);
|
||||||
$this->configCacheMock = \Mockery::mock(Config\Cache\ConfigCache::class);
|
$this->configCacheMock = \Mockery::mock(Config\Cache\ConfigCache::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testItEmpty()
|
public function testItEmpty()
|
||||||
{
|
{
|
||||||
$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
$mode = new Mode();
|
||||||
$this->assertTrue($mode->isInstall());
|
$this->assertTrue($mode->isInstall());
|
||||||
$this->assertFalse($mode->isNormal());
|
$this->assertFalse($mode->isNormal());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testWithoutConfig()
|
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->assertTrue($this->root->hasChild('config/local.config.php'));
|
||||||
|
|
||||||
|
@ -61,7 +59,7 @@ class ModeTest extends MockedTest
|
||||||
|
|
||||||
$this->assertFalse($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->assertTrue($mode->isInstall());
|
||||||
$this->assertFalse($mode->isNormal());
|
$this->assertFalse($mode->isNormal());
|
||||||
|
@ -71,10 +69,11 @@ class ModeTest extends MockedTest
|
||||||
|
|
||||||
public function testWithoutDatabase()
|
public function testWithoutDatabase()
|
||||||
{
|
{
|
||||||
|
$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
|
||||||
|
|
||||||
$this->databaseMock->shouldReceive('connected')->andReturn(false)->once();
|
$this->databaseMock->shouldReceive('connected')->andReturn(false)->once();
|
||||||
|
|
||||||
$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
||||||
$mode->determine();
|
|
||||||
|
|
||||||
$this->assertFalse($mode->isNormal());
|
$this->assertFalse($mode->isNormal());
|
||||||
$this->assertTrue($mode->isInstall());
|
$this->assertTrue($mode->isInstall());
|
||||||
|
@ -85,12 +84,13 @@ class ModeTest extends MockedTest
|
||||||
|
|
||||||
public function testWithoutDatabaseSetup()
|
public function testWithoutDatabaseSetup()
|
||||||
{
|
{
|
||||||
|
$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
|
||||||
|
|
||||||
$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
|
$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
|
||||||
$this->databaseMock->shouldReceive('fetchFirst')
|
$this->databaseMock->shouldReceive('fetchFirst')
|
||||||
->with('SHOW TABLES LIKE \'config\'')->andReturn(false)->once();
|
->with('SHOW TABLES LIKE \'config\'')->andReturn(false)->once();
|
||||||
|
|
||||||
$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
||||||
$mode->determine();
|
|
||||||
|
|
||||||
$this->assertFalse($mode->isNormal());
|
$this->assertFalse($mode->isNormal());
|
||||||
$this->assertTrue($mode->isInstall());
|
$this->assertTrue($mode->isInstall());
|
||||||
|
@ -100,14 +100,15 @@ class ModeTest extends MockedTest
|
||||||
|
|
||||||
public function testWithMaintenanceMode()
|
public function testWithMaintenanceMode()
|
||||||
{
|
{
|
||||||
|
$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
|
||||||
|
|
||||||
$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
|
$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
|
||||||
$this->databaseMock->shouldReceive('fetchFirst')
|
$this->databaseMock->shouldReceive('fetchFirst')
|
||||||
->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
|
->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
|
||||||
$this->configCacheMock->shouldReceive('get')->with('system', 'maintenance')
|
$this->configCacheMock->shouldReceive('get')->with('system', 'maintenance')
|
||||||
->andReturn(true)->once();
|
->andReturn(true)->once();
|
||||||
|
|
||||||
$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
||||||
$mode->determine();
|
|
||||||
|
|
||||||
$this->assertFalse($mode->isNormal());
|
$this->assertFalse($mode->isNormal());
|
||||||
$this->assertFalse($mode->isInstall());
|
$this->assertFalse($mode->isInstall());
|
||||||
|
@ -118,6 +119,8 @@ class ModeTest extends MockedTest
|
||||||
|
|
||||||
public function testNormalMode()
|
public function testNormalMode()
|
||||||
{
|
{
|
||||||
|
$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
|
||||||
|
|
||||||
$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
|
$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
|
||||||
$this->databaseMock->shouldReceive('fetchFirst')
|
$this->databaseMock->shouldReceive('fetchFirst')
|
||||||
->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
|
->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
|
||||||
|
@ -127,8 +130,7 @@ class ModeTest extends MockedTest
|
||||||
->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])
|
->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])
|
||||||
->andReturn(['v' => null])->once();
|
->andReturn(['v' => null])->once();
|
||||||
|
|
||||||
$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
||||||
$mode->determine();
|
|
||||||
|
|
||||||
$this->assertTrue($mode->isNormal());
|
$this->assertTrue($mode->isNormal());
|
||||||
$this->assertFalse($mode->isInstall());
|
$this->assertFalse($mode->isInstall());
|
||||||
|
@ -142,6 +144,8 @@ class ModeTest extends MockedTest
|
||||||
*/
|
*/
|
||||||
public function testDisabledMaintenance()
|
public function testDisabledMaintenance()
|
||||||
{
|
{
|
||||||
|
$this->basePathMock->shouldReceive('getPath')->andReturn($this->root->url())->once();
|
||||||
|
|
||||||
$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
|
$this->databaseMock->shouldReceive('connected')->andReturn(true)->once();
|
||||||
$this->databaseMock->shouldReceive('fetchFirst')
|
$this->databaseMock->shouldReceive('fetchFirst')
|
||||||
->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
|
->with('SHOW TABLES LIKE \'config\'')->andReturn(true)->once();
|
||||||
|
@ -151,8 +155,7 @@ class ModeTest extends MockedTest
|
||||||
->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])
|
->with('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])
|
||||||
->andReturn(['v' => '0'])->once();
|
->andReturn(['v' => '0'])->once();
|
||||||
|
|
||||||
$mode = new Mode($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
$mode = (new Mode())->determine($this->basePathMock, $this->databaseMock, $this->configCacheMock);
|
||||||
$mode->determine();
|
|
||||||
|
|
||||||
$this->assertTrue($mode->isNormal());
|
$this->assertTrue($mode->isNormal());
|
||||||
$this->assertFalse($mode->isInstall());
|
$this->assertFalse($mode->isInstall());
|
||||||
|
@ -160,4 +163,18 @@ class ModeTest extends MockedTest
|
||||||
$this->assertTrue($mode->has(Mode::DBCONFIGAVAILABLE));
|
$this->assertTrue($mode->has(Mode::DBCONFIGAVAILABLE));
|
||||||
$this->assertTrue($mode->has(Mode::MAINTENANCEDISABLED));
|
$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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
189
tests/src/App/ModuleTest.php
Normal file
189
tests/src/App/ModuleTest.php
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue