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