diff --git a/index.php b/index.php index 0afd2c7d3e..95a1306b39 100644 --- a/index.php +++ b/index.php @@ -41,11 +41,9 @@ $a = \Friendica\DI::app(); \Friendica\DI::mode()->setExecutor(\Friendica\App\Mode::INDEX); $a->runFrontend( - $dice->create(\Friendica\App\ModuleController::class), $dice->create(\Friendica\App\Router::class), $dice->create(\Friendica\Core\PConfig\Capability\IManagePersonalConfigValues::class), $dice->create(\Friendica\Security\Authentication::class), $dice->create(\Friendica\App\Page::class), - $dice, $start_time ); diff --git a/src/App.php b/src/App.php index f4534c0158..9083e98ad6 100644 --- a/src/App.php +++ b/src/App.php @@ -21,11 +21,9 @@ namespace Friendica; -use Dice\Dice; use Exception; use Friendica\App\Arguments; use Friendica\App\BaseURL; -use Friendica\App\ModuleController; use Friendica\Core\Config\Factory\Config; use Friendica\Module\Maintenance; use Friendica\Security\Authentication; @@ -567,7 +565,6 @@ class App * * This probably should change to limit the size of this monster method. * - * @param App\ModuleController $module The determined module * @param App\Router $router * @param IManagePersonalConfigValues $pconfig * @param Authentication $auth The Authentication backend of the node @@ -576,12 +573,12 @@ class App * @throws HTTPException\InternalServerErrorException * @throws \ImagickException */ - public function runFrontend(App\ModuleController $module, App\Router $router, IManagePersonalConfigValues $pconfig, Authentication $auth, App\Page $page, Dice $dice, float $start_time) + public function runFrontend(App\Router $router, IManagePersonalConfigValues $pconfig, Authentication $auth, App\Page $page, float $start_time) { $this->profiler->set($start_time, 'start'); $this->profiler->set(microtime(true), 'classinit'); - $moduleName = $module->getName(); + $moduleName = $this->args->getModuleName(); try { // Missing DB connection: ERROR @@ -703,20 +700,20 @@ class App $page['page_title'] = $moduleName; if (!$this->mode->isInstall() && !$this->mode->has(App\Mode::MAINTENANCEDISABLED)) { - $module = new ModuleController('maintenance', new Maintenance($this->l10n)); + $module = new Maintenance($this->l10n); } else { // 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, $dice); + $module = $router->getModule(); } // Let the module run it's internal process (init, get, post, ...) - $module->run($this->l10n, $this->baseURL, $this->logger, $this->profiler, $_SERVER, $_POST); + $module->run($this->baseURL, $this->args, $this->logger, $this->profiler, $_SERVER, $_POST); } catch (HTTPException $e) { (new ModuleHTTPException())->rawContent($e); } - $page->run($this, $this->baseURL, $this->mode, $module, $this->l10n, $this->profiler, $this->config, $pconfig); + $page->run($this, $this->baseURL, $this->args, $this->mode, $module, $this->l10n, $this->profiler, $this->config, $pconfig); } /** diff --git a/src/App/Arguments.php b/src/App/Arguments.php index ae6c64a4f3..19f8e92123 100644 --- a/src/App/Arguments.php +++ b/src/App/Arguments.php @@ -30,6 +30,8 @@ namespace Friendica\App; */ class Arguments { + const DEFAULT_MODULE = 'home'; + /** * @var string The complete query string */ @@ -38,6 +40,10 @@ class Arguments * @var string The current Friendica command */ private $command; + /** + * @var string The name of the current module + */ + private $moduleName; /** * @var array The arguments of the current execution */ @@ -47,10 +53,11 @@ class Arguments */ private $argc; - public function __construct(string $queryString = '', string $command = '', array $argv = [], int $argc = 0) + public function __construct(string $queryString = '', string $command = '', string $moduleName = '', array $argv = [], int $argc = 0) { $this->queryString = $queryString; $this->command = $command; + $this->moduleName = $moduleName; $this->argv = $argv; $this->argc = $argc; } @@ -71,6 +78,14 @@ class Arguments return $this->command; } + /** + * @return string The module name based on the arguments + */ + public function getModuleName(): string + { + return $this->moduleName; + } + /** * @return array All arguments of this call */ @@ -172,6 +187,18 @@ class Arguments $queryString = $command . ($queryParameters ? '?' . http_build_query($queryParameters) : ''); - return new Arguments($queryString, $command, $argv, $argc); + if ($argc > 0) { + $module = str_replace('.', '_', $argv[0]); + $module = str_replace('-', '_', $module); + } else { + $module = self::DEFAULT_MODULE; + } + + // Compatibility with the Firefox App + if (($module == "users") && ($command == "users/sign_in")) { + $module = "login"; + } + + return new Arguments($queryString, $command, $module, $argv, $argc); } } diff --git a/src/App/Mode.php b/src/App/Mode.php index 4a1213ae12..5d26a2d45e 100644 --- a/src/App/Mode.php +++ b/src/App/Mode.php @@ -25,6 +25,7 @@ use Detection\MobileDetect; use Friendica\Core\Config\ValueObject\Cache; use Friendica\Database\Database; use Friendica\Util\BasePath; +use phpDocumentor\Reflection\Types\Static_; /** * Mode of the current Friendica Node @@ -46,6 +47,38 @@ class Mode const BACKEND_CONTENT_TYPES = ['application/jrd+json', 'text/xml', 'application/rss+xml', 'application/atom+xml', 'application/activity+json']; + /** + * 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', + 'pubsub', + 'pubsubhubbub', + 'receive', + 'rsd_xml', + 'salmon', + 'statistics_json', + 'xrd', + ]; + /*** * @var int The mode of this Application * @@ -140,13 +173,13 @@ class Mode * Checks if the site is called via a backend process * * @param bool $isBackend True, if the call is from a backend script (daemon, worker, ...) - * @param ModuleController $module The pre-loaded module (just name, not class!) * @param array $server The $_SERVER variable + * @param Arguments $args The Friendica App arguments * @param MobileDetect $mobileDetect The mobile detection library * * @return Mode returns the determined mode */ - public function determineRunMode(bool $isBackend, ModuleController $module, array $server, MobileDetect $mobileDetect) + public function determineRunMode(bool $isBackend, array $server, Arguments $args, MobileDetect $mobileDetect) { foreach (self::BACKEND_CONTENT_TYPES as $type) { if (strpos(strtolower($server['HTTP_ACCEPT'] ?? ''), $type) !== false) { @@ -154,7 +187,7 @@ class Mode } } - $isBackend = $isBackend || $module->isBackend(); + $isBackend = $isBackend || in_array($args->getModuleName(), static::BACKEND_MODULES); $isMobile = $mobileDetect->isMobile(); $isTablet = $mobileDetect->isTablet(); $isAjax = strtolower($server['HTTP_X_REQUESTED_WITH'] ?? '') == 'xmlhttprequest'; diff --git a/src/App/ModuleController.php b/src/App/ModuleController.php deleted file mode 100644 index ae27236398..0000000000 --- a/src/App/ModuleController.php +++ /dev/null @@ -1,321 +0,0 @@ -. - * - */ - -namespace Friendica\App; - -use Dice\Dice; -use Friendica\App; -use Friendica\Capabilities\ICanHandleRequests; -use Friendica\Core; -use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\LegacyModule; -use Friendica\Module\Home; -use Friendica\Module\HTTPException\MethodNotAllowed; -use Friendica\Module\HTTPException\PageNotFound; -use Friendica\Network\HTTPException\MethodNotAllowedException; -use Friendica\Network\HTTPException\NoContentException; -use Friendica\Network\HTTPException\NotFoundException; -use Friendica\Util\Profiler; -use Psr\Log\LoggerInterface; - -/** - * Holds the common context of the current, loaded module - */ -class ModuleController -{ - const DEFAULT = 'home'; - const DEFAULT_CLASS = Home::class; - /** - * 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', - 'pubsub', - 'pubsubhubbub', - 'receive', - 'rsd_xml', - 'salmon', - 'statistics_json', - 'xrd', - ]; - - /** - * @var string The module name - */ - private $moduleName; - - /** - * @var ?ICanHandleRequests The module object - */ - private $module; - - /** - * @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; - - /** - * @return string - */ - public function getName() - { - return $this->moduleName; - } - - /** - * @return ?ICanHandleRequests The base module object - */ - public function getModule(): ?ICanHandleRequests - { - return $this->module; - } - - /** - * @return bool True, if the current module is a backend module - * @see ModuleController::BACKEND_MODULES for a list - */ - public function isBackend() - { - return $this->isBackend; - } - - public function __construct(string $moduleName = self::DEFAULT, ?ICanHandleRequests $module = null, bool $isBackend = false, bool $printNotAllowedAddon = false) - { - $this->moduleName = $moduleName; - $this->module = $module; - $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 - * - * @return ModuleController The module with the determined module - */ - public function determineName(Arguments $args) - { - 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 = in_array($module, ModuleController::BACKEND_MODULES); - - return new ModuleController($module, null, $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 IManageConfigValues $config The Friendica Configuration - * @param Dice $dice The Dependency Injection container - * - * @return ModuleController The determined module of this call - * - * @throws \Exception - */ - public function determineClass(Arguments $args, Router $router, IManageConfigValues $config, Dice $dice) - { - $printNotAllowedAddon = false; - - $module_class = null; - $module_parameters = []; - /** - * 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. - **/ - try { - $module_class = $router->getModuleClass($args->getCommand()); - $module_parameters[] = $router->getModuleParameters(); - } catch (MethodNotAllowedException $e) { - $module_class = MethodNotAllowed::class; - } catch (NotFoundException $e) { - // Then we try addon-provided modules that we wrap in the LegacyModule class - if (Core\Addon::isEnabled($this->moduleName) && file_exists("addon/{$this->moduleName}/{$this->moduleName}.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->moduleName) && $privateapps) { - $printNotAllowedAddon = true; - } else { - include_once "addon/{$this->moduleName}/{$this->moduleName}.php"; - if (function_exists($this->moduleName . '_module')) { - $module_parameters[] = "addon/{$this->moduleName}/{$this->moduleName}.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->moduleName}.php")) { - $module_parameters[] = "mod/{$this->moduleName}.php"; - $module_class = LegacyModule::class; - } - - $module_class = $module_class ?: PageNotFound::class; - } - - /** @var ICanHandleRequests $module */ - $module = $dice->create($module_class, $module_parameters); - - return new ModuleController($this->moduleName, $module, $this->isBackend, $printNotAllowedAddon); - } - - /** - * Run the determined module class and calls all hooks applied to - * - * @param \Friendica\Core\L10n $l10n The L10n instance - * @param App\BaseURL $baseUrl The Friendica Base URL - * @param LoggerInterface $logger The Friendica logger - * @param array $server The $_SERVER variable - * @param array $post The $_POST variables - * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - public function run(Core\L10n $l10n, App\BaseURL $baseUrl, LoggerInterface $logger, Profiler $profiler, array $server, array $post) - { - if ($this->printNotAllowedAddon) { - notice($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 === 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']]); - $baseUrl->redirect($server['REQUEST_URI']); - } - - $logger->debug('index.php: page not found.', ['request_uri' => $server['REQUEST_URI'], 'address' => $server['REMOTE_ADDR'], 'query' => $server['QUERY_STRING']]); - } - - // @see https://github.com/tootsuite/mastodon/blob/c3aef491d66aec743a3a53e934a494f653745b61/config/initializers/cors.rb - if (substr($_REQUEST['pagename'] ?? '', 0, 12) == '.well-known/') { - header('Access-Control-Allow-Origin: *'); - header('Access-Control-Allow-Headers: *'); - header('Access-Control-Allow-Methods: ' . Router::GET); - header('Access-Control-Allow-Credentials: false'); - } elseif (substr($_REQUEST['pagename'] ?? '', 0, 8) == 'profile/') { - header('Access-Control-Allow-Origin: *'); - header('Access-Control-Allow-Headers: *'); - header('Access-Control-Allow-Methods: ' . Router::GET); - header('Access-Control-Allow-Credentials: false'); - } elseif (substr($_REQUEST['pagename'] ?? '', 0, 4) == 'api/') { - header('Access-Control-Allow-Origin: *'); - header('Access-Control-Allow-Headers: *'); - header('Access-Control-Allow-Methods: ' . implode(',', Router::ALLOWED_METHODS)); - header('Access-Control-Allow-Credentials: false'); - header('Access-Control-Expose-Headers: Link'); - } elseif (substr($_REQUEST['pagename'] ?? '', 0, 11) == 'oauth/token') { - header('Access-Control-Allow-Origin: *'); - header('Access-Control-Allow-Headers: *'); - header('Access-Control-Allow-Methods: ' . Router::POST); - header('Access-Control-Allow-Credentials: false'); - } - - // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS - // @todo Check allowed methods per requested path - if ($server['REQUEST_METHOD'] === Router::OPTIONS) { - header('Allow: ' . implode(',', Router::ALLOWED_METHODS)); - throw new NoContentException(); - } - - $placeholder = ''; - - $profiler->set(microtime(true), 'ready'); - $timestamp = microtime(true); - - Core\Hook::callAll($this->moduleName . '_mod_init', $placeholder); - - $profiler->set(microtime(true) - $timestamp, 'init'); - - if ($server['REQUEST_METHOD'] === Router::DELETE) { - $this->module->delete(); - } - - if ($server['REQUEST_METHOD'] === Router::PATCH) { - $this->module->patch(); - } - - if ($server['REQUEST_METHOD'] === Router::POST) { - Core\Hook::callAll($this->moduleName . '_mod_post', $post); - $this->module->post(); - } - - if ($server['REQUEST_METHOD'] === Router::PUT) { - $this->module->put(); - } - - // "rawContent" is especially meant for technical endpoints. - // This endpoint doesn't need any theme initialization or other comparable stuff. - $this->module->rawContent(); - } -} diff --git a/src/App/Page.php b/src/App/Page.php index c1a0e4aa54..0efdc12051 100644 --- a/src/App/Page.php +++ b/src/App/Page.php @@ -25,6 +25,7 @@ use ArrayAccess; use DOMDocument; use DOMXPath; use Friendica\App; +use Friendica\Capabilities\ICanHandleRequests; use Friendica\Content\Nav; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; @@ -191,14 +192,14 @@ class Page implements ArrayAccess * - head.tpl template * * @param App $app The Friendica App instance - * @param ModuleController $module The loaded Friendica module + * @param Arguments $args The Friendica App Arguments * @param L10n $l10n The l10n language instance * @param IManageConfigValues $config The Friendica configuration * @param IManagePersonalConfigValues $pConfig The Friendica personal configuration (for user) * * @throws HTTPException\InternalServerErrorException */ - private function initHead(App $app, ModuleController $module, L10n $l10n, IManageConfigValues $config, IManagePersonalConfigValues $pConfig) + private function initHead(App $app, Arguments $args, L10n $l10n, IManageConfigValues $config, IManagePersonalConfigValues $pConfig) { $interval = ((local_user()) ? $pConfig->get(local_user(), 'system', 'update_interval') : 40000); @@ -212,8 +213,8 @@ class Page implements ArrayAccess } // Default title: current module called - if (empty($this->page['title']) && $module->getName()) { - $this->page['title'] = ucfirst($module->getName()); + if (empty($this->page['title']) && $args->getModuleName()) { + $this->page['title'] = ucfirst($args->getModuleName()); } // Prepend the sitename to the page title @@ -337,22 +338,20 @@ class Page implements ArrayAccess * - module content * - hooks for content * - * @param ModuleController $module The module + * @param ICanHandleRequests $module The module * @param Mode $mode The Friendica execution mode * * @throws HTTPException\InternalServerErrorException */ - private function initContent(ModuleController $module, Mode $mode) + private function initContent(ICanHandleRequests $module, Mode $mode) { $content = ''; try { - $moduleClass = $module->getModule(); - $arr = ['content' => $content]; - Hook::callAll($moduleClass->getClassName() . '_mod_content', $arr); + Hook::callAll($module->getClassName() . '_mod_content', $arr); $content = $arr['content']; - $content .= $module->getModule()->content(); + $content .= $module->content(); } catch (HTTPException $e) { $content = (new ModuleHTTPException())->content($e); } @@ -389,17 +388,18 @@ class Page implements ArrayAccess * * @param App $app The Friendica App * @param BaseURL $baseURL The Friendica Base URL + * @param Arguments $args The Friendica App arguments * @param Mode $mode The current node mode - * @param ModuleController $module The loaded Friendica module + * @param ICanHandleRequests $module The loaded Friendica module * @param L10n $l10n The l10n language class * @param IManageConfigValues $config The Configuration of this node * @param IManagePersonalConfigValues $pconfig The personal/user configuration * * @throws HTTPException\InternalServerErrorException */ - public function run(App $app, BaseURL $baseURL, Mode $mode, ModuleController $module, L10n $l10n, Profiler $profiler, IManageConfigValues $config, IManagePersonalConfigValues $pconfig) + public function run(App $app, BaseURL $baseURL, Arguments $args, Mode $mode, ICanHandleRequests $module, L10n $l10n, Profiler $profiler, IManageConfigValues $config, IManagePersonalConfigValues $pconfig) { - $moduleName = $module->getName(); + $moduleName = $args->getModuleName(); /* Create the page content. * Calls all hooks which are including content operations @@ -429,7 +429,7 @@ class Page implements ArrayAccess * all the module functions have executed so that all * theme choices made by the modules can take effect. */ - $this->initHead($app, $module, $l10n, $config, $pconfig); + $this->initHead($app, $args, $l10n, $config, $pconfig); /* Build the page ending -- this is stuff that goes right before * the closing
"> + "> t('Skip to main content'); ?>