Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
18
core/lib/Drupal/Core/Routing/Access/AccessInterface.php
Normal file
18
core/lib/Drupal/Core/Routing/Access/AccessInterface.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\Access\AccessInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing\Access;
|
||||
|
||||
/**
|
||||
* An access check service determines access rules for particular routes.
|
||||
*/
|
||||
interface AccessInterface {
|
||||
|
||||
// @todo Remove this interface since it no longer defines any methods?
|
||||
// @see https://www.drupal.org/node/2266817.
|
||||
|
||||
}
|
142
core/lib/Drupal/Core/Routing/AccessAwareRouter.php
Normal file
142
core/lib/Drupal/Core/Routing/AccessAwareRouter.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\AccessAwareRouter.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Cmf\Component\Routing\ChainRouter;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\RequestContext as SymfonyRequestContext;
|
||||
|
||||
/**
|
||||
* A router class for Drupal with access check and upcasting.
|
||||
*/
|
||||
class AccessAwareRouter implements AccessAwareRouterInterface {
|
||||
|
||||
/**
|
||||
* The chain router doing the actual routing.
|
||||
*
|
||||
* @var \Symfony\Cmf\Component\Routing\ChainRouter
|
||||
*/
|
||||
protected $chainRouter;
|
||||
|
||||
/**
|
||||
* The access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The account to use in access checks.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface;
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* Constructs a router for Drupal with access check and upcasting.
|
||||
*
|
||||
* @param \Symfony\Cmf\Component\Routing\ChainRouter $chain_router
|
||||
* The chain router doing the actual routing.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account to use in access checks.
|
||||
*/
|
||||
public function __construct(ChainRouter $chain_router, AccessManagerInterface $access_manager, AccountInterface $account) {
|
||||
$this->chainRouter = $chain_router;
|
||||
$this->accessManager = $access_manager;
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __call($name, $arguments) {
|
||||
// Ensure to call every other function to the chained router.
|
||||
// @todo Sadly does the ChainRouter not implement an interface in CMF.
|
||||
return call_user_func_array([$this->chainRouter, $name], $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setContext(SymfonyRequestContext $context) {
|
||||
$this->chainRouter->setContext($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
return $this->chainRouter->getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* Thrown when access checking failed.
|
||||
*/
|
||||
public function matchRequest(Request $request) {
|
||||
$parameters = $this->chainRouter->matchRequest($request);
|
||||
$request->attributes->add($parameters);
|
||||
$this->checkAccess($request);
|
||||
// We can not return $parameters because the access check can change the
|
||||
// request attributes.
|
||||
return $request->attributes->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply access check service to the route and parameters in the request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request to access check.
|
||||
*/
|
||||
protected function checkAccess(Request $request) {
|
||||
// The cacheability (if any) of this request's access check result must be
|
||||
// applied to the response.
|
||||
$access_result = $this->accessManager->checkRequest($request, $this->account, TRUE);
|
||||
// Allow a master request to set the access result for a subrequest: if an
|
||||
// access result attribute is already set, don't overwrite it.
|
||||
if (!$request->attributes->has(AccessAwareRouterInterface::ACCESS_RESULT)) {
|
||||
$request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, $access_result);
|
||||
}
|
||||
if (!$access_result->isAllowed()) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteCollection() {
|
||||
return $this->chainRouter->getRouteCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) {
|
||||
return $this->chainRouter->generate($name, $parameters, $referenceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* Thrown when access checking failed.
|
||||
*/
|
||||
public function match($pathinfo) {
|
||||
return $this->matchRequest(Request::create($pathinfo));
|
||||
}
|
||||
|
||||
}
|
||||
|
40
core/lib/Drupal/Core/Routing/AccessAwareRouterInterface.php
Normal file
40
core/lib/Drupal/Core/Routing/AccessAwareRouterInterface.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\AccessAwareRouterInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* Interface for a router class for Drupal with access check and upcasting.
|
||||
*/
|
||||
interface AccessAwareRouterInterface extends RouterInterface, RequestMatcherInterface {
|
||||
|
||||
/**
|
||||
* Attribute name of the access result for the request..
|
||||
*/
|
||||
const ACCESS_RESULT = '_access_result';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* Thrown when access checking failed.
|
||||
*/
|
||||
public function matchRequest(Request $request);
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* Thrown when $access_check is enabled and access checking failed.
|
||||
*/
|
||||
public function match($pathinfo);
|
||||
|
||||
}
|
54
core/lib/Drupal/Core/Routing/AdminContext.php
Normal file
54
core/lib/Drupal/Core/Routing/AdminContext.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\AdminContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides a helper class to determine whether the route is an admin one.
|
||||
*/
|
||||
class AdminContext {
|
||||
|
||||
/**
|
||||
* The route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Construct a new admin context helper instance.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
*/
|
||||
public function __construct(RouteMatchInterface $route_match) {
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the active route is an admin one.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* (optional) The route to determine whether it is an admin one. Per default
|
||||
* this falls back to the route object on the active request.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the route is an admin one, otherwise FALSE.
|
||||
*/
|
||||
public function isAdminRoute(Route $route = NULL) {
|
||||
if (!$route) {
|
||||
$route = $this->routeMatch->getRouteObject();
|
||||
if (!$route) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return (bool) $route->getOption('_admin_route');
|
||||
}
|
||||
|
||||
}
|
172
core/lib/Drupal/Core/Routing/CompiledRoute.php
Normal file
172
core/lib/Drupal/Core/Routing/CompiledRoute.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\CompiledRoute.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\Routing\CompiledRoute as SymfonyCompiledRoute;
|
||||
|
||||
/**
|
||||
* A compiled route contains derived information from a route object.
|
||||
*/
|
||||
class CompiledRoute extends SymfonyCompiledRoute {
|
||||
|
||||
/**
|
||||
* The fitness of this route.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $fit;
|
||||
|
||||
/**
|
||||
* The pattern outline of this route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $patternOutline;
|
||||
|
||||
/**
|
||||
* The number of parts in the path of this route.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $numParts;
|
||||
|
||||
/**
|
||||
* Constructs a new compiled route object.
|
||||
*
|
||||
* This is a ridiculously long set of constructor parameters, but as this
|
||||
* object is little more than a collection of values it's not a serious
|
||||
* problem. The parent Symfony class does the same, as well, making it
|
||||
* difficult to override differently.
|
||||
*
|
||||
* @param int $fit
|
||||
* The fitness of the route.
|
||||
* @param string $pattern_outline
|
||||
* The pattern outline for this route.
|
||||
* @param int $num_parts
|
||||
* The number of parts in the path.
|
||||
* @param string $staticPrefix
|
||||
* The static prefix of the compiled route
|
||||
* @param string $regex
|
||||
* The regular expression to use to match this route
|
||||
* @param array $tokens
|
||||
* An array of tokens to use to generate URL for this route
|
||||
* @param array $pathVariables
|
||||
* An array of path variables
|
||||
* @param string|null $hostRegex
|
||||
* Host regex
|
||||
* @param array $hostTokens
|
||||
* Host tokens
|
||||
* @param array $hostVariables
|
||||
* An array of host variables
|
||||
* @param array $variables
|
||||
* An array of variables (variables defined in the path and in the host patterns)
|
||||
*/
|
||||
public function __construct($fit, $pattern_outline, $num_parts, $staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) {
|
||||
parent::__construct($staticPrefix, $regex, $tokens, $pathVariables, $hostRegex, $hostTokens, $hostVariables, $variables);
|
||||
|
||||
$this->fit = $fit;
|
||||
$this->patternOutline = $pattern_outline;
|
||||
$this->numParts = $num_parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fit of this route.
|
||||
*
|
||||
* See RouteCompiler for a definition of how the fit is calculated.
|
||||
*
|
||||
* @return int
|
||||
* The fit of the route.
|
||||
*/
|
||||
public function getFit() {
|
||||
return $this->fit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of parts in this route's path.
|
||||
*
|
||||
* The string "foo/bar/baz" has 3 parts, regardless of how many of them are
|
||||
* placeholders.
|
||||
*
|
||||
* @return int
|
||||
* The number of parts in the path.
|
||||
*/
|
||||
public function getNumParts() {
|
||||
return $this->numParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pattern outline of this route.
|
||||
*
|
||||
* The pattern outline of a route is the path pattern of the route, but
|
||||
* normalized such that all placeholders are replaced with %.
|
||||
*
|
||||
* @return string
|
||||
* The normalized path pattern.
|
||||
*/
|
||||
public function getPatternOutline() {
|
||||
return $this->patternOutline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the options.
|
||||
*
|
||||
* @return array
|
||||
* The options.
|
||||
*/
|
||||
public function getOptions() {
|
||||
return $this->route->getOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the defaults.
|
||||
*
|
||||
* @return array
|
||||
* The defaults.
|
||||
*/
|
||||
public function getDefaults() {
|
||||
return $this->route->getDefaults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requirements.
|
||||
*
|
||||
* @return array
|
||||
* The requirements.
|
||||
*/
|
||||
public function getRequirements() {
|
||||
return $this->route->getRequirements();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function serialize() {
|
||||
// Calling the parent method is safer than trying to optimize out the extra
|
||||
// function calls.
|
||||
$data = unserialize(parent::serialize());
|
||||
$data['fit'] = $this->fit;
|
||||
$data['patternOutline'] = $this->patternOutline;
|
||||
$data['numParts'] = $this->numParts;
|
||||
|
||||
return serialize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unserialize($serialized) {
|
||||
parent::unserialize($serialized);
|
||||
$data = unserialize($serialized);
|
||||
|
||||
$this->fit = $data['fit'];
|
||||
$this->patternOutline = $data['patternOutline'];
|
||||
$this->numParts = $data['numParts'];
|
||||
}
|
||||
|
||||
|
||||
}
|
60
core/lib/Drupal/Core/Routing/ContentTypeHeaderMatcher.php
Normal file
60
core/lib/Drupal/Core/Routing/ContentTypeHeaderMatcher.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\ContentTypeHeaderMatcher.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Filters routes based on the HTTP Content-type header.
|
||||
*/
|
||||
class ContentTypeHeaderMatcher implements RouteFilterInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function filter(RouteCollection $collection, Request $request) {
|
||||
// The Content-type header does not make sense on GET requests, because GET
|
||||
// requests do not carry any content. Nothing to filter in this case.
|
||||
if ($request->isMethod('GET')) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
$format = $request->getContentType();
|
||||
|
||||
foreach ($collection as $name => $route) {
|
||||
$supported_formats = array_filter(explode('|', $route->getRequirement('_content_type_format')));
|
||||
if (empty($supported_formats)) {
|
||||
// No restriction on the route, so we move the route to the end of the
|
||||
// collection by re-adding it. That way generic routes sink down in the
|
||||
// list and exact matching routes stay on top.
|
||||
$collection->add($name, $route);
|
||||
}
|
||||
elseif (!in_array($format, $supported_formats)) {
|
||||
$collection->remove($name);
|
||||
}
|
||||
}
|
||||
if (count($collection)) {
|
||||
return $collection;
|
||||
}
|
||||
// We do not throw a
|
||||
// \Symfony\Component\Routing\Exception\ResourceNotFoundException here
|
||||
// because we don't want to return a 404 status code, but rather a 415.
|
||||
throw new UnsupportedMediaTypeHttpException('No route found that matches the Content-Type header.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Route $route) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
142
core/lib/Drupal/Core/Routing/CurrentRouteMatch.php
Normal file
142
core/lib/Drupal/Core/Routing/CurrentRouteMatch.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\CurrentRouteMatch.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Default object for current_route_match service.
|
||||
*/
|
||||
class CurrentRouteMatch implements RouteMatchInterface, StackedRouteMatchInterface {
|
||||
|
||||
/**
|
||||
* The related request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* Internal cache of RouteMatch objects.
|
||||
*
|
||||
* @var \SplObjectStorage
|
||||
*/
|
||||
protected $routeMatches;
|
||||
|
||||
/**
|
||||
* Constructs a CurrentRouteMatch object.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(RequestStack $request_stack) {
|
||||
$this->requestStack = $request_stack;
|
||||
$this->routeMatches = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteName() {
|
||||
return $this->getCurrentRouteMatch()->getRouteName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteObject() {
|
||||
return $this->getCurrentRouteMatch()->getRouteObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParameter($parameter_name) {
|
||||
return $this->getCurrentRouteMatch()->getParameter($parameter_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParameters() {
|
||||
return $this->getCurrentRouteMatch()->getParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRawParameter($parameter_name) {
|
||||
return $this->getCurrentRouteMatch()->getRawParameter($parameter_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRawParameters() {
|
||||
return $this->getCurrentRouteMatch()->getRawParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the route match for the current request.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RouteMatchInterface
|
||||
* The current route match object.
|
||||
*/
|
||||
public function getCurrentRouteMatch() {
|
||||
return $this->getRouteMatch($this->requestStack->getCurrentRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the route match for a passed in request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A request object.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RouteMatchInterface
|
||||
* A route match object created from the request.
|
||||
*/
|
||||
protected function getRouteMatch(Request $request) {
|
||||
if (isset($this->routeMatches[$request])) {
|
||||
$route_match = $this->routeMatches[$request];
|
||||
}
|
||||
else {
|
||||
$route_match = RouteMatch::createFromRequest($request);
|
||||
|
||||
// Since getRouteMatch() might be invoked both before and after routing
|
||||
// is completed, only statically cache the route match after there's a
|
||||
// matched route.
|
||||
if ($route_match->getRouteObject()) {
|
||||
$this->routeMatches[$request] = $route_match;
|
||||
}
|
||||
}
|
||||
return $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMasterRouteMatch() {
|
||||
return $this->getRouteMatch($this->requestStack->getMasterRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParentRouteMatch() {
|
||||
return $this->getRouteMatch($this->requestStack->getParentRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteMatchFromRequest(Request $request) {
|
||||
return $this->getRouteMatch($request);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\Enhancer\ParamConversionEnhancer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing\Enhancer;
|
||||
|
||||
use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
|
||||
use Drupal\Core\ParamConverter\ParamNotConvertedException;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides a route enhancer that handles parameter conversion.
|
||||
*/
|
||||
class ParamConversionEnhancer implements RouteEnhancerInterface, EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The parameter conversion manager.
|
||||
*
|
||||
* @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface
|
||||
*/
|
||||
protected $paramConverterManager;
|
||||
|
||||
/**
|
||||
* Constructs a new ParamConversionEnhancer.
|
||||
*
|
||||
* @param \Drupal\Core\ParamConverter\ParamConverterManagerInterface $param_converter_manager
|
||||
* The parameter conversion manager.
|
||||
*/
|
||||
public function __construct(ParamConverterManagerInterface $param_converter_manager) {
|
||||
$this->paramConverterManager = $param_converter_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enhance(array $defaults, Request $request) {
|
||||
$defaults['_raw_variables'] = $this->copyRawVariables($defaults);
|
||||
return $this->paramConverterManager->convert($defaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a backup of the raw values that corresponding to the route pattern.
|
||||
*
|
||||
* @param array $defaults
|
||||
* The route defaults array.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\ParameterBag
|
||||
*/
|
||||
protected function copyRawVariables(array $defaults) {
|
||||
/** @var $route \Symfony\Component\Routing\Route */
|
||||
$route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
|
||||
$variables = array_flip($route->compile()->getVariables());
|
||||
// Foreach will copy the values from the array it iterates. Even if they
|
||||
// are references, use it to break them. This avoids any scenarios where raw
|
||||
// variables also get replaced with converted values.
|
||||
$raw_variables = array();
|
||||
foreach (array_intersect_key($defaults, $variables) as $key => $value) {
|
||||
$raw_variables[$key] = $value;
|
||||
}
|
||||
return new ParameterBag($raw_variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Catches failed parameter conversions and throw a 404 instead.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
|
||||
*/
|
||||
public function onException(GetResponseForExceptionEvent $event) {
|
||||
$exception = $event->getException();
|
||||
if ($exception instanceof ParamNotConvertedException) {
|
||||
$event->setException(new NotFoundHttpException($exception->getMessage(), $exception));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[KernelEvents::EXCEPTION][] = array('onException', 75);
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Route $route) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\Enhancer\RouteEnhancerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing\Enhancer;
|
||||
|
||||
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface as BaseRouteEnhancerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* A route enhance service to determine route enhance rules.
|
||||
*/
|
||||
interface RouteEnhancerInterface extends BaseRouteEnhancerInterface {
|
||||
|
||||
/**
|
||||
* Declares if the route enhancer applies to the given route.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to consider attaching to.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the check applies to the passed route, False otherwise.
|
||||
*/
|
||||
public function applies(Route $route);
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\GeneratorNotInitializedException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
/**
|
||||
* Class for exceptions thrown when the generator has not been initialized.
|
||||
*/
|
||||
class GeneratorNotInitializedException extends \Exception { }
|
106
core/lib/Drupal/Core/Routing/LazyRouteEnhancer.php
Normal file
106
core/lib/Drupal/Core/Routing/LazyRouteEnhancer.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\LazyRouteEnhancer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface;
|
||||
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface as BaseRouteEnhancerInterface;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* A route enhancer which lazily loads route enhancers, depending on the route.
|
||||
*
|
||||
* We lazy initialize route enhancers, because otherwise all dependencies of
|
||||
* all route enhancers are initialized on every request, which is slow. However,
|
||||
* with the use of lazy loading, dependencies are instantiated only when used.
|
||||
*/
|
||||
class LazyRouteEnhancer implements BaseRouteEnhancerInterface, ContainerAwareInterface {
|
||||
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
* Array of enhancers service IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $serviceIds = [];
|
||||
|
||||
/**
|
||||
* The initialized route enhancers.
|
||||
*
|
||||
* @var \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]|\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface[]
|
||||
*/
|
||||
protected $enhancers = NULL;
|
||||
|
||||
/**
|
||||
* Constructs the LazyRouteEnhancer object.
|
||||
*
|
||||
* @param $service_ids
|
||||
* Array of enhancers service IDs.
|
||||
*/
|
||||
public function __construct($service_ids) {
|
||||
$this->serviceIds = $service_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each route, saves a list of applicable enhancers to the route.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\RouteCollection $route_collection
|
||||
* A collection of routes to apply enhancer checks to.
|
||||
*/
|
||||
public function setEnhancers(RouteCollection $route_collection) {
|
||||
/** @var \Symfony\Component\Routing\Route $route **/
|
||||
foreach ($route_collection as $route_name => $route) {
|
||||
$service_ids = [];
|
||||
foreach ($this->getEnhancers() as $service_id => $enhancer) {
|
||||
if ((!$enhancer instanceof RouteEnhancerInterface) || $enhancer->applies($route)) {
|
||||
$service_ids[] = $service_id;
|
||||
}
|
||||
}
|
||||
if ($service_ids) {
|
||||
$route->setOption('_route_enhancers', array_unique($service_ids));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For each route, gets a list of applicable enhancer to the route.
|
||||
*
|
||||
* @return \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]|\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface[]
|
||||
*/
|
||||
protected function getEnhancers() {
|
||||
if (!isset($this->enhancers)) {
|
||||
foreach ($this->serviceIds as $service_id) {
|
||||
$this->enhancers[$service_id] = $this->container->get($service_id);
|
||||
}
|
||||
}
|
||||
return $this->enhancers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enhance(array $defaults, Request $request) {
|
||||
/** @var \Symfony\Component\Routing\Route $route */
|
||||
$route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
|
||||
|
||||
$enhancer_ids = $route->getOption('_route_enhancers');
|
||||
|
||||
if (isset($enhancer_ids)) {
|
||||
foreach ($enhancer_ids as $enhancer_id) {
|
||||
$defaults = $this->container->get($enhancer_id)->enhance($defaults, $request);
|
||||
}
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
}
|
107
core/lib/Drupal/Core/Routing/LazyRouteFilter.php
Normal file
107
core/lib/Drupal/Core/Routing/LazyRouteFilter.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\LazyRouteFilter.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface as BaseRouteFilterInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* A route filter which lazily loads route filters, depending on the route.
|
||||
*
|
||||
* We lazy initialize route filters, because otherwise all dependencies of all
|
||||
* route filters are initialized on every request, which is slow. However, with
|
||||
* the use of lazy loading, dependencies are instantiated only when used.
|
||||
*/
|
||||
class LazyRouteFilter implements BaseRouteFilterInterface, ContainerAwareInterface {
|
||||
|
||||
use ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
* Array of route filter service IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $serviceIds = [];
|
||||
|
||||
/**
|
||||
* The initialized route filters.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteFilterInterface[]
|
||||
*/
|
||||
protected $filters = NULL;
|
||||
|
||||
/**
|
||||
* Constructs the LazyRouteEnhancer object.
|
||||
*
|
||||
* @param $service_ids
|
||||
* Array of route filter service IDs.
|
||||
*/
|
||||
public function __construct($service_ids) {
|
||||
$this->serviceIds = $service_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each route, filter down the route collection.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\RouteCollection $route_collection
|
||||
* A collection of routes to apply filter checks to.
|
||||
*/
|
||||
public function setFilters(RouteCollection $route_collection) {
|
||||
/** @var \Symfony\Component\Routing\Route $route **/
|
||||
foreach ($route_collection as $route) {
|
||||
$service_ids = [];
|
||||
foreach ($this->getFilters() as $service_id => $filter) {
|
||||
if ($filter instanceof RouteFilterInterface && $filter->applies($route)) {
|
||||
$service_ids[] = $service_id;
|
||||
}
|
||||
}
|
||||
if ($service_ids) {
|
||||
$route->setOption('_route_filters', array_unique($service_ids));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For each route, gets a list of applicable enhancers to the route.
|
||||
*
|
||||
* @return \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]|\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface[]
|
||||
*/
|
||||
protected function getFilters() {
|
||||
if (!isset($this->filters)) {
|
||||
foreach ($this->serviceIds as $service_id) {
|
||||
$this->filters[$service_id] = $this->container->get($service_id);
|
||||
}
|
||||
}
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function filter(RouteCollection $collection, Request $request) {
|
||||
$filter_ids = [];
|
||||
foreach ($collection->all() as $route) {
|
||||
$filter_ids = array_merge($filter_ids, $route->getOption('_route_filters') ?: []);
|
||||
}
|
||||
$filter_ids = array_unique($filter_ids);
|
||||
|
||||
if (isset($filter_ids)) {
|
||||
foreach ($filter_ids as $filter_id) {
|
||||
if ($filter = $this->container->get($filter_id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
|
||||
$collection = $filter->filter($collection, $request);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $collection;
|
||||
}
|
||||
|
||||
}
|
71
core/lib/Drupal/Core/Routing/LinkGeneratorTrait.php
Normal file
71
core/lib/Drupal/Core/Routing/LinkGeneratorTrait.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\LinkGeneratorTrait.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Utility\LinkGeneratorInterface;
|
||||
use Drupal\Core\Routing\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* Wrapper methods for the Link Generator.
|
||||
*
|
||||
* This utility trait should only be used in application-level code, such as
|
||||
* classes that would implement ContainerInjectionInterface. Services registered
|
||||
* in the Container should not use this trait but inject the appropriate service
|
||||
* directly for easier testing.
|
||||
*/
|
||||
trait LinkGeneratorTrait {
|
||||
|
||||
/**
|
||||
* The link generator.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\LinkGeneratorInterface
|
||||
*/
|
||||
protected $linkGenerator;
|
||||
|
||||
/**
|
||||
* Renders a link to a route given a route name and its parameters.
|
||||
*
|
||||
* @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() for details
|
||||
* on the arguments, usage, and possible exceptions.
|
||||
*
|
||||
* @return string
|
||||
* An HTML string containing a link to the given route and parameters.
|
||||
*/
|
||||
protected function l($text, Url $url) {
|
||||
return $this->getLinkGenerator()->generate($text, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link generator.
|
||||
*
|
||||
* @return \Drupal\Core\Utility\LinkGeneratorInterface
|
||||
* The link generator
|
||||
*/
|
||||
protected function getLinkGenerator() {
|
||||
if (!isset($this->linkGenerator)) {
|
||||
$this->linkGenerator = \Drupal::service('link_generator');
|
||||
}
|
||||
return $this->linkGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the link generator service.
|
||||
*
|
||||
* @param \Drupal\Core\Utility\LinkGeneratorInterface $generator
|
||||
* The link generator service.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLinkGenerator(LinkGeneratorInterface $generator) {
|
||||
$this->linkGenerator = $generator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
165
core/lib/Drupal/Core/Routing/MatcherDumper.php
Normal file
165
core/lib/Drupal/Core/Routing/MatcherDumper.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\MatcherDumper.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* Dumps Route information to a database table.
|
||||
*/
|
||||
class MatcherDumper implements MatcherDumperInterface {
|
||||
|
||||
/**
|
||||
* The database connection to which to dump route information.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The routes to be dumped.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\RouteCollection
|
||||
*/
|
||||
protected $routes;
|
||||
|
||||
/**
|
||||
* The state.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The name of the SQL table to which to dump the routes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName;
|
||||
|
||||
/**
|
||||
* Construct the MatcherDumper.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection which will be used to store the route
|
||||
* information.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state.
|
||||
* @param string $table
|
||||
* (optional) The table to store the route info in. Defaults to 'router'.
|
||||
*/
|
||||
public function __construct(Connection $connection, StateInterface $state, $table = 'router') {
|
||||
$this->connection = $connection;
|
||||
$this->state = $state;
|
||||
|
||||
$this->tableName = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addRoutes(RouteCollection $routes) {
|
||||
if (empty($this->routes)) {
|
||||
$this->routes = $routes;
|
||||
}
|
||||
else {
|
||||
$this->routes->addCollection($routes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a set of routes to the router table in the database.
|
||||
*
|
||||
* Available options:
|
||||
* - provider: The route grouping that is being dumped. All existing
|
||||
* routes with this provider will be deleted on dump.
|
||||
* - base_class: The base class name.
|
||||
*
|
||||
* @param array $options
|
||||
* An array of options.
|
||||
*/
|
||||
public function dump(array $options = array()) {
|
||||
// Convert all of the routes into database records.
|
||||
// Accumulate the menu masks on top of any we found before.
|
||||
$masks = array_flip($this->state->get('routing.menu_masks.' . $this->tableName, array()));
|
||||
// Delete any old records first, then insert the new ones. That avoids
|
||||
// stale data. The transaction makes it atomic to avoid unstable router
|
||||
// states due to random failures.
|
||||
$transaction = $this->connection->startTransaction();
|
||||
try {
|
||||
// We don't use truncate, because it is not guaranteed to be transaction
|
||||
// safe.
|
||||
$this->connection->delete($this->tableName)->execute();
|
||||
|
||||
// Split the routes into chunks to avoid big INSERT queries.
|
||||
$route_chunks = array_chunk($this->routes->all(), 50, TRUE);
|
||||
foreach ($route_chunks as $routes) {
|
||||
$insert = $this->connection->insert($this->tableName)->fields(array(
|
||||
'name',
|
||||
'fit',
|
||||
'path',
|
||||
'pattern_outline',
|
||||
'number_parts',
|
||||
'route',
|
||||
));
|
||||
$names = array();
|
||||
foreach ($routes as $name => $route) {
|
||||
/** @var \Symfony\Component\Routing\Route $route */
|
||||
$route->setOption('compiler_class', '\Drupal\Core\Routing\RouteCompiler');
|
||||
/** @var \Drupal\Core\Routing\CompiledRoute $compiled */
|
||||
$compiled = $route->compile();
|
||||
// The fit value is a binary number which has 1 at every fixed path
|
||||
// position and 0 where there is a wildcard. We keep track of all such
|
||||
// patterns that exist so that we can minimize the number of path
|
||||
// patterns we need to check in the RouteProvider.
|
||||
$masks[$compiled->getFit()] = 1;
|
||||
$names[] = $name;
|
||||
$values = array(
|
||||
'name' => $name,
|
||||
'fit' => $compiled->getFit(),
|
||||
'path' => $route->getPath(),
|
||||
'pattern_outline' => $compiled->getPatternOutline(),
|
||||
'number_parts' => $compiled->getNumParts(),
|
||||
'route' => serialize($route),
|
||||
);
|
||||
$insert->values($values);
|
||||
}
|
||||
|
||||
// Insert all new routes.
|
||||
$insert->execute();
|
||||
}
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$transaction->rollback();
|
||||
watchdog_exception('Routing', $e);
|
||||
throw $e;
|
||||
}
|
||||
// Sort the masks so they are in order of descending fit.
|
||||
$masks = array_keys($masks);
|
||||
rsort($masks);
|
||||
$this->state->set('routing.menu_masks.' . $this->tableName, $masks);
|
||||
|
||||
$this->routes = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the routes to match.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\RouteCollection
|
||||
* A RouteCollection instance representing all routes currently in the
|
||||
* dumper.
|
||||
*/
|
||||
public function getRoutes() {
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
}
|
26
core/lib/Drupal/Core/Routing/MatcherDumperInterface.php
Normal file
26
core/lib/Drupal/Core/Routing/MatcherDumperInterface.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\MatcherDumperInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface as SymfonyMatcherDumperInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Extends the symfony matcher dumper interface with a addRoutes method.
|
||||
*/
|
||||
interface MatcherDumperInterface extends SymfonyMatcherDumperInterface {
|
||||
|
||||
/**
|
||||
* Adds additional routes to be dumped.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\RouteCollection $routes
|
||||
* A collection of routes to add to this dumper.
|
||||
*/
|
||||
public function addRoutes(RouteCollection $routes);
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\MatchingRouteNotFoundException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||
|
||||
/**
|
||||
* No matching route was found.
|
||||
*/
|
||||
class MatchingRouteNotFoundException extends ResourceNotFoundException {
|
||||
|
||||
}
|
82
core/lib/Drupal/Core/Routing/NullGenerator.php
Normal file
82
core/lib/Drupal/Core/Routing/NullGenerator.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\NullGenerator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\RequestContext as SymfonyRequestContext;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* No-op implementation of a Url Generator, needed for backward compatibility.
|
||||
*/
|
||||
class NullGenerator extends UrlGenerator {
|
||||
|
||||
/**
|
||||
* Override the parent constructor.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(RequestStack $request_stack) {
|
||||
$this->requestStack = $request_stack;
|
||||
$this->context = new RequestContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* generate(), generateFromRoute(), and getPathFromRoute() all call this
|
||||
* protected method.
|
||||
*/
|
||||
protected function getRoute($name) {
|
||||
if ($name === '<front>') {
|
||||
return new Route('/');
|
||||
}
|
||||
elseif ($name === '<current>') {
|
||||
return new Route($this->requestStack->getCurrentRequest()->getPathInfo());
|
||||
}
|
||||
elseif ($name === '<none>') {
|
||||
return new Route('');
|
||||
}
|
||||
throw new RouteNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function processRoute($name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getInternalPathFromRoute($name, Route $route, $parameters = array(), $query_params = array()) {
|
||||
return $route->getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Routing\UrlGenerator::setContext();
|
||||
*/
|
||||
public function setContext(SymfonyRequestContext $context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Symfony\Component\Routing\RequestContextAwareInterface::getContext();
|
||||
*/
|
||||
public function getContext() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Routing\UrlGenerator::processPath().
|
||||
*/
|
||||
protected function processPath($path, &$options = array(), CacheableMetadata $cacheable_metadata = NULL) {
|
||||
return $path;
|
||||
}
|
||||
}
|
63
core/lib/Drupal/Core/Routing/NullMatcherDumper.php
Normal file
63
core/lib/Drupal/Core/Routing/NullMatcherDumper.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\NullMatcherDumper.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Does not dump Route information.
|
||||
*/
|
||||
class NullMatcherDumper implements MatcherDumperInterface {
|
||||
|
||||
/**
|
||||
* The routes to be dumped.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\RouteCollection
|
||||
*/
|
||||
protected $routes;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addRoutes(RouteCollection $routes) {
|
||||
if (empty($this->routes)) {
|
||||
$this->routes = $routes;
|
||||
}
|
||||
else {
|
||||
$this->routes->addCollection($routes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a set of routes to the router table in the database.
|
||||
*
|
||||
* Available options:
|
||||
* - provider: The route grouping that is being dumped. All existing
|
||||
* routes with this provider will be deleted on dump.
|
||||
* - base_class: The base class name.
|
||||
*
|
||||
* @param array $options
|
||||
* An array of options.
|
||||
*/
|
||||
public function dump(array $options = array()) {
|
||||
// The dumper is reused for multiple providers, so reset the queued routes.
|
||||
$this->routes = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the routes to match.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\RouteCollection
|
||||
* A RouteCollection instance representing all routes currently in the
|
||||
* dumper.
|
||||
*/
|
||||
public function getRoutes() {
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
}
|
59
core/lib/Drupal/Core/Routing/NullRouteMatch.php
Normal file
59
core/lib/Drupal/Core/Routing/NullRouteMatch.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\NullRouteMatch.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
|
||||
/**
|
||||
* Stub implementation of RouteMatchInterface for when there's no matched route.
|
||||
*/
|
||||
class NullRouteMatch implements RouteMatchInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteName() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteObject() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParameter($parameter_name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParameters() {
|
||||
return new ParameterBag();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRawParameter($parameter_name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRawParameters() {
|
||||
return new ParameterBag();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\PreloadableRouteProviderInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
/**
|
||||
* Extends the router provider interface to pre-load routes.
|
||||
*/
|
||||
interface PreloadableRouteProviderInterface extends RouteProviderInterface {
|
||||
|
||||
/**
|
||||
* Pre-load routes by their names using the provided list of names.
|
||||
*
|
||||
* This method exists in order to allow performance optimizations. It allows
|
||||
* pre-loading serialized routes that may latter be retrieved using
|
||||
* ::getRoutesByName()
|
||||
*
|
||||
* @param string[] $names
|
||||
* Array of route names to load.
|
||||
*/
|
||||
public function preLoadRoutes($names);
|
||||
|
||||
}
|
87
core/lib/Drupal/Core/Routing/RedirectDestination.php
Normal file
87
core/lib/Drupal/Core/Routing/RedirectDestination.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RedirectDestination.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Provides helpers for redirect destinations.
|
||||
*/
|
||||
class RedirectDestination implements RedirectDestinationInterface {
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The URL generator.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\UrlGeneratorInterface
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* The destination used by the current request.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $destination;
|
||||
|
||||
/**
|
||||
* Constructs a new RedirectDestination instance.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
|
||||
* The URL generator.
|
||||
*/
|
||||
public function __construct(RequestStack $request_stack, UrlGeneratorInterface $url_generator) {
|
||||
$this->requestStack = $request_stack;
|
||||
$this->urlGenerator = $url_generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAsArray() {
|
||||
return ['destination' => $this->get()];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {
|
||||
if (!isset($this->destination)) {
|
||||
$query = $this->requestStack->getCurrentRequest()->query;
|
||||
if (UrlHelper::isExternal($query->get('destination'))) {
|
||||
$this->destination = '/';
|
||||
}
|
||||
elseif ($query->has('destination')) {
|
||||
$this->destination = $query->get('destination');
|
||||
}
|
||||
else {
|
||||
$this->destination = $this->urlGenerator->generateFromRoute('<current>', [], ['query' => UrlHelper::buildQuery(UrlHelper::filterQueryParameters($query->all()))]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($new_destination) {
|
||||
$this->destination = $new_destination;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RedirectDestinationInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
/**
|
||||
* Provides an interface for redirect destinations.
|
||||
*/
|
||||
interface RedirectDestinationInterface {
|
||||
|
||||
/**
|
||||
* Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
|
||||
*
|
||||
* Used to direct the user back to the referring page after completing a form.
|
||||
* By default the current URL is returned. If a destination exists in the
|
||||
* current request, that destination is returned. As such, a destination can
|
||||
* persist across multiple pages.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the key:
|
||||
* - destination: The value of the current request's 'destination' query
|
||||
* parameter, if present. This can be either a relative or absolute URL.
|
||||
* However, for security, redirection to external URLs is not performed.
|
||||
* If the query parameter isn't present, then the URL of the current
|
||||
* request is returned.
|
||||
*
|
||||
* @see \Drupal\Core\EventSubscriber\RedirectResponseSubscriber::checkRedirectUrl()
|
||||
* @ingroup form_api
|
||||
*/
|
||||
public function getAsArray();
|
||||
|
||||
/**
|
||||
* Gets the destination as URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get();
|
||||
|
||||
/**
|
||||
* Sets the destination as URL.
|
||||
*
|
||||
* This method should be used really rarely, for example views uses it, in
|
||||
* order to override all destination calls in all of its rendering.
|
||||
*
|
||||
* @param string $new_destination
|
||||
* The new destination.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set($new_destination);
|
||||
|
||||
}
|
72
core/lib/Drupal/Core/Routing/RedirectDestinationTrait.php
Normal file
72
core/lib/Drupal/Core/Routing/RedirectDestinationTrait.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RedirectDestinationTrait.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
/**
|
||||
* Wrapper methods for the Redirect Destination.
|
||||
*
|
||||
* This utility trait should only be used in application-level code, such as
|
||||
* classes that would implement ContainerInjectionInterface. Services registered
|
||||
* in the Container should not use this trait but inject the appropriate service
|
||||
* directly for easier testing.
|
||||
*/
|
||||
trait RedirectDestinationTrait {
|
||||
|
||||
/**
|
||||
* The redirect destination service.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RedirectDestinationInterface
|
||||
*/
|
||||
protected $redirectDestination;
|
||||
|
||||
/**
|
||||
* Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
|
||||
*
|
||||
* @see \Drupal\Core\Routing\RedirectDestinationInterface::getAsArray()
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the key:
|
||||
* - destination: The value of the current request's 'destination' query
|
||||
* parameter, if present. This can be either a relative or absolute URL.
|
||||
* However, for security, redirection to external URLs is not performed.
|
||||
* If the query parameter isn't present, then the URL of the current
|
||||
* request is returned.
|
||||
*/
|
||||
protected function getDestinationArray() {
|
||||
return $this->getRedirectDestination()->getAsArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the redirect destination service.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RedirectDestinationInterface
|
||||
* The redirect destination helper.
|
||||
*/
|
||||
protected function getRedirectDestination() {
|
||||
if (!isset($this->redirectDestination)) {
|
||||
$this->redirectDestination = \Drupal::destination();
|
||||
}
|
||||
|
||||
return $this->redirectDestination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the redirect destination service.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
|
||||
* The redirect destination service.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRedirectDestination(RedirectDestinationInterface $redirect_destination) {
|
||||
$this->redirectDestination = $redirect_destination;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
72
core/lib/Drupal/Core/Routing/RequestContext.php
Normal file
72
core/lib/Drupal/Core/Routing/RequestContext.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RequestContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\RequestContext as SymfonyRequestContext;
|
||||
|
||||
/**
|
||||
* Holds information about the current request.
|
||||
*
|
||||
* @todo: Remove once the upstream RequestContext provides fromRequestStack():
|
||||
* https://github.com/symfony/symfony/issues/12057
|
||||
*/
|
||||
class RequestContext extends SymfonyRequestContext {
|
||||
|
||||
/**
|
||||
* The scheme, host and base path, for example "http://example.com/d8".
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $completeBaseUrl;
|
||||
|
||||
/**
|
||||
* Populates the context from the current request from the request stack.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The current request stack.
|
||||
*/
|
||||
public function fromRequestStack(RequestStack $request_stack) {
|
||||
$this->fromRequest($request_stack->getCurrentRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fromRequest(Request $request) {
|
||||
parent::fromRequest($request);
|
||||
|
||||
// @todo Extract the code in DrupalKernel::initializeRequestGlobals.
|
||||
// See https://www.drupal.org/node/2404601
|
||||
if (isset($GLOBALS['base_url'])) {
|
||||
$this->setCompleteBaseUrl($GLOBALS['base_url']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scheme, host and base path.
|
||||
*
|
||||
* For example, in an installation in a subdirectory "d8", it should be
|
||||
* "https://example.com/d8".
|
||||
*/
|
||||
public function getCompleteBaseUrl() {
|
||||
return $this->completeBaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the complete base URL for the Request context.
|
||||
*
|
||||
* @param string $complete_base_url
|
||||
* The complete base URL.
|
||||
*/
|
||||
public function setCompleteBaseUrl($complete_base_url) {
|
||||
$this->completeBaseUrl = $complete_base_url;
|
||||
}
|
||||
|
||||
}
|
57
core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php
Normal file
57
core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RequestFormatRouteFilter.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides a route filter, which filters by the request format.
|
||||
*/
|
||||
class RequestFormatRouteFilter implements RouteFilterInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Route $route) {
|
||||
return $route->hasRequirement('_format');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function filter(RouteCollection $collection, Request $request) {
|
||||
$format = $request->getRequestFormat('html');
|
||||
/** @var \Symfony\Component\Routing\Route $route */
|
||||
foreach ($collection as $name => $route) {
|
||||
// If the route has no _format specification, we move it to the end. If it
|
||||
// does, then no match means the route is removed entirely.
|
||||
if ($supported_formats = array_filter(explode('|', $route->getRequirement('_format')))) {
|
||||
if (!in_array($format, $supported_formats)) {
|
||||
$collection->remove($name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$collection->add($name, $route);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($collection)) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
// We do not throw a
|
||||
// \Symfony\Component\Routing\Exception\ResourceNotFoundException here
|
||||
// because we don't want to return a 404 status code, but rather a 406.
|
||||
throw new NotAcceptableHttpException(SafeMarkup::format('No route found for the specified format @format.', ['@format' => $format]));
|
||||
}
|
||||
|
||||
}
|
38
core/lib/Drupal/Core/Routing/RequestHelper.php
Normal file
38
core/lib/Drupal/Core/Routing/RequestHelper.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RequestHelper.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Provides some helper methods for dealing with the request.
|
||||
*/
|
||||
class RequestHelper {
|
||||
|
||||
/**
|
||||
* Returns whether the request is using a clean URL.
|
||||
*
|
||||
* A clean URL is one that does not include the script name. For example,
|
||||
* - http://example.com/node/1 is a clean URL.
|
||||
* - http://example.com/index.php/node/1 is not a clean URL.
|
||||
*
|
||||
* Unclean URLs are required on sites hosted by web servers that cannot be
|
||||
* configured to implicitly route URLs to index.php.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the request is using a clean URL.
|
||||
*/
|
||||
public static function isCleanUrl(Request $request) {
|
||||
$base_url = $request->getBaseUrl();
|
||||
return (empty($base_url) || strpos($base_url, $request->getScriptName()) === FALSE);
|
||||
}
|
||||
|
||||
}
|
42
core/lib/Drupal/Core/Routing/RouteBuildEvent.php
Normal file
42
core/lib/Drupal/Core/Routing/RouteBuildEvent.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteBuildEvent.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Represents route building information as event.
|
||||
*/
|
||||
class RouteBuildEvent extends Event {
|
||||
|
||||
/**
|
||||
* The route collection.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\RouteCollection
|
||||
*/
|
||||
protected $routeCollection;
|
||||
|
||||
/**
|
||||
* Constructs a RouteBuildEvent object.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\RouteCollection $route_collection
|
||||
* The route collection.
|
||||
*/
|
||||
public function __construct(RouteCollection $route_collection) {
|
||||
$this->routeCollection = $route_collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route collection.
|
||||
*/
|
||||
public function getRouteCollection() {
|
||||
return $this->routeCollection;
|
||||
}
|
||||
|
||||
}
|
238
core/lib/Drupal/Core/Routing/RouteBuilder.php
Normal file
238
core/lib/Drupal/Core/Routing/RouteBuilder.php
Normal file
|
@ -0,0 +1,238 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteBuilder.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Component\Discovery\YamlDiscovery;
|
||||
use Drupal\Core\Access\CheckProviderInterface;
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\Core\DestructableInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Managing class for rebuilding the router table.
|
||||
*/
|
||||
class RouteBuilder implements RouteBuilderInterface, DestructableInterface {
|
||||
|
||||
/**
|
||||
* The dumper to which we should send collected routes.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\MatcherDumperInterface
|
||||
*/
|
||||
protected $dumper;
|
||||
|
||||
/**
|
||||
* The used lock backend instance.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
*/
|
||||
protected $lock;
|
||||
|
||||
/**
|
||||
* The event dispatcher to notify of routes.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The controller resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The route collection during the rebuild.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\RouteCollection
|
||||
*/
|
||||
protected $routeCollection;
|
||||
|
||||
/**
|
||||
* Flag that indicates if we are currently rebuilding the routes.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $building = FALSE;
|
||||
|
||||
/**
|
||||
* Flag that indicates if we should rebuild at the end of the request.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $rebuildNeeded = FALSE;
|
||||
|
||||
/**
|
||||
* The check provider.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CheckProviderInterface
|
||||
*/
|
||||
protected $checkProvider;
|
||||
|
||||
/**
|
||||
* Constructs the RouteBuilder using the passed MatcherDumperInterface.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\MatcherDumperInterface $dumper
|
||||
* The matcher dumper used to store the route information.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock backend.
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
|
||||
* The event dispatcher to notify of routes.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* The controller resolver.
|
||||
* @param \Drupal\Core\Access\CheckProviderInterface $check_provider
|
||||
* The check provider.
|
||||
*/
|
||||
public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher, ModuleHandlerInterface $module_handler, ControllerResolverInterface $controller_resolver, CheckProviderInterface $check_provider) {
|
||||
$this->dumper = $dumper;
|
||||
$this->lock = $lock;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->checkProvider = $check_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setRebuildNeeded() {
|
||||
$this->rebuildNeeded = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rebuild() {
|
||||
if ($this->building) {
|
||||
throw new \RuntimeException('Recursive router rebuild detected.');
|
||||
}
|
||||
|
||||
if (!$this->lock->acquire('router_rebuild')) {
|
||||
// Wait for another request that is already doing this work.
|
||||
// We choose to block here since otherwise the routes might not be
|
||||
// available, resulting in a 404.
|
||||
$this->lock->wait('router_rebuild');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->building = TRUE;
|
||||
|
||||
$collection = new RouteCollection();
|
||||
foreach ($this->getRouteDefinitions() as $routes) {
|
||||
// The top-level 'routes_callback' is a list of methods in controller
|
||||
// syntax, see \Drupal\Core\Controller\ControllerResolver. These methods
|
||||
// should return a set of \Symfony\Component\Routing\Route objects, either
|
||||
// in an associative array keyed by the route name, which will be iterated
|
||||
// over and added to the collection for this provider, or as a new
|
||||
// \Symfony\Component\Routing\RouteCollection object, which will be added
|
||||
// to the collection.
|
||||
if (isset($routes['route_callbacks'])) {
|
||||
foreach ($routes['route_callbacks'] as $route_callback) {
|
||||
$callback = $this->controllerResolver->getControllerFromDefinition($route_callback);
|
||||
if ($callback_routes = call_user_func($callback)) {
|
||||
// If a RouteCollection is returned, add the whole collection.
|
||||
if ($callback_routes instanceof RouteCollection) {
|
||||
$collection->addCollection($callback_routes);
|
||||
}
|
||||
// Otherwise, add each Route object individually.
|
||||
else {
|
||||
foreach ($callback_routes as $name => $callback_route) {
|
||||
$collection->add($name, $callback_route);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($routes['route_callbacks']);
|
||||
}
|
||||
foreach ($routes as $name => $route_info) {
|
||||
$route_info += array(
|
||||
'defaults' => array(),
|
||||
'requirements' => array(),
|
||||
'options' => array(),
|
||||
'host' => NULL,
|
||||
'schemes' => array(),
|
||||
'methods' => array(),
|
||||
'condition' => '',
|
||||
);
|
||||
|
||||
$route = new Route($route_info['path'], $route_info['defaults'], $route_info['requirements'], $route_info['options'], $route_info['host'], $route_info['schemes'], $route_info['methods'], $route_info['condition']);
|
||||
$collection->add($name, $route);
|
||||
}
|
||||
}
|
||||
|
||||
// DYNAMIC is supposed to be used to add new routes based upon all the
|
||||
// static defined ones.
|
||||
$this->dispatcher->dispatch(RoutingEvents::DYNAMIC, new RouteBuildEvent($collection));
|
||||
|
||||
// ALTER is the final step to alter all the existing routes. We cannot stop
|
||||
// people from adding new routes here, but we define two separate steps to
|
||||
// make it clear.
|
||||
$this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection));
|
||||
|
||||
$this->checkProvider->setChecks($collection);
|
||||
|
||||
$this->dumper->addRoutes($collection);
|
||||
$this->dumper->dump();
|
||||
|
||||
$this->lock->release('router_rebuild');
|
||||
$this->dispatcher->dispatch(RoutingEvents::FINISHED, new Event());
|
||||
$this->building = FALSE;
|
||||
|
||||
$this->rebuildNeeded = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rebuildIfNeeded() {
|
||||
if ($this->rebuildNeeded) {
|
||||
return $this->rebuild();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destruct() {
|
||||
// Rebuild routes only once at the end of the request lifecycle to not
|
||||
// trigger multiple rebuilds and also make the page more responsive for the
|
||||
// user.
|
||||
$this->rebuildIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all defined routes from .routing.yml files.
|
||||
*
|
||||
* @return array
|
||||
* The defined routes, keyed by provider.
|
||||
*/
|
||||
protected function getRouteDefinitions() {
|
||||
// Always instantiate a new YamlDiscovery object so that we always search on
|
||||
// the up-to-date list of modules.
|
||||
$discovery = new YamlDiscovery('routing', $this->moduleHandler->getModuleDirectories());
|
||||
return $discovery->findAll();
|
||||
}
|
||||
|
||||
}
|
33
core/lib/Drupal/Core/Routing/RouteBuilderInterface.php
Normal file
33
core/lib/Drupal/Core/Routing/RouteBuilderInterface.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteBuilderInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
interface RouteBuilderInterface {
|
||||
|
||||
/**
|
||||
* Rebuilds the route info and dumps to dumper.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the rebuild succeeds, FALSE otherwise.
|
||||
*/
|
||||
public function rebuild();
|
||||
|
||||
/**
|
||||
* Rebuilds the route info and dumps to dumper if necessary.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the rebuild occurs, FALSE otherwise.
|
||||
*/
|
||||
public function rebuildIfNeeded();
|
||||
|
||||
/**
|
||||
* Sets the router to be rebuilt next time rebuildIfNeeded() is called.
|
||||
*/
|
||||
public function setRebuildNeeded();
|
||||
|
||||
}
|
138
core/lib/Drupal/Core/Routing/RouteCompiler.php
Normal file
138
core/lib/Drupal/Core/Routing/RouteCompiler.php
Normal file
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteCompiler.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\Routing\RouteCompilerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCompiler as SymfonyRouteCompiler;
|
||||
|
||||
/**
|
||||
* Compiler to generate derived information from a Route necessary for matching.
|
||||
*/
|
||||
class RouteCompiler extends SymfonyRouteCompiler implements RouteCompilerInterface {
|
||||
|
||||
/**
|
||||
* Utility constant to use for regular expressions against the path.
|
||||
*/
|
||||
const REGEX_DELIMITER = '#';
|
||||
|
||||
/**
|
||||
* Compiles the current route instance.
|
||||
*
|
||||
* Because so much of the parent class is private, we need to call the parent
|
||||
* class's compile() method and then dissect its return value to build our
|
||||
* new compiled object. If upstream gets refactored so we can subclass more
|
||||
* easily then this may not be necessary.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* A Route instance.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\CompiledRoute
|
||||
* A CompiledRoute instance.
|
||||
*/
|
||||
public static function compile(Route $route) {
|
||||
|
||||
$symfony_compiled = parent::compile($route);
|
||||
|
||||
// The Drupal-specific compiled information.
|
||||
$stripped_path = static::getPathWithoutDefaults($route);
|
||||
$fit = static::getFit($stripped_path);
|
||||
$pattern_outline = static::getPatternOutline($stripped_path);
|
||||
$num_parts = count(explode('/', trim($pattern_outline, '/')));
|
||||
|
||||
return new CompiledRoute(
|
||||
$fit,
|
||||
$pattern_outline,
|
||||
$num_parts,
|
||||
// These are the Symfony compiled parts.
|
||||
$symfony_compiled->getStaticPrefix(),
|
||||
$symfony_compiled->getRegex(),
|
||||
$symfony_compiled->getTokens(),
|
||||
$symfony_compiled->getPathVariables(),
|
||||
$symfony_compiled->getHostRegex(),
|
||||
$symfony_compiled->getHostTokens(),
|
||||
$symfony_compiled->getHostVariables(),
|
||||
$symfony_compiled->getVariables()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pattern outline.
|
||||
*
|
||||
* The pattern outline is the path pattern but normalized so that all
|
||||
* placeholders are equal strings and default values are removed.
|
||||
*
|
||||
* @param string $path
|
||||
* The path for which we want the normalized outline.
|
||||
*
|
||||
* @return string
|
||||
* The path pattern outline.
|
||||
*/
|
||||
public static function getPatternOutline($path) {
|
||||
return preg_replace('#\{\w+\}#', '%', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the fitness of the provided path.
|
||||
*
|
||||
* @param string $path
|
||||
* The path whose fitness we want.
|
||||
*
|
||||
* @return int
|
||||
* The fitness of the path, as an integer.
|
||||
*/
|
||||
public static function getFit($path) {
|
||||
$parts = explode('/', trim($path, '/'));
|
||||
$number_parts = count($parts);
|
||||
// We store the highest index of parts here to save some work in the fit
|
||||
// calculation loop.
|
||||
$slashes = $number_parts - 1;
|
||||
// The fit value is a binary number which has 1 at every fixed path
|
||||
// position and 0 where there is a wildcard. We keep track of all such
|
||||
// patterns that exist so that we can minimize the number of path
|
||||
// patterns we need to check in the RouteProvider.
|
||||
$fit = 0;
|
||||
foreach ($parts as $k => $part) {
|
||||
if (strpos($part, '{') === FALSE) {
|
||||
$fit |= 1 << ($slashes - $k);
|
||||
}
|
||||
}
|
||||
|
||||
return $fit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the route, without placeholders with a default value.
|
||||
*
|
||||
* When computing the path outline and fit, we want to skip default-value
|
||||
* placeholders. If we didn't, the path would never match. Note that this
|
||||
* only works for placeholders at the end of the path. Infix placeholders
|
||||
* with default values don't make sense anyway, so that should not be a
|
||||
* problem.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to have the placeholders removed from.
|
||||
*
|
||||
* @return string
|
||||
* The path string, stripped of placeholders that have default values.
|
||||
*/
|
||||
public static function getPathWithoutDefaults(Route $route) {
|
||||
$path = $route->getPath();
|
||||
$defaults = $route->getDefaults();
|
||||
|
||||
// Remove placeholders with default values from the outline, so that they
|
||||
// will still match.
|
||||
$remove = array_map(function($a) {
|
||||
return '/{' . $a . '}';
|
||||
}, array_keys($defaults));
|
||||
$path = str_replace($remove, '', $path);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
29
core/lib/Drupal/Core/Routing/RouteFilterInterface.php
Normal file
29
core/lib/Drupal/Core/Routing/RouteFilterInterface.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteFilterInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface as BaseRouteFilterInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* A route filter service to filter down the collection of route instances.
|
||||
*/
|
||||
interface RouteFilterInterface extends BaseRouteFilterInterface {
|
||||
|
||||
/**
|
||||
* Determines if the route filter applies to the given route.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to consider attaching to.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the check applies to the passed route, FALSE otherwise.
|
||||
*/
|
||||
public function applies(Route $route);
|
||||
|
||||
}
|
165
core/lib/Drupal/Core/Routing/RouteMatch.php
Normal file
165
core/lib/Drupal/Core/Routing/RouteMatch.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteMatch.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Default object representing the results of routing.
|
||||
*/
|
||||
class RouteMatch implements RouteMatchInterface {
|
||||
|
||||
/**
|
||||
* The route name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $routeName;
|
||||
|
||||
/**
|
||||
* The route.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\Route
|
||||
*/
|
||||
protected $route;
|
||||
|
||||
/**
|
||||
* A key|value store of parameters.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\ParameterBag
|
||||
*/
|
||||
protected $parameters;
|
||||
|
||||
/**
|
||||
* A key|value store of raw parameters.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\ParameterBag
|
||||
*/
|
||||
protected $rawParameters;
|
||||
|
||||
/**
|
||||
* Constructs a RouteMatch object.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The name of the route.
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route.
|
||||
* @param array $parameters
|
||||
* The parameters array.
|
||||
* @param array $raw_parameters
|
||||
* The raw $parameters array.
|
||||
*/
|
||||
public function __construct($route_name, Route $route, array $parameters = array(), array $raw_parameters = array()) {
|
||||
$this->routeName = $route_name;
|
||||
$this->route = $route;
|
||||
|
||||
// Pre-filter parameters.
|
||||
$route_params = $this->getParameterNames();
|
||||
$parameters = array_intersect_key($parameters, $route_params);
|
||||
$raw_parameters = array_intersect_key($raw_parameters, $route_params);
|
||||
$this->parameters = new ParameterBag($parameters);
|
||||
$this->rawParameters = new ParameterBag($raw_parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a RouteMatch from a request.
|
||||
*
|
||||
* @param Request $request
|
||||
* A request object.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RouteMatchInterface
|
||||
* A new RouteMatch object if there's a matched route for the request.
|
||||
* A new NullRouteMatch object otherwise (e.g., on a 404 page or when
|
||||
* invoked prior to routing).
|
||||
*/
|
||||
public static function createFromRequest(Request $request) {
|
||||
if ($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
|
||||
$raw_variables = array();
|
||||
if ($raw = $request->attributes->get('_raw_variables')) {
|
||||
$raw_variables = $raw->all();
|
||||
}
|
||||
return new static(
|
||||
$request->attributes->get(RouteObjectInterface::ROUTE_NAME),
|
||||
$request->attributes->get(RouteObjectInterface::ROUTE_OBJECT),
|
||||
$request->attributes->all(),
|
||||
$raw_variables);
|
||||
}
|
||||
else {
|
||||
return new NullRouteMatch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteName() {
|
||||
return $this->routeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteObject() {
|
||||
return $this->route;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParameter($parameter_name) {
|
||||
return $this->parameters->get($parameter_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParameters() {
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRawParameter($parameter_name) {
|
||||
return $this->rawParameters->get($parameter_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRawParameters() {
|
||||
return $this->rawParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of all parameters for the currently matched route.
|
||||
*
|
||||
* @return array
|
||||
* Route parameter names as both the keys and values.
|
||||
*/
|
||||
protected function getParameterNames() {
|
||||
$names = array();
|
||||
if ($route = $this->getRouteObject()) {
|
||||
// Variables defined in path and host patterns are route parameters.
|
||||
$variables = $route->compile()->getVariables();
|
||||
$names = array_combine($variables, $variables);
|
||||
// Route defaults that do not start with a leading "_" are also
|
||||
// parameters, even if they are not included in path or host patterns.
|
||||
foreach ($route->getDefaults() as $name => $value) {
|
||||
if (!isset($names[$name]) && substr($name, 0, 1) !== '_') {
|
||||
$names[$name] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $names;
|
||||
}
|
||||
|
||||
}
|
102
core/lib/Drupal/Core/Routing/RouteMatchInterface.php
Normal file
102
core/lib/Drupal/Core/Routing/RouteMatchInterface.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteMatchInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
/**
|
||||
* Provides an interface for classes representing the result of routing.
|
||||
*
|
||||
* Routing is the process of selecting the best matching candidate from a
|
||||
* collection of routes for an incoming request. The relevant properties of a
|
||||
* request include the path as well as a list of raw parameter values derived
|
||||
* from the URL. If an appropriate route is found, raw parameter values will be
|
||||
* upcast automatically if possible.
|
||||
*
|
||||
* The route match object contains useful information about the selected route
|
||||
* as well as the raw and upcast parameters derived from the incoming
|
||||
* request.
|
||||
*/
|
||||
interface RouteMatchInterface {
|
||||
|
||||
/**
|
||||
* Returns the route name.
|
||||
*
|
||||
* @return string|null
|
||||
* The route name. NULL if no route is matched.
|
||||
*/
|
||||
public function getRouteName();
|
||||
|
||||
/**
|
||||
* Returns the route object.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The route object. NULL if no route is matched.
|
||||
*/
|
||||
public function getRouteObject();
|
||||
|
||||
/**
|
||||
* Returns the processed value of a named route parameter.
|
||||
*
|
||||
* Raw URL parameters are processed by the parameter conversion system, which
|
||||
* does operations such as converting entity ID parameters to fully-loaded
|
||||
* entities. For example, the path node/12345 would have a raw node ID
|
||||
* parameter value of 12345, while the processed parameter value would be the
|
||||
* corresponding loaded node object.
|
||||
*
|
||||
* @param string $parameter_name
|
||||
* The parameter name.
|
||||
*
|
||||
* @return mixed|null
|
||||
* The parameter value. NULL if the route doesn't define the parameter or
|
||||
* if the parameter value can't be determined from the request.
|
||||
*
|
||||
* @see \Drupal\Core\Routing\RouteMatchInterface::getRawParameter()
|
||||
*/
|
||||
public function getParameter($parameter_name);
|
||||
|
||||
/**
|
||||
* Returns the bag of all processed route parameters.
|
||||
*
|
||||
* Raw URL parameters are processed by the parameter conversion system, which
|
||||
* does operations such as converting entity ID parameters to fully-loaded
|
||||
* entities. For example, the path node/12345 would have a raw node ID
|
||||
* parameter value of 12345, while the processed parameter value would be the
|
||||
* corresponding loaded node object.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\ParameterBag
|
||||
* The parameter bag.
|
||||
*
|
||||
* @see \Drupal\Core\Routing\RouteMatchInterface::getRawParameters()
|
||||
*/
|
||||
public function getParameters();
|
||||
|
||||
/**
|
||||
* Returns the raw value of a named route parameter.
|
||||
*
|
||||
* @param string $parameter_name
|
||||
* The parameter name.
|
||||
*
|
||||
* @return string|null
|
||||
* The raw (non-upcast) parameter value. NULL if the route doesn't define
|
||||
* the parameter or if the raw parameter value can't be determined from the
|
||||
* request.
|
||||
*
|
||||
* @see \Drupal\Core\Routing\RouteMatchInterface::getParameter()
|
||||
*/
|
||||
public function getRawParameter($parameter_name);
|
||||
|
||||
/**
|
||||
* Returns the bag of all raw route parameters.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\ParameterBag
|
||||
* The parameter bag.
|
||||
*
|
||||
* @see \Drupal\Core\Routing\RouteMatchInterface::getParameters()
|
||||
*/
|
||||
public function getRawParameters();
|
||||
|
||||
}
|
115
core/lib/Drupal/Core/Routing/RoutePreloader.php
Normal file
115
core/lib/Drupal/Core/Routing/RoutePreloader.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RoutePreloader.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Defines a class which preloads non-admin routes.
|
||||
*
|
||||
* On an actual site we want to avoid too many database queries so we build a
|
||||
* list of all routes which most likely appear on the actual site, which are all
|
||||
* HTML routes not starting with "/admin".
|
||||
*/
|
||||
class RoutePreloader implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The route provider.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface|\Drupal\Core\Routing\PreloadableRouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The state key value store.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Contains the non-admin routes while rebuilding the routes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $nonAdminRoutesOnRebuild = array();
|
||||
|
||||
/**
|
||||
* Constructs a new RoutePreloader.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state key value store.
|
||||
*/
|
||||
public function __construct(RouteProviderInterface $route_provider, StateInterface $state) {
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all non-admin routes right before the actual page is rendered.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\KernelEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onRequest(KernelEvent $event) {
|
||||
// Only preload on normal HTML pages, as they will display menu links.
|
||||
if ($this->routeProvider instanceof PreloadableRouteProviderInterface && $event->getRequest()->getRequestFormat() == 'html') {
|
||||
if ($routes = $this->state->get('routing.non_admin_routes', [])) {
|
||||
// Preload all the non-admin routes at once.
|
||||
$this->routeProvider->preLoadRoutes($routes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters existing routes for a specific collection.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteBuildEvent $event
|
||||
* The route build event.
|
||||
*/
|
||||
public function onAlterRoutes(RouteBuildEvent $event) {
|
||||
$collection = $event->getRouteCollection();
|
||||
foreach ($collection->all() as $name => $route) {
|
||||
if (strpos($route->getPath(), '/admin/') !== 0 && $route->getPath() != '/admin') {
|
||||
$this->nonAdminRoutesOnRebuild[] = $name;
|
||||
}
|
||||
}
|
||||
$this->nonAdminRoutesOnRebuild = array_unique($this->nonAdminRoutesOnRebuild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the non admin routes in state when the route building is finished.
|
||||
*
|
||||
* @param \Symfony\Component\EventDispatcher\Event $event
|
||||
* The route finish event.
|
||||
*/
|
||||
public function onFinishedRoutes(Event $event) {
|
||||
$this->state->set('routing.non_admin_routes', $this->nonAdminRoutesOnRebuild);
|
||||
$this->nonAdminRoutesOnRebuild = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
// Set a really low priority to catch as many as possible routes.
|
||||
$events[RoutingEvents::ALTER] = array('onAlterRoutes', -1024);
|
||||
$events[RoutingEvents::FINISHED] = array('onFinishedRoutes');
|
||||
// Load the routes before the controller is executed (which happens after
|
||||
// the kernel request event).
|
||||
$events[KernelEvents::REQUEST][] = array('onRequest');
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
380
core/lib/Drupal/Core/Routing/RouteProvider.php
Normal file
380
core/lib/Drupal/Core/Routing/RouteProvider.php
Normal file
|
@ -0,0 +1,380 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteProvider.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Symfony\Cmf\Component\Routing\PagedRouteCollection;
|
||||
use Symfony\Cmf\Component\Routing\PagedRouteProviderInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||
|
||||
use \Drupal\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* A Route Provider front-end for all Drupal-stored routes.
|
||||
*/
|
||||
class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProviderInterface, EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The database connection from which to read route information.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The name of the SQL table from which to read the routes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName;
|
||||
|
||||
/**
|
||||
* The state.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* A cache of already-loaded routes, keyed by route name.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\Route[]
|
||||
*/
|
||||
protected $routes = array();
|
||||
|
||||
/**
|
||||
* A cache of already-loaded serialized routes, keyed by route name.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $serializedRoutes = [];
|
||||
|
||||
/**
|
||||
* The current path.
|
||||
*
|
||||
* @var \Drupal\Core\Path\CurrentPathStack
|
||||
*/
|
||||
protected $currentPath;
|
||||
|
||||
/**
|
||||
* The cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* A path processor manager for resolving the system path.
|
||||
*
|
||||
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
|
||||
*/
|
||||
protected $pathProcessor;
|
||||
|
||||
/**
|
||||
* Constructs a new PathMatcher.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* A database connection object.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state.
|
||||
* @param \Drupal\Core\Path\CurrentPathStack $current_path
|
||||
* The current path.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor
|
||||
* The path processor.
|
||||
* @param string $table
|
||||
* The table in the database to use for matching.
|
||||
*/
|
||||
public function __construct(Connection $connection, StateInterface $state, CurrentPathStack $current_path, CacheBackendInterface $cache_backend, InboundPathProcessorInterface $path_processor, $table = 'router') {
|
||||
$this->connection = $connection;
|
||||
$this->state = $state;
|
||||
$this->tableName = $table;
|
||||
$this->currentPath = $current_path;
|
||||
$this->cache = $cache_backend;
|
||||
$this->pathProcessor = $path_processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds routes that may potentially match the request.
|
||||
*
|
||||
* This may return a mixed list of class instances, but all routes returned
|
||||
* must extend the core symfony route. The classes may also implement
|
||||
* RouteObjectInterface to link to a content document.
|
||||
*
|
||||
* This method may not throw an exception based on implementation specific
|
||||
* restrictions on the url. That case is considered a not found - returning
|
||||
* an empty array. Exceptions are only used to abort the whole request in
|
||||
* case something is seriously broken, like the storage backend being down.
|
||||
*
|
||||
* Note that implementations may not implement an optimal matching
|
||||
* algorithm, simply a reasonable first pass. That allows for potentially
|
||||
* very large route sets to be filtered down to likely candidates, which
|
||||
* may then be filtered in memory more completely.
|
||||
*
|
||||
* @param Request $request A request against which to match.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\RouteCollection with all urls that
|
||||
* could potentially match $request. Empty collection if nothing can
|
||||
* match.
|
||||
*/
|
||||
public function getRouteCollectionForRequest(Request $request) {
|
||||
// Cache both the system path as well as route parameters and matching
|
||||
// routes.
|
||||
$cid = 'route:' . $request->getPathInfo() . ':' . $request->getQueryString();
|
||||
if ($cached = $this->cache->get($cid)) {
|
||||
$this->currentPath->setPath($cached->data['path'], $request);
|
||||
$request->query->replace($cached->data['query']);
|
||||
return $cached->data['routes'];
|
||||
}
|
||||
else {
|
||||
// Just trim on the right side.
|
||||
$path = $request->getPathInfo();
|
||||
$path = $path === '/' ? $path : rtrim($request->getPathInfo(), '/');
|
||||
$path = $this->pathProcessor->processInbound($path, $request);
|
||||
$this->currentPath->setPath($path, $request);
|
||||
// Incoming path processors may also set query parameters.
|
||||
$query_parameters = $request->query->all();
|
||||
$routes = $this->getRoutesByPath(rtrim($path, '/'));
|
||||
$cache_value = [
|
||||
'path' => $path,
|
||||
'query' => $query_parameters,
|
||||
'routes' => $routes,
|
||||
];
|
||||
$this->cache->set($cid, $cache_value, CacheBackendInterface::CACHE_PERMANENT, ['route_match']);
|
||||
return $routes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the route using the provided route name (and parameters).
|
||||
*
|
||||
* @param string $name
|
||||
* The route name to fetch
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route
|
||||
* The found route.
|
||||
*
|
||||
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException
|
||||
* Thrown if there is no route with that name in this repository.
|
||||
*/
|
||||
public function getRouteByName($name) {
|
||||
$routes = $this->getRoutesByNames(array($name));
|
||||
if (empty($routes)) {
|
||||
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
return reset($routes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preLoadRoutes($names) {
|
||||
if (empty($names)) {
|
||||
throw new \InvalidArgumentException('You must specify the route names to load');
|
||||
}
|
||||
|
||||
$routes_to_load = array_diff($names, array_keys($this->routes), array_keys($this->serializedRoutes));
|
||||
if ($routes_to_load) {
|
||||
$result = $this->connection->query('SELECT name, route FROM {' . $this->connection->escapeTable($this->tableName) . '} WHERE name IN ( :names[] )', array(':names[]' => $routes_to_load));
|
||||
$routes = $result->fetchAllKeyed();
|
||||
$this->serializedRoutes += $routes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutesByNames($names) {
|
||||
$this->preLoadRoutes($names);
|
||||
|
||||
foreach ($names as $name) {
|
||||
// The specified route name might not exist or might be serialized.
|
||||
if (!isset($this->routes[$name]) && isset($this->serializedRoutes[$name])) {
|
||||
$this->routes[$name] = unserialize($this->serializedRoutes[$name]);
|
||||
unset($this->serializedRoutes[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return array_intersect_key($this->routes, array_flip($names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of path pattern outlines that could match the path parts.
|
||||
*
|
||||
* @param array $parts
|
||||
* The parts of the path for which we want candidates.
|
||||
*
|
||||
* @return array
|
||||
* An array of outlines that could match the specified path parts.
|
||||
*/
|
||||
public function getCandidateOutlines(array $parts) {
|
||||
$number_parts = count($parts);
|
||||
$ancestors = array();
|
||||
$length = $number_parts - 1;
|
||||
$end = (1 << $number_parts) - 1;
|
||||
|
||||
// The highest possible mask is a 1 bit for every part of the path. We will
|
||||
// check every value down from there to generate a possible outline.
|
||||
if ($number_parts == 1) {
|
||||
$masks = array(1);
|
||||
}
|
||||
elseif ($number_parts <= 3) {
|
||||
// Optimization - don't query the state system for short paths. This also
|
||||
// insulates against the state entry for masks going missing for common
|
||||
// user-facing paths since we generate all values without checking state.
|
||||
$masks = range($end, 1);
|
||||
}
|
||||
elseif ($number_parts <= 0) {
|
||||
// No path can match, short-circuit the process.
|
||||
$masks = array();
|
||||
}
|
||||
else {
|
||||
// Get the actual patterns that exist out of state.
|
||||
$masks = (array) $this->state->get('routing.menu_masks.' . $this->tableName, array());
|
||||
}
|
||||
|
||||
|
||||
// Only examine patterns that actually exist as router items (the masks).
|
||||
foreach ($masks as $i) {
|
||||
if ($i > $end) {
|
||||
// Only look at masks that are not longer than the path of interest.
|
||||
continue;
|
||||
}
|
||||
elseif ($i < (1 << $length)) {
|
||||
// We have exhausted the masks of a given length, so decrease the length.
|
||||
--$length;
|
||||
}
|
||||
$current = '';
|
||||
for ($j = $length; $j >= 0; $j--) {
|
||||
// Check the bit on the $j offset.
|
||||
if ($i & (1 << $j)) {
|
||||
// Bit one means the original value.
|
||||
$current .= $parts[$length - $j];
|
||||
}
|
||||
else {
|
||||
// Bit zero means means wildcard.
|
||||
$current .= '%';
|
||||
}
|
||||
// Unless we are at offset 0, add a slash.
|
||||
if ($j) {
|
||||
$current .= '/';
|
||||
}
|
||||
}
|
||||
$ancestors[] = '/' . $current;
|
||||
}
|
||||
return $ancestors;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutesByPattern($pattern) {
|
||||
$path = RouteCompiler::getPatternOutline($pattern);
|
||||
|
||||
return $this->getRoutesByPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all routes which match a certain pattern.
|
||||
*
|
||||
* @param string $path
|
||||
* The route pattern to search for (contains % as placeholders).
|
||||
*
|
||||
* @return \Symfony\Component\Routing\RouteCollection
|
||||
* Returns a route collection of matching routes.
|
||||
*/
|
||||
protected function getRoutesByPath($path) {
|
||||
// Filter out each empty value, though allow '0' and 0, which would be
|
||||
// filtered out by empty().
|
||||
$parts = array_values(array_filter(explode('/', $path), function($value) {
|
||||
return $value !== NULL && $value !== '';
|
||||
}));
|
||||
|
||||
$collection = new RouteCollection();
|
||||
|
||||
$ancestors = $this->getCandidateOutlines($parts);
|
||||
if (empty($ancestors)) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
$routes = $this->connection->query("SELECT name, route FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN ( :patterns[] ) ORDER BY fit DESC, name ASC", array(
|
||||
':patterns[]' => $ancestors,
|
||||
))
|
||||
->fetchAllKeyed();
|
||||
|
||||
foreach ($routes as $name => $route) {
|
||||
$route = unserialize($route);
|
||||
if (preg_match($route->compile()->getRegex(), $path, $matches)) {
|
||||
$collection->add($name, $route);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllRoutes() {
|
||||
return new PagedRouteCollection($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset() {
|
||||
$this->routes = array();
|
||||
$this->serializedRoutes = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[RoutingEvents::FINISHED][] = array('reset');
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutesPaged($offset, $length = NULL) {
|
||||
$select = $this->connection->select($this->tableName, 'router')
|
||||
->fields('router', ['name', 'route']);
|
||||
|
||||
if (isset($length)) {
|
||||
$select->range($offset, $length);
|
||||
}
|
||||
|
||||
$routes = $select->execute()->fetchAllKeyed();
|
||||
|
||||
$result = [];
|
||||
foreach ($routes as $name => $route) {
|
||||
$result[$name] = unserialize($route);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutesCount() {
|
||||
return $this->connection->query("SELECT COUNT(*) FROM {" . $this->connection->escapeTable($this->tableName) . "}")->fetchField();
|
||||
}
|
||||
|
||||
}
|
48
core/lib/Drupal/Core/Routing/RouteProviderInterface.php
Normal file
48
core/lib/Drupal/Core/Routing/RouteProviderInterface.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteProviderInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Cmf\Component\Routing\RouteProviderInterface as RouteProviderBaseInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Extends the router provider interface
|
||||
*
|
||||
* @see \Symfony\Cmf\Component\Routing
|
||||
*/
|
||||
interface RouteProviderInterface extends RouteProviderBaseInterface {
|
||||
|
||||
/**
|
||||
* Get all routes which match a certain pattern.
|
||||
*
|
||||
* @param string $pattern
|
||||
* The route pattern to search for (contains {} as placeholders).
|
||||
*
|
||||
* @return \Symfony\Component\Routing\RouteCollection
|
||||
* Returns a route collection of matching routes.
|
||||
*/
|
||||
public function getRoutesByPattern($pattern);
|
||||
|
||||
/**
|
||||
* Returns all the routes on the system.
|
||||
*
|
||||
* Usage of this method is discouraged for performance reasons. If possible,
|
||||
* use RouteProviderInterface::getRoutesByNames() or
|
||||
* RouteProviderInterface::getRoutesByPattern() instead.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route[]
|
||||
* An iterator of routes keyed by route name.
|
||||
*/
|
||||
public function getAllRoutes();
|
||||
|
||||
/**
|
||||
* Resets the route provider object.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
}
|
46
core/lib/Drupal/Core/Routing/RouteSubscriberBase.php
Normal file
46
core/lib/Drupal/Core/Routing/RouteSubscriberBase.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RouteSubscriberBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides a base implementation for RouteSubscriber.
|
||||
*/
|
||||
abstract class RouteSubscriberBase implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Alters existing routes for a specific collection.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\RouteCollection $collection
|
||||
* The route collection for adding routes.
|
||||
*/
|
||||
abstract protected function alterRoutes(RouteCollection $collection);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[RoutingEvents::ALTER] = 'onAlterRoutes';
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates the route altering to self::alterRoutes().
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteBuildEvent $event
|
||||
* The route build event.
|
||||
*/
|
||||
public function onAlterRoutes(RouteBuildEvent $event) {
|
||||
$collection = $event->getRouteCollection();
|
||||
$this->alterRoutes($collection);
|
||||
}
|
||||
|
||||
}
|
68
core/lib/Drupal/Core/Routing/RoutingEvents.php
Normal file
68
core/lib/Drupal/Core/Routing/RoutingEvents.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\RoutingEvents.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
/**
|
||||
* Contains all events thrown in the core routing component.
|
||||
*/
|
||||
final class RoutingEvents {
|
||||
|
||||
/**
|
||||
* Name of the event fired during route collection to allow new routes.
|
||||
*
|
||||
* This event is used to add new routes based upon existing routes, giving
|
||||
* modules the opportunity to dynamically generate additional routes. The
|
||||
* event listener method receives a \Drupal\Core\Routing\RouteBuildEvent
|
||||
* instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\Core\Routing\RouteBuildEvent
|
||||
* @see \Drupal\Core\EventSubscriber\EntityRouteProviderSubscriber
|
||||
* @see \Drupal\Core\Routing\RouteBuilder::rebuild()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DYNAMIC = 'routing.route_dynamic';
|
||||
|
||||
/**
|
||||
* Name of the event fired during route collection to allow changes to routes.
|
||||
*
|
||||
* This event is used to process new routes before they get saved, giving
|
||||
* modules the opportunity to alter routes provided by any other module. The
|
||||
* event listener method receives a \Drupal\Core\Routing\RouteBuildEvent
|
||||
* instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Symfony\Component\Routing\RouteCollection
|
||||
* @see \Drupal\system\EventSubscriber\AdminRouteSubscriber
|
||||
* @see \Drupal\Core\Routing\RouteBuilder::rebuild()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ALTER = 'routing.route_alter';
|
||||
|
||||
/**
|
||||
* Name of the event fired to indicate route building has ended.
|
||||
*
|
||||
* This event gives modules the opportunity to perform some action after route
|
||||
* building has completed. The event listener receives a
|
||||
* \Symfony\Component\EventDispatcher\Event instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Symfony\Component\EventDispatcher\Event
|
||||
* @see \Drupal\Core\EventSubscriber\MenuRouterRebuildSubscriber
|
||||
* @see \Drupal\Core\Routing\RouteBuilder::rebuild()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const FINISHED = 'routing.route_finished';
|
||||
|
||||
}
|
52
core/lib/Drupal/Core/Routing/StackedRouteMatchInterface.php
Normal file
52
core/lib/Drupal/Core/Routing/StackedRouteMatchInterface.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\StackedRouteMatchInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Defines an interface for a stack of route matches.
|
||||
*
|
||||
* This could be for example used on exception pages.
|
||||
*/
|
||||
interface StackedRouteMatchInterface extends RouteMatchInterface {
|
||||
|
||||
/**
|
||||
* Gets the current route match.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
public function getCurrentRouteMatch();
|
||||
|
||||
/**
|
||||
* Gets the master route match..
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
public function getMasterRouteMatch();
|
||||
|
||||
/**
|
||||
* Returns the parent route match of the current.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RouteMatchInterface\NULL
|
||||
* The parent route match or NULL, if it the master route match.
|
||||
*/
|
||||
public function getParentRouteMatch();
|
||||
|
||||
/**
|
||||
* Returns a route match from a given request, if possible.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request
|
||||
* The request.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\RouteMatchInterface|NULL
|
||||
* THe matching route match, or NULL if there is no matching one.
|
||||
*/
|
||||
public function getRouteMatchFromRequest(Request $request);
|
||||
|
||||
}
|
558
core/lib/Drupal/Core/Routing/UrlGenerator.php
Normal file
558
core/lib/Drupal/Core/Routing/UrlGenerator.php
Normal file
|
@ -0,0 +1,558 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\UrlGenerator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\GeneratedUrl;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\RequestContext as SymfonyRequestContext;
|
||||
use Symfony\Component\Routing\Route as SymfonyRoute;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
|
||||
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
|
||||
use Symfony\Component\Routing\Exception\InvalidParameterException;
|
||||
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
|
||||
|
||||
/**
|
||||
* Generates URLs from route names and parameters.
|
||||
*/
|
||||
class UrlGenerator implements UrlGeneratorInterface {
|
||||
|
||||
/**
|
||||
* @var RequestContext
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* A request stack object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The path processor to convert the system path to one suitable for urls.
|
||||
*
|
||||
* @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface
|
||||
*/
|
||||
protected $pathProcessor;
|
||||
|
||||
/**
|
||||
* The route processor.
|
||||
*
|
||||
* @var \Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface
|
||||
*/
|
||||
protected $routeProcessor;
|
||||
|
||||
/**
|
||||
* Overrides characters that will not be percent-encoded in the path segment.
|
||||
*
|
||||
* @see \Symfony\Component\Routing\Generator\UrlGenerator
|
||||
*/
|
||||
protected $decodedChars = array(
|
||||
// the slash can be used to designate a hierarchical structure and we want allow using it with this meaning
|
||||
// some webservers don't allow the slash in encoded form in the path for security reasons anyway
|
||||
// see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss
|
||||
'%2F' => '/',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs a new generator object.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $provider
|
||||
* The route provider to be searched for routes.
|
||||
* @param \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $path_processor
|
||||
* The path processor to convert the system path to one suitable for urls.
|
||||
* @param \Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface $route_processor
|
||||
* The route processor.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
|
||||
* The config factory.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* A request stack object.
|
||||
*/
|
||||
public function __construct(RouteProviderInterface $provider, OutboundPathProcessorInterface $path_processor, OutboundRouteProcessorInterface $route_processor, ConfigFactoryInterface $config, RequestStack $request_stack) {
|
||||
$this->provider = $provider;
|
||||
$this->context = new RequestContext();
|
||||
|
||||
$this->pathProcessor = $path_processor;
|
||||
$this->routeProcessor = $route_processor;
|
||||
$allowed_protocols = $config->get('system.filter')->get('protocols') ?: array('http', 'https');
|
||||
UrlHelper::setAllowedProtocols($allowed_protocols);
|
||||
$this->requestStack = $request_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setContext(SymfonyRequestContext $context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStrictRequirements($enabled) {
|
||||
// Ignore changes to this.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isStrictRequirements() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPathFromRoute($name, $parameters = array()) {
|
||||
$route = $this->getRoute($name);
|
||||
$name = $this->getRouteDebugMessage($name);
|
||||
$this->processRoute($name, $route, $parameters);
|
||||
$path = $this->getInternalPathFromRoute($name, $route, $parameters);
|
||||
// Router-based paths may have a querystring on them but Drupal paths may
|
||||
// not have one, so remove any ? and anything after it. For generate() this
|
||||
// is handled in processPath().
|
||||
$path = preg_replace('/\?.*/', '', $path);
|
||||
return trim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitute the route parameters into the route path.
|
||||
*
|
||||
* Note: This code was copied from
|
||||
* \Symfony\Component\Routing\Generator\UrlGenerator::doGenerate() and
|
||||
* shortened by removing code that is not relevant to Drupal or that is
|
||||
* handled separately in ::generateFromRoute(). The Symfony version should be
|
||||
* examined for changes in new Symfony releases.
|
||||
*
|
||||
* @param array $variables
|
||||
* The variables form the compiled route, corresponding to slugs in the
|
||||
* route path.
|
||||
* @param array $defaults
|
||||
* The defaults from the route.
|
||||
* @param array $tokens
|
||||
* The tokens from the compiled route.
|
||||
* @param array $parameters
|
||||
* The route parameters passed to the generator. Parameters that do not
|
||||
* match any variables will be added to the result as query parameters.
|
||||
* @param array $query_params
|
||||
* Query parameters passed to the generator as $options['query'].
|
||||
* @param string $name
|
||||
* The route name or other identifying string from ::getRouteDebugMessage().
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* The url path, without any base path, including possible query string.
|
||||
*
|
||||
* @throws MissingMandatoryParametersException
|
||||
* When some parameters are missing that are mandatory for the route
|
||||
* @throws InvalidParameterException
|
||||
* When a parameter value for a placeholder is not correct because it does
|
||||
* not match the requirement
|
||||
*/
|
||||
protected function doGenerate(array $variables, array $defaults, array $tokens, array $parameters, array $query_params, $name) {
|
||||
$variables = array_flip($variables);
|
||||
$mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters);
|
||||
|
||||
// all params must be given
|
||||
if ($diff = array_diff_key($variables, $mergedParams)) {
|
||||
throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name));
|
||||
}
|
||||
|
||||
$url = '';
|
||||
// Tokens start from the end of the path and work to the beginning. The
|
||||
// first one or several variable tokens may be optional, but once we find a
|
||||
// supplied token or a static text portion of the path, all remaining
|
||||
// variables up to the start of the path must be supplied to there is no gap.
|
||||
$optional = TRUE;
|
||||
// Structure of $tokens from the compiled route:
|
||||
// If the path is /admin/config/user-interface/shortcut/manage/{shortcut_set}/add-link-inline
|
||||
// [ [ 0 => 'text', 1 => '/add-link-inline' ], [ 0 => 'variable', 1 => '/', 2 => '[^/]++', 3 => 'shortcut_set' ], [ 0 => 'text', 1 => '/admin/config/user-interface/shortcut/manage' ] ]
|
||||
//
|
||||
// For a simple fixed path, there is just one token.
|
||||
// If the path is /admin/config
|
||||
// [ [ 0 => 'text', 1 => '/admin/config' ] ]
|
||||
foreach ($tokens as $token) {
|
||||
if ('variable' === $token[0]) {
|
||||
if (!$optional || !array_key_exists($token[3], $defaults) || (isset($mergedParams[$token[3]]) && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]])) {
|
||||
// check requirement
|
||||
if (!preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) {
|
||||
$message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]);
|
||||
throw new InvalidParameterException($message);
|
||||
}
|
||||
|
||||
$url = $token[1] . $mergedParams[$token[3]] . $url;
|
||||
$optional = FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Static text
|
||||
$url = $token[1] . $url;
|
||||
$optional = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if ('' === $url) {
|
||||
$url = '/';
|
||||
}
|
||||
|
||||
// The contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request)
|
||||
$url = strtr(rawurlencode($url), $this->decodedChars);
|
||||
|
||||
// Drupal paths rarely include dots, so skip this processing if possible.
|
||||
if (strpos($url, '/.') !== FALSE) {
|
||||
// the path segments "." and ".." are interpreted as relative reference when
|
||||
// resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3
|
||||
// so we need to encode them as they are not used for this purpose here
|
||||
// otherwise we would generate a URI that, when followed by a user agent
|
||||
// (e.g. browser), does not match this route
|
||||
$url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/'));
|
||||
if ('/..' === substr($url, -3)) {
|
||||
$url = substr($url, 0, -2) . '%2E%2E';
|
||||
}
|
||||
elseif ('/.' === substr($url, -2)) {
|
||||
$url = substr($url, 0, -1) . '%2E';
|
||||
}
|
||||
}
|
||||
|
||||
// Add a query string if needed, including extra parameters.
|
||||
$query_params += array_diff_key($parameters, $variables, $defaults);
|
||||
if ($query_params && $query = http_build_query($query_params, '', '&')) {
|
||||
// "/" and "?" can be left decoded for better user experience, see
|
||||
// http://tools.ietf.org/html/rfc3986#section-3.4
|
||||
$url .= '?'.strtr($query, array('%2F' => '/'));
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of a route.
|
||||
*
|
||||
* @param $name
|
||||
* The route name or other debug message.
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route object.
|
||||
* @param array $parameters
|
||||
* An array of parameters as passed to
|
||||
* \Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate().
|
||||
* @param array $query_params
|
||||
* An array of query string parameter, which will get any extra values from
|
||||
* $parameters merged in.
|
||||
*
|
||||
* @return string
|
||||
* The url path corresponding to the route, without the base path.
|
||||
*/
|
||||
protected function getInternalPathFromRoute($name, SymfonyRoute $route, $parameters = array(), $query_params = array()) {
|
||||
// The Route has a cache of its own and is not recompiled as long as it does
|
||||
// not get modified.
|
||||
$compiledRoute = $route->compile();
|
||||
|
||||
return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $compiledRoute->getTokens(), $parameters, $query_params, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generate($name, $parameters = array(), $absolute = FALSE) {
|
||||
$options['absolute'] = $absolute;
|
||||
return $this->generateFromRoute($name, $parameters, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generateFromRoute($name, $parameters = array(), $options = array(), $collect_cacheability_metadata = FALSE) {
|
||||
$generated_url = $collect_cacheability_metadata ? new GeneratedUrl() : NULL;
|
||||
|
||||
$options += array('prefix' => '');
|
||||
$route = $this->getRoute($name);
|
||||
$name = $this->getRouteDebugMessage($name);
|
||||
$this->processRoute($name, $route, $parameters, $generated_url);
|
||||
|
||||
$query_params = [];
|
||||
// Symfony adds any parameters that are not path slugs as query strings.
|
||||
if (isset($options['query']) && is_array($options['query'])) {
|
||||
$query_params = $options['query'];
|
||||
}
|
||||
|
||||
$path = $this->getInternalPathFromRoute($name, $route, $parameters, $query_params);
|
||||
$path = $this->processPath($path, $options, $generated_url);
|
||||
|
||||
if (!empty($options['prefix'])) {
|
||||
$path = ltrim($path, '/');
|
||||
$prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
|
||||
$path = '/' . str_replace('%2F', '/', rawurlencode($prefix)) . $path;
|
||||
}
|
||||
|
||||
$fragment = '';
|
||||
if (isset($options['fragment'])) {
|
||||
if (($fragment = trim($options['fragment'])) != '') {
|
||||
$fragment = '#' . $fragment;
|
||||
}
|
||||
}
|
||||
|
||||
// The base_url might be rewritten from the language rewrite in domain mode.
|
||||
if (isset($options['base_url'])) {
|
||||
$base_url = $options['base_url'];
|
||||
|
||||
if (isset($options['https'])) {
|
||||
if ($options['https'] === TRUE) {
|
||||
$base_url = str_replace('http://', 'https://', $base_url);
|
||||
}
|
||||
elseif ($options['https'] === FALSE) {
|
||||
$base_url = str_replace('https://', 'http://', $base_url);
|
||||
}
|
||||
}
|
||||
|
||||
$url = $base_url . $path . $fragment;
|
||||
return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($url) : $url;
|
||||
}
|
||||
|
||||
$base_url = $this->context->getBaseUrl();
|
||||
|
||||
$absolute = !empty($options['absolute']);
|
||||
if (!$absolute || !$host = $this->context->getHost()) {
|
||||
|
||||
if ($route->getOption('_only_fragment')) {
|
||||
return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($fragment) : $fragment;
|
||||
}
|
||||
|
||||
$url = $base_url . $path . $fragment;
|
||||
return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($url) : $url;
|
||||
}
|
||||
|
||||
// Prepare an absolute URL by getting the correct scheme, host and port from
|
||||
// the request context.
|
||||
if (isset($options['https'])) {
|
||||
$scheme = $options['https'] ? 'https' : 'http';
|
||||
}
|
||||
else {
|
||||
$scheme = $this->context->getScheme();
|
||||
}
|
||||
$scheme_req = $route->getSchemes();
|
||||
if ($scheme_req && ($req = $scheme_req[0]) && $scheme !== $req) {
|
||||
$scheme = $req;
|
||||
}
|
||||
$port = '';
|
||||
if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
|
||||
$port = ':' . $this->context->getHttpPort();
|
||||
} elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) {
|
||||
$port = ':' . $this->context->getHttpsPort();
|
||||
}
|
||||
if ($collect_cacheability_metadata) {
|
||||
$generated_url->addCacheContexts(['url.site']);
|
||||
}
|
||||
$url = $scheme . '://' . $host . $port . $base_url . $path . $fragment;
|
||||
return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($url) : $url;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generateFromPath($path = NULL, $options = array(), $collect_cacheability_metadata = FALSE) {
|
||||
$generated_url = $collect_cacheability_metadata ? new GeneratedUrl() : NULL;
|
||||
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
$current_base_path = $request->getBasePath() . '/';
|
||||
$current_base_url = $request->getSchemeAndHttpHost() . $current_base_path;
|
||||
$current_script_path = '';
|
||||
$base_path_with_script = $request->getBaseUrl();
|
||||
if (!empty($base_path_with_script)) {
|
||||
$script_name = $request->getScriptName();
|
||||
if (strpos($base_path_with_script, $script_name) !== FALSE) {
|
||||
$current_script_path = ltrim(substr($script_name, strlen($current_base_path)), '/') . '/';
|
||||
}
|
||||
}
|
||||
|
||||
// Merge in defaults.
|
||||
$options += array(
|
||||
'fragment' => '',
|
||||
'query' => array(),
|
||||
'absolute' => FALSE,
|
||||
'prefix' => '',
|
||||
);
|
||||
|
||||
// A duplicate of the code from
|
||||
// \Drupal\Component\Utility\UrlHelper::isExternal() to avoid needing
|
||||
// another function call, since performance inside url() is critical.
|
||||
if (!isset($options['external'])) {
|
||||
$colonpos = strpos($path, ':');
|
||||
// Avoid calling drupal_strip_dangerous_protocols() if there is any slash
|
||||
// (/), hash (#) or question_mark (?) before the colon (:) occurrence -
|
||||
// if any - as this would clearly mean it is not a URL. If the path starts
|
||||
// with 2 slashes then it is always considered an external URL without an
|
||||
// explicit protocol part.
|
||||
$options['external'] = (strpos($path, '//') === 0)
|
||||
|| ($colonpos !== FALSE
|
||||
&& !preg_match('![/?#]!', substr($path, 0, $colonpos))
|
||||
&& UrlHelper::stripDangerousProtocols($path) == $path);
|
||||
}
|
||||
|
||||
if (isset($options['fragment']) && $options['fragment'] !== '') {
|
||||
$options['fragment'] = '#' . $options['fragment'];
|
||||
}
|
||||
|
||||
if ($options['external']) {
|
||||
// Split off the fragment.
|
||||
if (strpos($path, '#') !== FALSE) {
|
||||
list($path, $old_fragment) = explode('#', $path, 2);
|
||||
// If $options contains no fragment, take it over from the path.
|
||||
if (isset($old_fragment) && !$options['fragment']) {
|
||||
$options['fragment'] = '#' . $old_fragment;
|
||||
}
|
||||
}
|
||||
// Append the query.
|
||||
if ($options['query']) {
|
||||
$path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($options['query']);
|
||||
}
|
||||
if (isset($options['https'])) {
|
||||
if ($options['https'] === TRUE) {
|
||||
$path = str_replace('http://', 'https://', $path);
|
||||
}
|
||||
elseif ($options['https'] === FALSE) {
|
||||
$path = str_replace('https://', 'http://', $path);
|
||||
}
|
||||
}
|
||||
// Reassemble.
|
||||
$url = $path . $options['fragment'];
|
||||
return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($url) : $url;
|
||||
}
|
||||
else {
|
||||
$path = ltrim($this->processPath('/' . $path, $options, $generated_url), '/');
|
||||
}
|
||||
|
||||
if (!isset($options['script'])) {
|
||||
$options['script'] = $current_script_path;
|
||||
}
|
||||
// The base_url might be rewritten from the language rewrite in domain mode.
|
||||
if (!isset($options['base_url'])) {
|
||||
if (isset($options['https'])) {
|
||||
if ($options['https'] === TRUE) {
|
||||
$options['base_url'] = str_replace('http://', 'https://', $current_base_url);
|
||||
$options['absolute'] = TRUE;
|
||||
}
|
||||
elseif ($options['https'] === FALSE) {
|
||||
$options['base_url'] = str_replace('https://', 'http://', $current_base_url);
|
||||
$options['absolute'] = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$options['base_url'] = $current_base_url;
|
||||
}
|
||||
}
|
||||
elseif (rtrim($options['base_url'], '/') == $options['base_url']) {
|
||||
$options['base_url'] .= '/';
|
||||
}
|
||||
$base = $options['absolute'] ? $options['base_url'] : $current_base_path;
|
||||
$prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
|
||||
|
||||
if ($options['absolute'] && $collect_cacheability_metadata) {
|
||||
$generated_url->addCacheContexts(['url.site']);
|
||||
}
|
||||
|
||||
$path = str_replace('%2F', '/', rawurlencode($prefix . $path));
|
||||
$query = $options['query'] ? ('?' . UrlHelper::buildQuery($options['query'])) : '';
|
||||
$url = $base . $options['script'] . $path . $query . $options['fragment'];
|
||||
return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($url) : $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the path to a processor manager to allow alterations.
|
||||
*/
|
||||
protected function processPath($path, &$options = array(), CacheableMetadata $cacheable_metadata = NULL) {
|
||||
// Router-based paths may have a querystring on them.
|
||||
if ($query_pos = strpos($path, '?')) {
|
||||
// We don't need to do a strict check here because position 0 would mean we
|
||||
// have no actual path to work with.
|
||||
$actual_path = substr($path, 0, $query_pos);
|
||||
$query_string = substr($path, $query_pos);
|
||||
}
|
||||
else {
|
||||
$actual_path = $path;
|
||||
$query_string = '';
|
||||
}
|
||||
$path = $this->pathProcessor->processOutbound($actual_path === '/' ? $actual_path : rtrim($actual_path, '/'), $options, $this->requestStack->getCurrentRequest(), $cacheable_metadata);
|
||||
$path .= $query_string;
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the route to the processor manager for altering before compilation.
|
||||
*
|
||||
* @param string $name
|
||||
* The route name.
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route object to process.
|
||||
* @param array $parameters
|
||||
* An array of parameters to be passed to the route compiler.
|
||||
* @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata
|
||||
* (optional) Object to collect route processors' cacheability.
|
||||
*/
|
||||
protected function processRoute($name, SymfonyRoute $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
|
||||
$this->routeProcessor->processOutbound($name, $route, $parameters, $cacheable_metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the route using the provided route name.
|
||||
*
|
||||
* @param string|\Symfony\Component\Routing\Route $name
|
||||
* The route name or a route object.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route
|
||||
* The found route.
|
||||
*
|
||||
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException
|
||||
* Thrown if there is no route with that name in this repository.
|
||||
*
|
||||
* @see \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected function getRoute($name) {
|
||||
if ($name instanceof SymfonyRoute) {
|
||||
$route = $name;
|
||||
}
|
||||
elseif (NULL === $route = clone $this->provider->getRouteByName($name)) {
|
||||
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name));
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function supports($name) {
|
||||
// Support a route object and any string as route name.
|
||||
return is_string($name) || $name instanceof SymfonyRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRouteDebugMessage($name, array $parameters = array()) {
|
||||
if (is_scalar($name)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if ($name instanceof SymfonyRoute) {
|
||||
return 'Route with pattern ' . $name->getPath();
|
||||
}
|
||||
|
||||
return serialize($name);
|
||||
}
|
||||
}
|
164
core/lib/Drupal/Core/Routing/UrlGeneratorInterface.php
Normal file
164
core/lib/Drupal/Core/Routing/UrlGeneratorInterface.php
Normal file
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\UrlGeneratorInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Cmf\Component\Routing\VersatileGeneratorInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for generating a url from a route or system path.
|
||||
*
|
||||
* Provides additional methods and options not present in the base interface.
|
||||
*/
|
||||
interface UrlGeneratorInterface extends VersatileGeneratorInterface {
|
||||
|
||||
/**
|
||||
* Generates an internal or external URL.
|
||||
*
|
||||
* @param string $path
|
||||
* (optional) The internal path or external URL being linked to, such as
|
||||
* "node/34" or "http://example.com/foo". The default value is equivalent to
|
||||
* passing in '<front>'. A few notes:
|
||||
* - If you provide a full URL, it will be considered an external URL.
|
||||
* - If you provide only the path (e.g. "node/34"), it will be
|
||||
* considered an internal link. In this case, it should be a system URL,
|
||||
* and it will be replaced with the alias, if one exists. Additional query
|
||||
* arguments for internal paths must be supplied in $options['query'], not
|
||||
* included in $path.
|
||||
* - If you provide an internal path and $options['alias'] is set to TRUE, the
|
||||
* path is assumed already to be the correct path alias, and the alias is
|
||||
* not looked up.
|
||||
* - The special string '<front>' generates a link to the site's base URL.
|
||||
* - If your external URL contains a query (e.g. http://example.com/foo?a=b),
|
||||
* then you can either URL encode the query keys and values yourself and
|
||||
* include them in $path, or use $options['query'] to let this method
|
||||
* URL encode them.
|
||||
*
|
||||
* @param array $options
|
||||
* (optional) An associative array of additional options, with the following
|
||||
* elements:
|
||||
* - 'query': An array of query key/value-pairs (without any URL-encoding) to
|
||||
* append to the URL.
|
||||
* - 'fragment': A fragment identifier (named anchor) to append to the URL.
|
||||
* Do not include the leading '#' character.
|
||||
* - 'absolute': Defaults to FALSE. Whether to force the output to be an
|
||||
* absolute link (beginning with http:). Useful for links that will be
|
||||
* displayed outside the site, such as in an RSS feed.
|
||||
* - 'alias': Defaults to FALSE. Whether the given path is a URL alias
|
||||
* already.
|
||||
* - 'external': Whether the given path is an external URL.
|
||||
* - 'language': An optional language object. If the path being linked to is
|
||||
* internal to the site, $options['language'] is used to look up the alias
|
||||
* for the URL. If $options['language'] is omitted, the language will be
|
||||
* obtained from
|
||||
* \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_URL).
|
||||
* - 'https': Whether this URL should point to a secure location. If not
|
||||
* defined, the current scheme is used, so the user stays on HTTP or HTTPS
|
||||
* respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
|
||||
* - 'base_url': Only used internally by a path processor, for example, to
|
||||
* modify the base URL when a language dependent URL requires so.
|
||||
* - 'prefix': Only used internally, to modify the path when a language
|
||||
* dependent URL requires so.
|
||||
* - 'script': Added to the URL between the base path and the path prefix.
|
||||
* Defaults to empty string when clean URLs are in effect, and to
|
||||
* 'index.php/' when they are not.
|
||||
* - 'entity_type': The entity type of the object that called _url(). Only
|
||||
* set if _url() is invoked by Drupal\Core\Entity\Entity::uri().
|
||||
* - 'entity': The entity object (such as a node) for which the URL is being
|
||||
* generated. Only set if _url() is invoked by Drupal\Core\Entity\Entity::uri().
|
||||
* @param bool $collect_cacheability_metadata
|
||||
* (optional) Defaults to FALSE. When TRUE, both the generated URL and its
|
||||
* associated cacheability metadata are returned.
|
||||
*
|
||||
* @return string|\Drupal\Core\GeneratedUrl
|
||||
* A string containing a URL to the given path.
|
||||
* When $collect_cacheability_metadata is TRUE, a GeneratedUrl object is
|
||||
* returned, containing the generated URL plus cacheability metadata.
|
||||
*
|
||||
* @throws \Drupal\Core\Routing\GeneratorNotInitializedException.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 8.0.0.
|
||||
* To generate URLs for Drupal routes (that is, most pages generated by
|
||||
* Drupal), see UrlGeneratorInterface::generateFromRoute() instead. For
|
||||
* non-routed local URIs relative to the base path (like robots.txt) see
|
||||
* \Drupal\Core\Utility\UnroutedUrlAssembler.
|
||||
*
|
||||
* @see static::generateFromRoute()
|
||||
* @see \Drupal\Core\Utility\UnroutedUrlAssembler
|
||||
* @see \Drupal\Core\Url
|
||||
* @see \Drupal\Core\GeneratedUrl
|
||||
*/
|
||||
public function generateFromPath($path = NULL, $options = array(), $collect_cacheability_metadata = FALSE);
|
||||
|
||||
/**
|
||||
* Gets the internal path (system path) of a route.
|
||||
*
|
||||
* @param string|\Symfony\Component\Routing\Route $name
|
||||
* The route name or a route object.
|
||||
* @param array $parameters
|
||||
* An array of parameters as passed to
|
||||
* \Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate().
|
||||
*
|
||||
* @return string
|
||||
* The internal Drupal path corresponding to the route.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 8.0.0
|
||||
* System paths should not be used - use route names and parameters.
|
||||
*/
|
||||
public function getPathFromRoute($name, $parameters = array());
|
||||
|
||||
/**
|
||||
* Generates a URL or path for a specific route based on the given parameters.
|
||||
*
|
||||
* Parameters that reference placeholders in the route pattern will be
|
||||
* substituted for them in the pattern. Extra params are added as query
|
||||
* strings to the URL.
|
||||
*
|
||||
* @param string|\Symfony\Component\Routing\Route $name
|
||||
* The route name or a route object.
|
||||
* @param array $parameters
|
||||
* An associative array of parameter names and values.
|
||||
* @param array $options
|
||||
* (optional) An associative array of additional options, with the following
|
||||
* elements:
|
||||
* - 'query': An array of query key/value-pairs (without any URL-encoding)
|
||||
* to append to the URL.
|
||||
* - 'fragment': A fragment identifier (named anchor) to append to the URL.
|
||||
* Do not include the leading '#' character.
|
||||
* - 'absolute': Defaults to FALSE. Whether to force the output to be an
|
||||
* absolute link (beginning with http:). Useful for links that will be
|
||||
* displayed outside the site, such as in an RSS feed.
|
||||
* - 'language': An optional language object used to look up the alias
|
||||
* for the URL. If $options['language'] is omitted, it defaults to the
|
||||
* current language for the language type LanguageInterface::TYPE_URL.
|
||||
* - 'https': Whether this URL should point to a secure location. If not
|
||||
* defined, the current scheme is used, so the user stays on HTTP or HTTPS
|
||||
* respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
|
||||
* - 'base_url': Only used internally by a path processor, for example, to
|
||||
* modify the base URL when a language dependent URL requires so.
|
||||
* - 'prefix': Only used internally, to modify the path when a language
|
||||
* dependent URL requires so.
|
||||
* @param bool $collect_cacheability_metadata
|
||||
* (optional) Defaults to FALSE. When TRUE, both the generated URL and its
|
||||
* associated cacheability metadata are returned.
|
||||
*
|
||||
* @return string|\Drupal\Core\GeneratedUrl
|
||||
* The generated URL for the given route.
|
||||
* When $collect_cacheability_metadata is TRUE, a GeneratedUrl object is
|
||||
* returned, containing the generated URL plus cacheability metadata.
|
||||
*
|
||||
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException
|
||||
* Thrown when the named route does not exist.
|
||||
* @throws \Symfony\Component\Routing\Exception\MissingMandatoryParametersException
|
||||
* Thrown when some parameters are missing that are mandatory for the route.
|
||||
* @throws \Symfony\Component\Routing\Exception\InvalidParameterException
|
||||
* Thrown when a parameter value for a placeholder is not correct because it
|
||||
* does not match the requirement.
|
||||
*/
|
||||
public function generateFromRoute($name, $parameters = array(), $options = array(), $collect_cacheability_metadata = FALSE);
|
||||
|
||||
}
|
90
core/lib/Drupal/Core/Routing/UrlGeneratorTrait.php
Normal file
90
core/lib/Drupal/Core/Routing/UrlGeneratorTrait.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\UrlGeneratorTrait.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
/**
|
||||
* Wrapper methods for the Url Generator.
|
||||
*
|
||||
* This utility trait should only be used in application-level code, such as
|
||||
* classes that would implement ContainerInjectionInterface. Services registered
|
||||
* in the Container should not use this trait but inject the appropriate service
|
||||
* directly for easier testing.
|
||||
*/
|
||||
trait UrlGeneratorTrait {
|
||||
|
||||
/**
|
||||
* The url generator.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\UrlGeneratorInterface
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* Generates a URL or path for a specific route based on the given parameters.
|
||||
*
|
||||
* @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
|
||||
* details on the arguments, usage, and possible exceptions.
|
||||
*
|
||||
* @return string
|
||||
* The generated URL for the given route.
|
||||
*/
|
||||
protected function url($route_name, $route_parameters = array(), $options = array()) {
|
||||
return $this->getUrlGenerator()->generateFromRoute($route_name, $route_parameters, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a redirect response object for the specified route.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The name of the route to which to redirect.
|
||||
* @param array $route_parameters
|
||||
* (optional) Parameters for the route.
|
||||
* @param array $options
|
||||
* (optional) An associative array of additional options.
|
||||
* @param int $status
|
||||
* (optional) The HTTP redirect status code for the redirect. The default is
|
||||
* 302 Found.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* A redirect response object that may be returned by the controller.
|
||||
*/
|
||||
protected function redirect($route_name, array $route_parameters = [], array $options = [], $status = 302) {
|
||||
$options['absolute'] = TRUE;
|
||||
$url = $this->url($route_name, $route_parameters, $options);
|
||||
return new RedirectResponse($url, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL generator service.
|
||||
*
|
||||
* @return \Drupal\Core\Routing\UrlGeneratorInterface
|
||||
* The URL generator service.
|
||||
*/
|
||||
protected function getUrlGenerator() {
|
||||
if (!$this->urlGenerator) {
|
||||
$this->urlGenerator = \Drupal::service('url_generator');
|
||||
}
|
||||
return $this->urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL generator service.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\UrlGeneratorInterface $generator
|
||||
* The url generator service.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUrlGenerator(UrlGeneratorInterface $generator) {
|
||||
$this->urlGenerator = $generator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
49
core/lib/Drupal/Core/Routing/UrlMatcher.php
Normal file
49
core/lib/Drupal/Core/Routing/UrlMatcher.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\UrlMatcher.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
use Symfony\Cmf\Component\Routing\NestedMatcher\UrlMatcher as BaseUrlMatcher;
|
||||
|
||||
/**
|
||||
* Drupal-specific URL Matcher; handles the Drupal "system path" mapping.
|
||||
*/
|
||||
class UrlMatcher extends BaseUrlMatcher {
|
||||
|
||||
/**
|
||||
* The current path.
|
||||
*
|
||||
* @var \Drupal\Core\Path\CurrentPathStack
|
||||
*/
|
||||
protected $currentPath;
|
||||
|
||||
/**
|
||||
* Constructs a new UrlMatcher.
|
||||
*
|
||||
* The parent class has a constructor we need to skip, so just override it
|
||||
* with a no-op.
|
||||
*
|
||||
* @param \Drupal\Core\Path\CurrentPathStack $current_path
|
||||
* The current path.
|
||||
*/
|
||||
public function __construct(CurrentPathStack $current_path) {
|
||||
$this->currentPath = $current_path;
|
||||
}
|
||||
|
||||
public function finalMatch(RouteCollection $collection, Request $request) {
|
||||
$this->routes = $collection;
|
||||
$context = new RequestContext();
|
||||
$context->fromRequest($request);
|
||||
$this->setContext($context);
|
||||
|
||||
return $this->match($this->currentPath->getPath($request));
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue