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