PathBasedBreadcrumbBuilder.php

Same filename in other branches
  1. 8.9.x core/modules/system/src/PathBasedBreadcrumbBuilder.php
  2. 10 core/modules/system/src/PathBasedBreadcrumbBuilder.php
  3. 11.x core/modules/system/src/PathBasedBreadcrumbBuilder.php

Namespace

Drupal\system

File

core/modules/system/src/PathBasedBreadcrumbBuilder.php

View source
<?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\Path\PathMatcherInterface;
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;

/**
 * Defines a class to build path-based breadcrumbs.
 *
 * @see \Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface
 */
class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
    use StringTranslationTrait;
    
    /**
     * The router request context.
     *
     * @var \Drupal\Core\Routing\RequestContext
     */
    protected $context;
    
    /**
     * The access check service.
     *
     * @var \Drupal\Core\Access\AccessManagerInterface
     */
    protected $accessManager;
    
    /**
     * The dynamic router service.
     *
     * @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
     */
    protected $router;
    
    /**
     * The inbound path processor.
     *
     * @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;
    
    /**
     * The current path service.
     *
     * @var \Drupal\Core\Path\CurrentPathStack
     */
    protected $currentPath;
    
    /**
     * The patch matcher service.
     *
     * @var \Drupal\Core\Path\PathMatcherInterface
     */
    protected $pathMatcher;
    
    /**
     * Constructs the PathBasedBreadcrumbBuilder.
     *
     * @param \Drupal\Core\Routing\RequestContext $context
     *   The router request context.
     * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
     *   The access check 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.
     * @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
     *   The path matcher service.
     */
    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, PathMatcherInterface $path_matcher = NULL) {
        $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;
        $this->pathMatcher = $path_matcher ?: \Drupal::service('path.matcher');
    }
    
    /**
     * {@inheritdoc}
     */
    public function applies(RouteMatchInterface $route_match) {
        return TRUE;
    }
    
    /**
     * {@inheritdoc}
     */
    public function build(RouteMatchInterface $route_match) {
        $breadcrumb = new Breadcrumb();
        $links = [];
        // 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',
            'url.path.is_front',
        ]);
        // Do not display a breadcrumb on the frontpage.
        if ($this->pathMatcher
            ->isFrontPage()) {
            return $breadcrumb;
        }
        // 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 = [];
        // 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;
        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([
                            '-',
                            '_',
                        ], ' ', Unicode::ucfirst(end($path_elements)));
                    }
                    $url = Url::fromRouteMatch($route_match);
                    $links[] = new Link($title, $url);
                }
            }
        }
        // Add the Home link.
        $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;
        }
        $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;
        }
    }

}

Classes

Title Deprecated Summary
PathBasedBreadcrumbBuilder Defines a class to build path-based breadcrumbs.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.