220 lines
7.5 KiB
PHP
220 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace Drupal\system;
|
|
|
|
use Drupal\Component\Utility\Unicode;
|
|
use Drupal\Core\Access\AccessManagerInterface;
|
|
use Drupal\Core\Breadcrumb\Breadcrumb;
|
|
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
|
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
|
use Drupal\Core\Controller\TitleResolverInterface;
|
|
use Drupal\Core\Link;
|
|
use Drupal\Core\ParamConverter\ParamNotConvertedException;
|
|
use Drupal\Core\Path\CurrentPathStack;
|
|
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
|
use Drupal\Core\Routing\RequestContext;
|
|
use Drupal\Core\Routing\RouteMatch;
|
|
use Drupal\Core\Routing\RouteMatchInterface;
|
|
use Drupal\Core\Session\AccountInterface;
|
|
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
|
use Drupal\Core\Url;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
|
|
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
|
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
|
|
|
/**
|
|
* Class to define the menu_link breadcrumb builder.
|
|
*/
|
|
class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
|
use StringTranslationTrait;
|
|
|
|
/**
|
|
* The router request context.
|
|
*
|
|
* @var \Drupal\Core\Routing\RequestContext
|
|
*/
|
|
protected $context;
|
|
|
|
/**
|
|
* The menu link access service.
|
|
*
|
|
* @var \Drupal\Core\Access\AccessManagerInterface
|
|
*/
|
|
protected $accessManager;
|
|
|
|
/**
|
|
* The dynamic router service.
|
|
*
|
|
* @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
|
|
*/
|
|
protected $router;
|
|
|
|
/**
|
|
* The dynamic router service.
|
|
*
|
|
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
|
|
*/
|
|
protected $pathProcessor;
|
|
|
|
/**
|
|
* Site config object.
|
|
*
|
|
* @var \Drupal\Core\Config\Config
|
|
*/
|
|
protected $config;
|
|
|
|
/**
|
|
* The title resolver.
|
|
*
|
|
* @var \Drupal\Core\Controller\TitleResolverInterface
|
|
*/
|
|
protected $titleResolver;
|
|
|
|
/**
|
|
* The current user object.
|
|
*
|
|
* @var \Drupal\Core\Session\AccountInterface
|
|
*/
|
|
protected $currentUser;
|
|
|
|
/**
|
|
* Constructs the PathBasedBreadcrumbBuilder.
|
|
*
|
|
* @param \Drupal\Core\Routing\RequestContext $context
|
|
* The router request context.
|
|
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
|
* The menu link access service.
|
|
* @param \Symfony\Component\Routing\Matcher\RequestMatcherInterface $router
|
|
* The dynamic router service.
|
|
* @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor
|
|
* The inbound path processor.
|
|
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
|
* The config factory service.
|
|
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
|
|
* The title resolver service.
|
|
* @param \Drupal\Core\Session\AccountInterface $current_user
|
|
* The current user object.
|
|
* @param \Drupal\Core\Path\CurrentPathStack $current_path
|
|
* The current path.
|
|
*/
|
|
public function __construct(RequestContext $context, AccessManagerInterface $access_manager, RequestMatcherInterface $router, InboundPathProcessorInterface $path_processor, ConfigFactoryInterface $config_factory, TitleResolverInterface $title_resolver, AccountInterface $current_user, CurrentPathStack $current_path) {
|
|
$this->context = $context;
|
|
$this->accessManager = $access_manager;
|
|
$this->router = $router;
|
|
$this->pathProcessor = $path_processor;
|
|
$this->config = $config_factory->get('system.site');
|
|
$this->titleResolver = $title_resolver;
|
|
$this->currentUser = $current_user;
|
|
$this->currentPath = $current_path;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function applies(RouteMatchInterface $route_match) {
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function build(RouteMatchInterface $route_match) {
|
|
$breadcrumb = new Breadcrumb();
|
|
$links = array();
|
|
|
|
// General path-based breadcrumbs. Use the actual request path, prior to
|
|
// resolving path aliases, so the breadcrumb can be defined by simply
|
|
// creating a hierarchy of path aliases.
|
|
$path = trim($this->context->getPathInfo(), '/');
|
|
$path_elements = explode('/', $path);
|
|
$exclude = array();
|
|
// Don't show a link to the front-page path.
|
|
$front = $this->config->get('page.front');
|
|
$exclude[$front] = TRUE;
|
|
// /user is just a redirect, so skip it.
|
|
// @todo Find a better way to deal with /user.
|
|
$exclude['/user'] = TRUE;
|
|
// Add the url.path.parent cache context. This code ignores the last path
|
|
// part so the result only depends on the path parents.
|
|
$breadcrumb->addCacheContexts(['url.path.parent']);
|
|
while (count($path_elements) > 1) {
|
|
array_pop($path_elements);
|
|
// Copy the path elements for up-casting.
|
|
$route_request = $this->getRequestForPath('/' . implode('/', $path_elements), $exclude);
|
|
if ($route_request) {
|
|
$route_match = RouteMatch::createFromRequest($route_request);
|
|
$access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE);
|
|
// The set of breadcrumb links depends on the access result, so merge
|
|
// the access result's cacheability metadata.
|
|
$breadcrumb = $breadcrumb->addCacheableDependency($access);
|
|
if ($access->isAllowed()) {
|
|
$title = $this->titleResolver->getTitle($route_request, $route_match->getRouteObject());
|
|
if (!isset($title)) {
|
|
// Fallback to using the raw path component as the title if the
|
|
// route is missing a _title or _title_callback attribute.
|
|
$title = str_replace(array('-', '_'), ' ', Unicode::ucfirst(end($path_elements)));
|
|
}
|
|
$url = Url::fromRouteMatch($route_match);
|
|
$links[] = new Link($title, $url);
|
|
}
|
|
}
|
|
|
|
}
|
|
if ($path && '/' . $path != $front) {
|
|
// Add the Home link, except for the front page.
|
|
$links[] = Link::createFromRoute($this->t('Home'), '<front>');
|
|
}
|
|
|
|
return $breadcrumb->setLinks(array_reverse($links));
|
|
}
|
|
|
|
/**
|
|
* Matches a path in the router.
|
|
*
|
|
* @param string $path
|
|
* The request path with a leading slash.
|
|
* @param array $exclude
|
|
* An array of paths or system paths to skip.
|
|
*
|
|
* @return \Symfony\Component\HttpFoundation\Request
|
|
* A populated request object or NULL if the path couldn't be matched.
|
|
*/
|
|
protected function getRequestForPath($path, array $exclude) {
|
|
if (!empty($exclude[$path])) {
|
|
return NULL;
|
|
}
|
|
// @todo Use the RequestHelper once https://www.drupal.org/node/2090293 is
|
|
// fixed.
|
|
$request = Request::create($path);
|
|
// Performance optimization: set a short accept header to reduce overhead in
|
|
// AcceptHeaderMatcher when matching the request.
|
|
$request->headers->set('Accept', 'text/html');
|
|
// Find the system path by resolving aliases, language prefix, etc.
|
|
$processed = $this->pathProcessor->processInbound($path, $request);
|
|
if (empty($processed) || !empty($exclude[$processed])) {
|
|
// This resolves to the front page, which we already add.
|
|
return NULL;
|
|
}
|
|
$this->currentPath->setPath($processed, $request);
|
|
// Attempt to match this path to provide a fully built request.
|
|
try {
|
|
$request->attributes->add($this->router->matchRequest($request));
|
|
return $request;
|
|
}
|
|
catch (ParamNotConvertedException $e) {
|
|
return NULL;
|
|
}
|
|
catch (ResourceNotFoundException $e) {
|
|
return NULL;
|
|
}
|
|
catch (MethodNotAllowedException $e) {
|
|
return NULL;
|
|
}
|
|
catch (AccessDeniedHttpException $e) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
}
|