| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 | <?phpnamespace 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;/** * 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 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 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.   * @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']);    // 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;    }    // @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;    }  }}
 |