annotate core/modules/system/src/PathBasedBreadcrumbBuilder.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\system;
Chris@0 4
Chris@0 5 use Drupal\Component\Utility\Unicode;
Chris@0 6 use Drupal\Core\Access\AccessManagerInterface;
Chris@0 7 use Drupal\Core\Breadcrumb\Breadcrumb;
Chris@0 8 use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
Chris@0 9 use Drupal\Core\Config\ConfigFactoryInterface;
Chris@0 10 use Drupal\Core\Controller\TitleResolverInterface;
Chris@0 11 use Drupal\Core\Link;
Chris@0 12 use Drupal\Core\ParamConverter\ParamNotConvertedException;
Chris@0 13 use Drupal\Core\Path\CurrentPathStack;
Chris@0 14 use Drupal\Core\Path\PathMatcherInterface;
Chris@0 15 use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
Chris@0 16 use Drupal\Core\Routing\RequestContext;
Chris@0 17 use Drupal\Core\Routing\RouteMatch;
Chris@0 18 use Drupal\Core\Routing\RouteMatchInterface;
Chris@0 19 use Drupal\Core\Session\AccountInterface;
Chris@0 20 use Drupal\Core\StringTranslation\StringTranslationTrait;
Chris@0 21 use Drupal\Core\Url;
Chris@0 22 use Symfony\Component\HttpFoundation\Request;
Chris@0 23 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
Chris@0 24 use Symfony\Component\Routing\Exception\MethodNotAllowedException;
Chris@0 25 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
Chris@0 26 use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
Chris@0 27
Chris@0 28 /**
Chris@0 29 * Class to define the menu_link breadcrumb builder.
Chris@0 30 */
Chris@0 31 class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
Chris@0 32 use StringTranslationTrait;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * The router request context.
Chris@0 36 *
Chris@0 37 * @var \Drupal\Core\Routing\RequestContext
Chris@0 38 */
Chris@0 39 protected $context;
Chris@0 40
Chris@0 41 /**
Chris@0 42 * The menu link access service.
Chris@0 43 *
Chris@0 44 * @var \Drupal\Core\Access\AccessManagerInterface
Chris@0 45 */
Chris@0 46 protected $accessManager;
Chris@0 47
Chris@0 48 /**
Chris@0 49 * The dynamic router service.
Chris@0 50 *
Chris@0 51 * @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
Chris@0 52 */
Chris@0 53 protected $router;
Chris@0 54
Chris@0 55 /**
Chris@0 56 * The inbound path processor.
Chris@0 57 *
Chris@0 58 * @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
Chris@0 59 */
Chris@0 60 protected $pathProcessor;
Chris@0 61
Chris@0 62 /**
Chris@0 63 * Site config object.
Chris@0 64 *
Chris@0 65 * @var \Drupal\Core\Config\Config
Chris@0 66 */
Chris@0 67 protected $config;
Chris@0 68
Chris@0 69 /**
Chris@0 70 * The title resolver.
Chris@0 71 *
Chris@0 72 * @var \Drupal\Core\Controller\TitleResolverInterface
Chris@0 73 */
Chris@0 74 protected $titleResolver;
Chris@0 75
Chris@0 76 /**
Chris@0 77 * The current user object.
Chris@0 78 *
Chris@0 79 * @var \Drupal\Core\Session\AccountInterface
Chris@0 80 */
Chris@0 81 protected $currentUser;
Chris@0 82
Chris@0 83 /**
Chris@0 84 * The current path service.
Chris@0 85 *
Chris@0 86 * @var \Drupal\Core\Path\CurrentPathStack
Chris@0 87 */
Chris@0 88 protected $currentPath;
Chris@0 89
Chris@0 90 /**
Chris@0 91 * The patch matcher service.
Chris@0 92 *
Chris@0 93 * @var \Drupal\Core\Path\PathMatcherInterface
Chris@0 94 */
Chris@0 95 protected $pathMatcher;
Chris@0 96
Chris@0 97 /**
Chris@0 98 * Constructs the PathBasedBreadcrumbBuilder.
Chris@0 99 *
Chris@0 100 * @param \Drupal\Core\Routing\RequestContext $context
Chris@0 101 * The router request context.
Chris@0 102 * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
Chris@0 103 * The menu link access service.
Chris@0 104 * @param \Symfony\Component\Routing\Matcher\RequestMatcherInterface $router
Chris@0 105 * The dynamic router service.
Chris@0 106 * @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor
Chris@0 107 * The inbound path processor.
Chris@0 108 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
Chris@0 109 * The config factory service.
Chris@0 110 * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
Chris@0 111 * The title resolver service.
Chris@0 112 * @param \Drupal\Core\Session\AccountInterface $current_user
Chris@0 113 * The current user object.
Chris@0 114 * @param \Drupal\Core\Path\CurrentPathStack $current_path
Chris@0 115 * The current path.
Chris@0 116 * @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
Chris@0 117 * The path matcher service.
Chris@0 118 */
Chris@0 119 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) {
Chris@0 120 $this->context = $context;
Chris@0 121 $this->accessManager = $access_manager;
Chris@0 122 $this->router = $router;
Chris@0 123 $this->pathProcessor = $path_processor;
Chris@0 124 $this->config = $config_factory->get('system.site');
Chris@0 125 $this->titleResolver = $title_resolver;
Chris@0 126 $this->currentUser = $current_user;
Chris@0 127 $this->currentPath = $current_path;
Chris@0 128 $this->pathMatcher = $path_matcher ?: \Drupal::service('path.matcher');
Chris@0 129 }
Chris@0 130
Chris@0 131 /**
Chris@0 132 * {@inheritdoc}
Chris@0 133 */
Chris@0 134 public function applies(RouteMatchInterface $route_match) {
Chris@0 135 return TRUE;
Chris@0 136 }
Chris@0 137
Chris@0 138 /**
Chris@0 139 * {@inheritdoc}
Chris@0 140 */
Chris@0 141 public function build(RouteMatchInterface $route_match) {
Chris@0 142 $breadcrumb = new Breadcrumb();
Chris@0 143 $links = [];
Chris@0 144
Chris@0 145 // Add the url.path.parent cache context. This code ignores the last path
Chris@0 146 // part so the result only depends on the path parents.
Chris@17 147 $breadcrumb->addCacheContexts(['url.path.parent', 'url.path.is_front']);
Chris@0 148
Chris@0 149 // Do not display a breadcrumb on the frontpage.
Chris@0 150 if ($this->pathMatcher->isFrontPage()) {
Chris@0 151 return $breadcrumb;
Chris@0 152 }
Chris@0 153
Chris@0 154 // General path-based breadcrumbs. Use the actual request path, prior to
Chris@0 155 // resolving path aliases, so the breadcrumb can be defined by simply
Chris@0 156 // creating a hierarchy of path aliases.
Chris@0 157 $path = trim($this->context->getPathInfo(), '/');
Chris@0 158 $path_elements = explode('/', $path);
Chris@0 159 $exclude = [];
Chris@0 160 // Don't show a link to the front-page path.
Chris@0 161 $front = $this->config->get('page.front');
Chris@0 162 $exclude[$front] = TRUE;
Chris@0 163 // /user is just a redirect, so skip it.
Chris@0 164 // @todo Find a better way to deal with /user.
Chris@0 165 $exclude['/user'] = TRUE;
Chris@0 166 while (count($path_elements) > 1) {
Chris@0 167 array_pop($path_elements);
Chris@0 168 // Copy the path elements for up-casting.
Chris@0 169 $route_request = $this->getRequestForPath('/' . implode('/', $path_elements), $exclude);
Chris@0 170 if ($route_request) {
Chris@0 171 $route_match = RouteMatch::createFromRequest($route_request);
Chris@0 172 $access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE);
Chris@0 173 // The set of breadcrumb links depends on the access result, so merge
Chris@0 174 // the access result's cacheability metadata.
Chris@0 175 $breadcrumb = $breadcrumb->addCacheableDependency($access);
Chris@0 176 if ($access->isAllowed()) {
Chris@0 177 $title = $this->titleResolver->getTitle($route_request, $route_match->getRouteObject());
Chris@0 178 if (!isset($title)) {
Chris@0 179 // Fallback to using the raw path component as the title if the
Chris@0 180 // route is missing a _title or _title_callback attribute.
Chris@0 181 $title = str_replace(['-', '_'], ' ', Unicode::ucfirst(end($path_elements)));
Chris@0 182 }
Chris@0 183 $url = Url::fromRouteMatch($route_match);
Chris@0 184 $links[] = new Link($title, $url);
Chris@0 185 }
Chris@0 186 }
Chris@0 187 }
Chris@0 188
Chris@0 189 // Add the Home link.
Chris@0 190 $links[] = Link::createFromRoute($this->t('Home'), '<front>');
Chris@0 191
Chris@0 192 return $breadcrumb->setLinks(array_reverse($links));
Chris@0 193 }
Chris@0 194
Chris@0 195 /**
Chris@0 196 * Matches a path in the router.
Chris@0 197 *
Chris@0 198 * @param string $path
Chris@0 199 * The request path with a leading slash.
Chris@0 200 * @param array $exclude
Chris@0 201 * An array of paths or system paths to skip.
Chris@0 202 *
Chris@0 203 * @return \Symfony\Component\HttpFoundation\Request
Chris@0 204 * A populated request object or NULL if the path couldn't be matched.
Chris@0 205 */
Chris@0 206 protected function getRequestForPath($path, array $exclude) {
Chris@0 207 if (!empty($exclude[$path])) {
Chris@0 208 return NULL;
Chris@0 209 }
Chris@0 210 // @todo Use the RequestHelper once https://www.drupal.org/node/2090293 is
Chris@0 211 // fixed.
Chris@0 212 $request = Request::create($path);
Chris@0 213 // Performance optimization: set a short accept header to reduce overhead in
Chris@0 214 // AcceptHeaderMatcher when matching the request.
Chris@0 215 $request->headers->set('Accept', 'text/html');
Chris@0 216 // Find the system path by resolving aliases, language prefix, etc.
Chris@0 217 $processed = $this->pathProcessor->processInbound($path, $request);
Chris@0 218 if (empty($processed) || !empty($exclude[$processed])) {
Chris@0 219 // This resolves to the front page, which we already add.
Chris@0 220 return NULL;
Chris@0 221 }
Chris@0 222 $this->currentPath->setPath($processed, $request);
Chris@0 223 // Attempt to match this path to provide a fully built request.
Chris@0 224 try {
Chris@0 225 $request->attributes->add($this->router->matchRequest($request));
Chris@0 226 return $request;
Chris@0 227 }
Chris@0 228 catch (ParamNotConvertedException $e) {
Chris@0 229 return NULL;
Chris@0 230 }
Chris@0 231 catch (ResourceNotFoundException $e) {
Chris@0 232 return NULL;
Chris@0 233 }
Chris@0 234 catch (MethodNotAllowedException $e) {
Chris@0 235 return NULL;
Chris@0 236 }
Chris@0 237 catch (AccessDeniedHttpException $e) {
Chris@0 238 return NULL;
Chris@0 239 }
Chris@0 240 }
Chris@0 241
Chris@0 242 }