Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
5.3KB

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