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 }
|