3 namespace Friendica\App;
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;
15 * Wrapper for FastRoute\Router
17 * This wrapper only makes use of a subset of the router features, mainly parses a route rule to return the relevant
20 * Actual routes are defined in App->collectRoutes.
22 * @package Friendica\App
29 const ALLOWED_METHODS = [
34 /** @var RouteCollector */
35 protected $routeCollector;
38 * @var string The HTTP method
43 * @var array Module parameters
45 private $parameters = [];
48 * @param array $server The $_SERVER variable
49 * @param RouteCollector|null $routeCollector Optional the loaded Route collector
51 public function __construct(array $server, RouteCollector $routeCollector = null)
53 $httpMethod = $server['REQUEST_METHOD'] ?? self::GET;
54 $this->httpMethod = in_array($httpMethod, self::ALLOWED_METHODS) ? $httpMethod : self::GET;
56 $this->routeCollector = isset($routeCollector) ?
58 new RouteCollector(new Std(), new GroupCountBased());
62 * @param array $routes The routes to add to the Router
64 * @return self The router instance with the loaded routes
66 * @throws HTTPException\InternalServerErrorException In case of invalid configs
68 public function addRoutes(array $routes)
70 $routeCollector = (isset($this->routeCollector) ?
71 $this->routeCollector :
72 new RouteCollector(new Std(), new GroupCountBased()));
74 foreach ($routes as $route => $config) {
75 if ($this->isGroup($config)) {
76 $this->addGroup($route, $config, $routeCollector);
77 } elseif ($this->isRoute($config)) {
78 $routeCollector->addRoute($config[1], $route, $config[0]);
80 throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
84 $this->routeCollector = $routeCollector;
90 * Adds a group of routes to a given group
92 * @param string $groupRoute The route of the group
93 * @param array $routes The routes of the group
94 * @param RouteCollector $routeCollector The route collector to add this group
96 private function addGroup(string $groupRoute, array $routes, RouteCollector $routeCollector)
98 $routeCollector->addGroup($groupRoute, function (RouteCollector $routeCollector) use ($routes) {
99 foreach ($routes as $route => $config) {
100 if ($this->isGroup($config)) {
101 $this->addGroup($route, $config, $routeCollector);
102 } elseif ($this->isRoute($config)) {
103 $routeCollector->addRoute($config[1], $route, $config[0]);
105 throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'");
112 * Returns true in case the config is a group config
114 * @param array $config
118 private function isGroup(array $config)
122 is_string(array_keys($config)[0]) &&
123 // This entry should NOT be a BaseModule
124 (substr(array_keys($config)[0], 0, strlen('Friendica\Module')) !== 'Friendica\Module') &&
125 // The second argument is an array (another routes)
126 is_array(array_values($config)[0]);
130 * Returns true in case the config is a route config
132 * @param array $config
136 private function isRoute(array $config)
139 // The config array should at least have one entry
140 !empty($config[0]) &&
141 // This entry should be a BaseModule
142 (substr($config[0], 0, strlen('Friendica\Module')) === 'Friendica\Module') &&
143 // Either there is no other argument
144 (empty($config[1]) ||
145 // Or the second argument is an array (HTTP-Methods)
146 is_array($config[1]));
150 * The current route collector
152 * @return RouteCollector|null
154 public function getRouteCollector()
156 return $this->routeCollector;
160 * Returns the relevant module class name for the given page URI or NULL if no route rule matched.
162 * @param string $cmd The path component of the request URL without the query string
164 * @return string A Friendica\BaseModule-extending class name if a route rule matched
166 * @throws HTTPException\InternalServerErrorException
167 * @throws HTTPException\MethodNotAllowedException If a rule matched but the method didn't
168 * @throws HTTPException\NotFoundException If no rule matched
170 public function getModuleClass($cmd)
172 // Add routes from addons
173 Hook::callAll('route_collection', $this->routeCollector);
175 $cmd = '/' . ltrim($cmd, '/');
177 $dispatcher = new \FastRoute\Dispatcher\GroupCountBased($this->routeCollector->getData());
180 $this->parameters = [];
182 $routeInfo = $dispatcher->dispatch($this->httpMethod, $cmd);
183 if ($routeInfo[0] === Dispatcher::FOUND) {
184 $moduleClass = $routeInfo[1];
185 $this->parameters = $routeInfo[2];
186 } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
187 throw new HTTPException\MethodNotAllowedException(L10n::t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1])));
189 throw new HTTPException\NotFoundException(L10n::t('Page not found.'));
196 * Returns the module parameters.
198 * @return array parameters
200 public function getModuleParameters()
202 return $this->parameters;