]> git.mxchange.org Git - friendica.git/blob - src/App/Router.php
The "item-activity" is removed
[friendica.git] / src / App / Router.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\App;
23
24
25 use FastRoute\DataGenerator\GroupCountBased;
26 use FastRoute\Dispatcher;
27 use FastRoute\RouteCollector;
28 use FastRoute\RouteParser\Std;
29 use Friendica\Core\Hook;
30 use Friendica\Core\L10n;
31 use Friendica\Network\HTTPException;
32
33 /**
34  * Wrapper for FastRoute\Router
35  *
36  * This wrapper only makes use of a subset of the router features, mainly parses a route rule to return the relevant
37  * module class.
38  *
39  * Actual routes are defined in App->collectRoutes.
40  *
41  * @package Friendica\App
42  */
43 class Router
44 {
45         const POST = 'POST';
46         const GET  = 'GET';
47
48         const ALLOWED_METHODS = [
49                 self::POST,
50                 self::GET,
51         ];
52
53         /** @var RouteCollector */
54         protected $routeCollector;
55
56         /**
57          * @var string The HTTP method
58          */
59         private $httpMethod;
60
61         /**
62          * @var array Module parameters
63          */
64         private $parameters = [];
65
66         /** @var L10n */
67         private $l10n;
68
69         /**
70          * @param array $server The $_SERVER variable
71          * @param L10n  $l10n
72          * @param RouteCollector|null $routeCollector Optional the loaded Route collector
73          */
74         public function __construct(array $server, L10n $l10n, RouteCollector $routeCollector = null)
75         {
76                 $this->l10n = $l10n;
77
78                 $httpMethod = $server['REQUEST_METHOD'] ?? self::GET;
79                 $this->httpMethod = in_array($httpMethod, self::ALLOWED_METHODS) ? $httpMethod : self::GET;
80
81                 $this->routeCollector = isset($routeCollector) ?
82                         $routeCollector :
83                         new RouteCollector(new Std(), new GroupCountBased());
84         }
85
86         /**
87          * @param array $routes The routes to add to the Router
88          *
89          * @return self The router instance with the loaded routes
90          *
91          * @throws HTTPException\InternalServerErrorException In case of invalid configs
92          */
93         public function loadRoutes(array $routes)
94         {
95                 $routeCollector = (isset($this->routeCollector) ?
96                         $this->routeCollector :
97                         new RouteCollector(new Std(), new GroupCountBased()));
98
99                 $this->addRoutes($routeCollector, $routes);
100
101                 $this->routeCollector = $routeCollector;
102
103                 return $this;
104         }
105
106         private function addRoutes(RouteCollector $routeCollector, array $routes)
107         {
108                 foreach ($routes as $route => $config) {
109                         if ($this->isGroup($config)) {
110                                 $this->addGroup($route, $config, $routeCollector);
111                         } elseif ($this->isRoute($config)) {
112                                 $routeCollector->addRoute($config[1], $route, $config[0]);
113                         } else {
114                                 throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
115                         }
116                 }
117         }
118
119         /**
120          * Adds a group of routes to a given group
121          *
122          * @param string         $groupRoute     The route of the group
123          * @param array          $routes         The routes of the group
124          * @param RouteCollector $routeCollector The route collector to add this group
125          */
126         private function addGroup(string $groupRoute, array $routes, RouteCollector $routeCollector)
127         {
128                 $routeCollector->addGroup($groupRoute, function (RouteCollector $routeCollector) use ($routes) {
129                         $this->addRoutes($routeCollector, $routes);
130                 });
131         }
132
133         /**
134          * Returns true in case the config is a group config
135          *
136          * @param array $config
137          *
138          * @return bool
139          */
140         private function isGroup(array $config)
141         {
142                 return
143                         is_array($config) &&
144                         is_string(array_keys($config)[0]) &&
145                         // This entry should NOT be a BaseModule
146                         (substr(array_keys($config)[0], 0, strlen('Friendica\Module')) !== 'Friendica\Module') &&
147                         // The second argument is an array (another routes)
148                         is_array(array_values($config)[0]);
149         }
150
151         /**
152          * Returns true in case the config is a route config
153          *
154          * @param array $config
155          *
156          * @return bool
157          */
158         private function isRoute(array $config)
159         {
160                 return
161                         // The config array should at least have one entry
162                         !empty($config[0]) &&
163                         // This entry should be a BaseModule
164                         (substr($config[0], 0, strlen('Friendica\Module')) === 'Friendica\Module') &&
165                         // Either there is no other argument
166                         (empty($config[1]) ||
167                          // Or the second argument is an array (HTTP-Methods)
168                          is_array($config[1]));
169         }
170
171         /**
172          * The current route collector
173          *
174          * @return RouteCollector|null
175          */
176         public function getRouteCollector()
177         {
178                 return $this->routeCollector;
179         }
180
181         /**
182          * Returns the relevant module class name for the given page URI or NULL if no route rule matched.
183          *
184          * @param string $cmd The path component of the request URL without the query string
185          *
186          * @return string A Friendica\BaseModule-extending class name if a route rule matched
187          *
188          * @throws HTTPException\InternalServerErrorException
189          * @throws HTTPException\MethodNotAllowedException    If a rule matched but the method didn't
190          * @throws HTTPException\NotFoundException            If no rule matched
191          */
192         public function getModuleClass($cmd)
193         {
194                 // Add routes from addons
195                 Hook::callAll('route_collection', $this->routeCollector);
196
197                 $cmd = '/' . ltrim($cmd, '/');
198
199                 $dispatcher = new Dispatcher\GroupCountBased($this->routeCollector->getData());
200
201                 $moduleClass = null;
202                 $this->parameters = [];
203
204                 $routeInfo  = $dispatcher->dispatch($this->httpMethod, $cmd);
205                 if ($routeInfo[0] === Dispatcher::FOUND) {
206                         $moduleClass = $routeInfo[1];
207                         $this->parameters = $routeInfo[2];
208                 } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
209                         throw new HTTPException\MethodNotAllowedException($this->l10n->t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1])));
210                 } else {
211                         throw new HTTPException\NotFoundException($this->l10n->t('Page not found.'));
212                 }
213
214                 return $moduleClass;
215         }
216
217         /**
218          * Returns the module parameters.
219          *
220          * @return array parameters
221          */
222         public function getModuleParameters()
223         {
224                 return $this->parameters;
225         }
226 }