Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View 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.
}

View 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));
}
}

View 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);
}

View 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');
}
}

View 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'];
}
}

View 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;
}
}

View 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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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 { }

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}

View file

@ -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 {
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View file

@ -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);
}

View 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;
}
}

View file

@ -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);
}

View 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;
}
}

View 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;
}
}

View 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]));
}
}

View 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);
}
}

View 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;
}
}

View 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();
}
}

View 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();
}

View 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;
}
}

View 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);
}

View 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;
}
}

View 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();
}

View 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;
}
}

View 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();
}
}

View 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();
}

View 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);
}
}

View 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';
}

View 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);
}

View 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);
}
}

View 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);
}

View 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;
}
}

View 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));
}
}