3 namespace Friendica\App;
6 use Friendica\BaseModule;
8 use Friendica\LegacyModule;
9 use Friendica\Module\Home;
10 use Friendica\Module\HTTPException\MethodNotAllowed;
11 use Friendica\Module\HTTPException\PageNotFound;
12 use Friendica\Network\HTTPException\MethodNotAllowedException;
13 use Friendica\Network\HTTPException\NotFoundException;
14 use Psr\Log\LoggerInterface;
17 * Holds the common context of the current, loaded module
21 const DEFAULT = 'home';
22 const DEFAULT_CLASS = Home::class;
24 * A list of modules, which are backend methods
28 const BACKEND_MODULES = [
57 * @var string The module name
62 * @var BaseModule The module class
64 private $module_class;
67 * @var array The module parameters
69 private $module_parameters;
72 * @var bool true, if the module is a backend module
77 * @var bool true, if the loaded addon is private, so we have to print out not allowed
79 private $printNotAllowedAddon;
84 public function getName()
90 * @return string The base class name
92 public function getClassName()
94 return $this->module_class;
98 * @return array The module parameters extracted from the route
100 public function getParameters()
102 return $this->module_parameters;
106 * @return bool True, if the current module is a backend module
107 * @see Module::BACKEND_MODULES for a list
109 public function isBackend()
111 return $this->isBackend;
114 public function __construct(string $module = self::DEFAULT, string $moduleClass = self::DEFAULT_CLASS, array $moduleParameters = [], bool $isBackend = false, bool $printNotAllowedAddon = false)
116 $this->module = $module;
117 $this->module_class = $moduleClass;
118 $this->module_parameters = $moduleParameters;
119 $this->isBackend = $isBackend;
120 $this->printNotAllowedAddon = $printNotAllowedAddon;
124 * Determines the current module based on the App arguments and the server variable
126 * @param Arguments $args The Friendica arguments
128 * @return Module The module with the determined module
130 public function determineModule(Arguments $args)
132 if ($args->getArgc() > 0) {
133 $module = str_replace('.', '_', $args->get(0));
134 $module = str_replace('-', '_', $module);
136 $module = self::DEFAULT;
139 // Compatibility with the Firefox App
140 if (($module == "users") && ($args->getCommand() == "users/sign_in")) {
144 $isBackend = in_array($module, Module::BACKEND_MODULES);;
146 return new Module($module, $this->module_class, [], $isBackend, $this->printNotAllowedAddon);
150 * Determine the class of the current module
152 * @param Arguments $args The Friendica execution arguments
153 * @param Router $router The Friendica routing instance
154 * @param Core\Config\IConfig $config The Friendica Configuration
156 * @return Module The determined module of this call
160 public function determineClass(Arguments $args, Router $router, Core\Config\IConfig $config)
162 $printNotAllowedAddon = false;
164 $module_class = null;
165 $module_parameters = [];
169 * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the
170 * post() and/or content() static methods can be respectively called to produce a data change or an output.
173 $module_class = $router->getModuleClass($args->getCommand());
174 $module_parameters = $router->getModuleParameters();
175 } catch (MethodNotAllowedException $e) {
176 $module_class = MethodNotAllowed::class;
177 } catch (NotFoundException $e) {
178 // Then we try addon-provided modules that we wrap in the LegacyModule class
179 if (Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) {
180 //Check if module is an app and if public access to apps is allowed or not
181 $privateapps = $config->get('config', 'private_addons', false);
182 if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) {
183 $printNotAllowedAddon = true;
185 include_once "addon/{$this->module}/{$this->module}.php";
186 if (function_exists($this->module . '_module')) {
187 LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php");
188 $module_class = LegacyModule::class;
193 /* Finally, we look for a 'standard' program module in the 'mod' directory
194 * We emulate a Module class through the LegacyModule class
196 if (!$module_class && file_exists("mod/{$this->module}.php")) {
197 LegacyModule::setModuleFile("mod/{$this->module}.php");
198 $module_class = LegacyModule::class;
201 $module_class = $module_class ?: PageNotFound::class;
204 return new Module($this->module, $module_class, $module_parameters, $this->isBackend, $printNotAllowedAddon);
208 * Run the determined module class and calls all hooks applied to
210 * @param \Friendica\Core\L10n $l10n The L10n instance
211 * @param App\BaseURL $baseUrl The Friendica Base URL
212 * @param LoggerInterface $logger The Friendica logger
213 * @param array $server The $_SERVER variable
214 * @param array $post The $_POST variables
216 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
218 public function run(Core\L10n $l10n, App\BaseURL $baseUrl, LoggerInterface $logger, array $server, array $post)
220 if ($this->printNotAllowedAddon) {
221 info($l10n->t("You must be logged in to use addons. "));
224 /* The URL provided does not resolve to a valid module.
226 * On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'.
227 * We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic -
228 * 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
229 * this will often succeed and eventually do the right thing.
231 * Otherwise we are going to emit a 404 not found.
233 if ($this->module_class === PageNotFound::class) {
234 $queryString = $server['QUERY_STRING'];
235 // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
236 if (!empty($queryString) && preg_match('/{[0-9]}/', $queryString) !== 0) {
240 if (!empty($queryString) && ($queryString === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
241 $logger->info('index.php: dreamhost_error_hack invoked.', ['Original URI' => $server['REQUEST_URI']]);
242 $baseUrl->redirect($server['REQUEST_URI']);
245 $logger->debug('index.php: page not found.', ['request_uri' => $server['REQUEST_URI'], 'address' => $server['REMOTE_ADDR'], 'query' => $server['QUERY_STRING']]);
250 Core\Hook::callAll($this->module . '_mod_init', $placeholder);
252 call_user_func([$this->module_class, 'init'], $this->module_parameters);
254 if ($server['REQUEST_METHOD'] === 'POST') {
255 Core\Hook::callAll($this->module . '_mod_post', $post);
256 call_user_func([$this->module_class, 'post'], $this->module_parameters);
259 Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
260 call_user_func([$this->module_class, 'afterpost'], $this->module_parameters);
262 // "rawContent" is especially meant for technical endpoints.
263 // This endpoint doesn't need any theme initialization or other comparable stuff.
264 call_user_func([$this->module_class, 'rawContent'], $this->module_parameters);