]> git.mxchange.org Git - friendica.git/blob - src/App/Module.php
cf2c453ce7e0a39289502f2a571605d278b7de5b
[friendica.git] / src / App / Module.php
1 <?php
2
3 namespace Friendica\App;
4
5 use Friendica\App;
6 use Friendica\BaseObject;
7 use Friendica\Core;
8 use Friendica\LegacyModule;
9 use Friendica\Module\Home;
10 use Friendica\Module\PageNotFound;
11 use Psr\Log\LoggerInterface;
12
13 /**
14  * Holds the common context of the current, loaded module
15  */
16 class Module
17 {
18         const DEFAULT = 'home';
19         const DEFAULT_CLASS = Home::class;
20
21         /**
22          * @var string The module name
23          */
24         private $module;
25
26         /**
27          * @var BaseObject The module class
28          */
29         private $module_class;
30
31         /**
32          * @var bool true, if the module is a backend module
33          */
34         private $isBackend;
35
36         /**
37          * @var bool true, if the loaded addon is private, so we have to print out not allowed
38          */
39         private $printNotAllowedAddon;
40
41         /**
42          * A list of modules, which are backend methods
43          *
44          * @var array
45          */
46         const BACKEND_MODULES = [
47                 '_well_known',
48                 'api',
49                 'dfrn_notify',
50                 'feed',
51                 'fetch',
52                 'followers',
53                 'following',
54                 'hcard',
55                 'hostxrd',
56                 'inbox',
57                 'manifest',
58                 'nodeinfo',
59                 'noscrape',
60                 'objects',
61                 'outbox',
62                 'poco',
63                 'post',
64                 'proxy',
65                 'pubsub',
66                 'pubsubhubbub',
67                 'receive',
68                 'rsd_xml',
69                 'salmon',
70                 'statistics_json',
71                 'xrd',
72         ];
73
74         /**
75          * @return string
76          */
77         public function getName()
78         {
79                 return $this->module;
80         }
81
82         /**
83          * @return string The base class name
84          */
85         public function getClassName()
86         {
87                 return $this->module_class;
88         }
89
90         /**
91          * @return bool
92          */
93         public function isBackend()
94         {
95                 return $this->isBackend;
96         }
97
98         public function __construct(string $module = self::DEFAULT, string $moduleClass = self::DEFAULT_CLASS, bool $isBackend = false, bool $printNotAllowedAddon = false)
99         {
100                 $this->module       = $module;
101                 $this->module_class = $moduleClass;
102                 $this->isBackend    = $isBackend;
103                 $this->printNotAllowedAddon = $printNotAllowedAddon;
104         }
105
106         /**
107          * Determines the current module based on the App arguments and the server variable
108          *
109          * @param Arguments $args   The Friendica arguments
110          * @param array     $server The $_SERVER variable
111          *
112          * @return Module The module with the determined module
113          */
114         public function determineModule(Arguments $args, array $server)
115         {
116                 if ($args->getArgc() > 0) {
117                         $module = str_replace('.', '_', $args->get(0));
118                         $module = str_replace('-', '_', $module);
119                 } else {
120                         $module = self::DEFAULT;
121                 }
122
123                 // Compatibility with the Firefox App
124                 if (($module == "users") && ($args->getCommand() == "users/sign_in")) {
125                         $module = "login";
126                 }
127
128                 $isBackend = $this->checkBackend($module, $server);
129
130                 return new Module($module, $this->module_class, $isBackend, $this->printNotAllowedAddon);
131         }
132
133         /**
134          * Determine the class of the current module
135          *
136          * @param Arguments                 $args   The Friendica execution arguments
137          * @param Router                    $router The Friendica routing instance
138          * @param Core\Config\Configuration $config The Friendica Configuration
139          *
140          * @return Module The determined module of this call
141          *
142          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
143          */
144         public function determineClass(Arguments $args, Router $router, Core\Config\Configuration $config)
145         {
146                 $printNotAllowedAddon = false;
147
148                 /**
149                  * ROUTING
150                  *
151                  * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the
152                  * post() and/or content() static methods can be respectively called to produce a data change or an output.
153                  **/
154
155                 // First we try explicit routes defined in App\Router
156                 $router->collectRoutes();
157
158                 $data = $router->getRouteCollector();
159                 Core\Hook::callAll('route_collection', $data);
160
161                 $module_class = $router->getModuleClass($args->getCommand());
162
163                 // Then we try addon-provided modules that we wrap in the LegacyModule class
164                 if (!$module_class && Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) {
165                         //Check if module is an app and if public access to apps is allowed or not
166                         $privateapps = $config->get('config', 'private_addons', false);
167                         if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) {
168                                 $printNotAllowedAddon = true;
169                         } else {
170                                 include_once "addon/{$this->module}/{$this->module}.php";
171                                 if (function_exists($this->module . '_module')) {
172                                         LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php");
173                                         $module_class = LegacyModule::class;
174                                 }
175                         }
176                 }
177
178                 /* Finally, we look for a 'standard' program module in the 'mod' directory
179                  * We emulate a Module class through the LegacyModule class
180                  */
181                 if (!$module_class && file_exists("mod/{$this->module}.php")) {
182                         LegacyModule::setModuleFile("mod/{$this->module}.php");
183                         $module_class = LegacyModule::class;
184                 }
185
186                 $module_class = !isset($module_class) ? PageNotFound::class : $module_class;
187
188                 return new Module($this->module, $module_class, $this->isBackend, $printNotAllowedAddon);
189         }
190
191         /**
192          * Run the determined module class and calls all hooks applied to
193          *
194          * @param Core\L10n\L10n $l10n         The L10n instance
195          * @param App            $app          The whole Friendica app (for method arguments)
196          * @param LoggerInterface           $logger The Friendica logger
197          * @param string         $currentTheme The chosen theme
198          * @param array          $server       The $_SERVER variable
199          * @param array          $post         The $_POST variables
200          *
201          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
202          */
203         public function run(Core\L10n\L10n $l10n, App $app,  LoggerInterface $logger, string $currentTheme, array $server, array $post)
204         {
205                 if ($this->printNotAllowedAddon) {
206                         info($l10n->t("You must be logged in to use addons. "));
207                 }
208
209                 /* The URL provided does not resolve to a valid module.
210                  *
211                  * On Dreamhost sites, quite often things go wrong for no apparent reason and they send us to '/internal_error.html'.
212                  * We don't like doing this, but as it occasionally accounts for 10-20% or more of all site traffic -
213                  * 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
214                  * this will often succeed and eventually do the right thing.
215                  *
216                  * Otherwise we are going to emit a 404 not found.
217                  */
218                 if ($this->module_class === PageNotFound::class) {
219                         $queryString = $server['QUERY_STRING'];
220                         // Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
221                         if (!empty($queryString) && preg_match('/{[0-9]}/', $queryString) !== 0) {
222                                 exit();
223                         }
224
225                         if (!empty($queryString) && ($queryString === 'q=internal_error.html') && isset($dreamhost_error_hack)) {
226                                 $logger->info('index.php: dreamhost_error_hack invoked.', ['Original URI' => $server['REQUEST_URI']]);
227                                 $app->internalRedirect($server['REQUEST_URI']);
228                         }
229
230                         $logger->debug('index.php: page not found.', ['request_uri' => $server['REQUEST_URI'], 'address' => $server['REMOTE_ADDR'], 'query' => $server['QUERY_STRING']]);
231                 }
232
233                 $placeholder = '';
234
235                 Core\Hook::callAll($this->module . '_mod_init', $placeholder);
236
237                 call_user_func([$this->module_class, 'init']);
238
239                 // "rawContent" is especially meant for technical endpoints.
240                 // This endpoint doesn't need any theme initialization or other comparable stuff.
241                 call_user_func([$this->module_class, 'rawContent']);
242
243                 // Load current theme info after module has been initialized as theme could have been set in module
244                 $theme_info_file = 'view/theme/' . $currentTheme . '/theme.php';
245                 if (file_exists($theme_info_file)) {
246                         require_once $theme_info_file;
247                 }
248
249                 if (function_exists(str_replace('-', '_', $currentTheme) . '_init')) {
250                         $func = str_replace('-', '_', $currentTheme) . '_init';
251                         $func($app);
252                 }
253
254                 if ($server['REQUEST_METHOD'] === 'POST') {
255                         Core\Hook::callAll($this->module . '_mod_post', $post);
256                         call_user_func([$this->module_class, 'post']);
257                 }
258
259                 Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
260                 call_user_func([$this->module_class, 'afterpost']);
261         }
262
263         /**
264          * @brief Checks if the site is called via a backend process
265          *
266          * This isn't a perfect solution. But we need this check very early.
267          * So we cannot wait until the modules are loaded.
268          *
269          * @param string $module The determined module
270          * @param array  $server The $_SERVER variable
271          *
272          * @return bool True, if the current module is called at backend
273          */
274         private function checkBackend($module, array $server)
275         {
276                 // Check if current module is in backend or backend flag is set
277                 return basename(($server['PHP_SELF'] ?? ''), '.php') !== 'index' &&
278                        in_array($module, Module::BACKEND_MODULES);
279         }
280 }