2019-04-30 18:32:33 -04:00
< ? php
namespace Friendica\App ;
use FastRoute\DataGenerator\GroupCountBased ;
use FastRoute\Dispatcher ;
use FastRoute\RouteCollector ;
use FastRoute\RouteParser\Std ;
2019-09-26 15:18:01 -04:00
use Friendica\Core\Hook ;
2019-10-11 11:55:02 -04:00
use Friendica\Network\HTTPException ;
2019-04-30 18:32:33 -04:00
/**
* Wrapper for FastRoute\Router
*
* This wrapper only makes use of a subset of the router features , mainly parses a route rule to return the relevant
* module class .
*
* Actual routes are defined in App -> collectRoutes .
*
* @ package Friendica\App
*/
class Router
{
2019-09-26 15:18:01 -04:00
const POST = 'POST' ;
const GET = 'GET' ;
const ALLOWED_METHODS = [
self :: POST ,
self :: GET ,
];
2019-04-30 18:32:33 -04:00
/** @var RouteCollector */
protected $routeCollector ;
/**
2019-09-26 15:18:01 -04:00
* @ var string The HTTP method
2019-04-30 18:32:33 -04:00
*/
2019-09-26 15:18:01 -04:00
private $httpMethod ;
2019-05-02 16:03:27 -04:00
2019-11-05 00:03:05 -05:00
/**
* @ var array Module parameters
*/
private $parameters = [];
2019-09-26 15:18:01 -04:00
/**
* @ param array $server The $_SERVER variable
* @ param RouteCollector | null $routeCollector Optional the loaded Route collector
*/
public function __construct ( array $server , RouteCollector $routeCollector = null )
{
$httpMethod = $server [ 'REQUEST_METHOD' ] ? ? self :: GET ;
$this -> httpMethod = in_array ( $httpMethod , self :: ALLOWED_METHODS ) ? $httpMethod : self :: GET ;
2019-05-02 16:03:27 -04:00
2019-09-26 15:18:01 -04:00
$this -> routeCollector = isset ( $routeCollector ) ?
$routeCollector :
new RouteCollector ( new Std (), new GroupCountBased ());
}
2019-05-02 16:03:27 -04:00
2019-09-26 15:18:01 -04:00
/**
* @ param array $routes The routes to add to the Router
*
* @ return self The router instance with the loaded routes
*
2019-10-11 11:55:02 -04:00
* @ throws HTTPException\InternalServerErrorException In case of invalid configs
2019-09-26 15:18:01 -04:00
*/
2019-11-06 22:34:38 -05:00
public function loadRoutes ( array $routes )
2019-09-26 15:18:01 -04:00
{
$routeCollector = ( isset ( $this -> routeCollector ) ?
$this -> routeCollector :
new RouteCollector ( new Std (), new GroupCountBased ()));
2019-11-06 22:34:38 -05:00
$this -> addRoutes ( $routeCollector , $routes );
$this -> routeCollector = $routeCollector ;
return $this ;
}
private function addRoutes ( RouteCollector $routeCollector , array $routes )
{
2019-09-26 15:18:01 -04:00
foreach ( $routes as $route => $config ) {
if ( $this -> isGroup ( $config )) {
$this -> addGroup ( $route , $config , $routeCollector );
} elseif ( $this -> isRoute ( $config )) {
$routeCollector -> addRoute ( $config [ 1 ], $route , $config [ 0 ]);
} else {
2019-10-11 11:55:02 -04:00
throw new HTTPException\InternalServerErrorException ( " Wrong route config for route ' " . print_r ( $route , true ) . " ' " );
2019-09-26 15:18:01 -04:00
}
}
}
2019-05-01 15:29:04 -04:00
2019-09-26 15:18:01 -04:00
/**
* Adds a group of routes to a given group
*
* @ param string $groupRoute The route of the group
* @ param array $routes The routes of the group
* @ param RouteCollector $routeCollector The route collector to add this group
*/
private function addGroup ( string $groupRoute , array $routes , RouteCollector $routeCollector )
{
$routeCollector -> addGroup ( $groupRoute , function ( RouteCollector $routeCollector ) use ( $routes ) {
2019-11-06 22:34:38 -05:00
$this -> addRoutes ( $routeCollector , $routes );
2019-05-01 15:29:04 -04:00
});
2019-09-26 15:18:01 -04:00
}
2019-05-13 01:38:15 -04:00
2019-09-26 15:18:01 -04:00
/**
* Returns true in case the config is a group config
*
* @ param array $config
*
* @ return bool
*/
private function isGroup ( array $config )
{
return
is_array ( $config ) &&
is_string ( array_keys ( $config )[ 0 ]) &&
// This entry should NOT be a BaseModule
( substr ( array_keys ( $config )[ 0 ], 0 , strlen ( 'Friendica\Module' )) !== 'Friendica\Module' ) &&
// The second argument is an array (another routes)
is_array ( array_values ( $config )[ 0 ]);
2019-04-30 18:32:33 -04:00
}
2019-09-26 15:18:01 -04:00
/**
* Returns true in case the config is a route config
*
* @ param array $config
*
* @ return bool
*/
private function isRoute ( array $config )
2019-04-30 18:32:33 -04:00
{
2019-09-26 15:18:01 -04:00
return
// The config array should at least have one entry
! empty ( $config [ 0 ]) &&
// This entry should be a BaseModule
( substr ( $config [ 0 ], 0 , strlen ( 'Friendica\Module' )) === 'Friendica\Module' ) &&
// Either there is no other argument
( empty ( $config [ 1 ]) ||
// Or the second argument is an array (HTTP-Methods)
is_array ( $config [ 1 ]));
2019-04-30 18:32:33 -04:00
}
2019-09-26 15:18:01 -04:00
/**
* The current route collector
*
* @ return RouteCollector | null
*/
2019-04-30 18:32:33 -04:00
public function getRouteCollector ()
{
return $this -> routeCollector ;
}
/**
* Returns the relevant module class name for the given page URI or NULL if no route rule matched .
*
* @ param string $cmd The path component of the request URL without the query string
2019-09-26 15:18:01 -04:00
*
2019-10-11 11:55:02 -04:00
* @ return string A Friendica\BaseModule - extending class name if a route rule matched
*
* @ throws HTTPException\InternalServerErrorException
* @ throws HTTPException\MethodNotAllowedException If a rule matched but the method didn ' t
* @ throws HTTPException\NotFoundException If no rule matched
2019-04-30 18:32:33 -04:00
*/
public function getModuleClass ( $cmd )
{
2019-09-26 15:18:01 -04:00
// Add routes from addons
Hook :: callAll ( 'route_collection' , $this -> routeCollector );
2019-04-30 18:32:33 -04:00
$cmd = '/' . ltrim ( $cmd , '/' );
2019-11-06 22:34:38 -05:00
$dispatcher = new Dispatcher\GroupCountBased ( $this -> routeCollector -> getData ());
2019-04-30 18:32:33 -04:00
$moduleClass = null ;
2019-11-05 00:03:05 -05:00
$this -> parameters = [];
2019-04-30 18:32:33 -04:00
2019-09-26 15:18:01 -04:00
$routeInfo = $dispatcher -> dispatch ( $this -> httpMethod , $cmd );
2019-04-30 18:32:33 -04:00
if ( $routeInfo [ 0 ] === Dispatcher :: FOUND ) {
$moduleClass = $routeInfo [ 1 ];
2019-11-05 00:03:05 -05:00
$this -> parameters = $routeInfo [ 2 ];
2019-10-11 11:55:02 -04:00
} elseif ( $routeInfo [ 0 ] === Dispatcher :: METHOD_NOT_ALLOWED ) {
2020-01-18 14:52:34 -05:00
throw new HTTPException\MethodNotAllowedException ( DI :: l10n () -> t ( 'Method not allowed for this module. Allowed method(s): %s' , implode ( ', ' , $routeInfo [ 1 ])));
2019-10-11 11:55:02 -04:00
} else {
2020-01-18 14:52:34 -05:00
throw new HTTPException\NotFoundException ( DI :: l10n () -> t ( 'Page not found.' ));
2019-04-30 18:32:33 -04:00
}
return $moduleClass ;
}
2019-11-05 00:03:05 -05:00
/**
* Returns the module parameters .
*
* @ return array parameters
*/
public function getModuleParameters ()
{
return $this -> parameters ;
}
2019-04-30 18:32:33 -04:00
}