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