3 * @copyright Copyright (C) 2020, Friendica
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\App;
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;
34 * Wrapper for FastRoute\Router
36 * This wrapper only makes use of a subset of the router features, mainly parses a route rule to return the relevant
39 * Actual routes are defined in App->collectRoutes.
41 * @package Friendica\App
48 const ALLOWED_METHODS = [
53 /** @var RouteCollector */
54 protected $routeCollector;
57 * @var string The HTTP method
62 * @var array Module parameters
64 private $parameters = [];
70 * @param array $server The $_SERVER variable
72 * @param RouteCollector|null $routeCollector Optional the loaded Route collector
74 public function __construct(array $server, L10n $l10n, RouteCollector $routeCollector = null)
78 $httpMethod = $server['REQUEST_METHOD'] ?? self::GET;
79 $this->httpMethod = in_array($httpMethod, self::ALLOWED_METHODS) ? $httpMethod : self::GET;
81 $this->routeCollector = isset($routeCollector) ?
83 new RouteCollector(new Std(), new GroupCountBased());
87 * @param array $routes The routes to add to the Router
89 * @return self The router instance with the loaded routes
91 * @throws HTTPException\InternalServerErrorException In case of invalid configs
93 public function loadRoutes(array $routes)
95 $routeCollector = (isset($this->routeCollector) ?
96 $this->routeCollector :
97 new RouteCollector(new Std(), new GroupCountBased()));
99 $this->addRoutes($routeCollector, $routes);
101 $this->routeCollector = $routeCollector;
106 private function addRoutes(RouteCollector $routeCollector, array $routes)
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]);
114 throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
120 * Adds a group of routes to a given group
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
126 private function addGroup(string $groupRoute, array $routes, RouteCollector $routeCollector)
128 $routeCollector->addGroup($groupRoute, function (RouteCollector $routeCollector) use ($routes) {
129 $this->addRoutes($routeCollector, $routes);
134 * Returns true in case the config is a group config
136 * @param array $config
140 private function isGroup(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]);
152 * Returns true in case the config is a route config
154 * @param array $config
158 private function isRoute(array $config)
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]));
172 * The current route collector
174 * @return RouteCollector|null
176 public function getRouteCollector()
178 return $this->routeCollector;
182 * Returns the relevant module class name for the given page URI or NULL if no route rule matched.
184 * @param string $cmd The path component of the request URL without the query string
186 * @return string A Friendica\BaseModule-extending class name if a route rule matched
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
192 public function getModuleClass($cmd)
194 // Add routes from addons
195 Hook::callAll('route_collection', $this->routeCollector);
197 $cmd = '/' . ltrim($cmd, '/');
199 $dispatcher = new Dispatcher\GroupCountBased($this->routeCollector->getData());
202 $this->parameters = [];
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])));
211 throw new HTTPException\NotFoundException($this->l10n->t('Page not found.'));
218 * Returns the module parameters.
220 * @return array parameters
222 public function getModuleParameters()
224 return $this->parameters;