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