Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Menu;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Utility\NestedArray;
|
Chris@0
|
6 use Drupal\Core\Access\AccessResultInterface;
|
Chris@0
|
7 use Drupal\Core\Cache\CacheableMetadata;
|
Chris@0
|
8 use Drupal\Core\Controller\ControllerResolverInterface;
|
Chris@0
|
9 use Drupal\Core\Routing\RouteProviderInterface;
|
Chris@0
|
10 use Drupal\Core\Template\Attribute;
|
Chris@0
|
11
|
Chris@0
|
12 /**
|
Chris@0
|
13 * Implements the loading, transforming and rendering of menu link trees.
|
Chris@0
|
14 */
|
Chris@0
|
15 class MenuLinkTree implements MenuLinkTreeInterface {
|
Chris@0
|
16
|
Chris@0
|
17 /**
|
Chris@0
|
18 * The menu link tree storage.
|
Chris@0
|
19 *
|
Chris@0
|
20 * @var \Drupal\Core\Menu\MenuTreeStorageInterface
|
Chris@0
|
21 */
|
Chris@0
|
22 protected $treeStorage;
|
Chris@0
|
23
|
Chris@0
|
24 /**
|
Chris@0
|
25 * The menu link plugin manager.
|
Chris@0
|
26 *
|
Chris@0
|
27 * @var \Drupal\Core\Menu\MenuLinkManagerInterface
|
Chris@0
|
28 */
|
Chris@0
|
29 protected $menuLinkManager;
|
Chris@0
|
30
|
Chris@0
|
31 /**
|
Chris@0
|
32 * The route provider to load routes by name.
|
Chris@0
|
33 *
|
Chris@0
|
34 * @var \Drupal\Core\Routing\RouteProviderInterface
|
Chris@0
|
35 */
|
Chris@0
|
36 protected $routeProvider;
|
Chris@0
|
37
|
Chris@0
|
38 /**
|
Chris@0
|
39 * The active menu trail service.
|
Chris@0
|
40 *
|
Chris@0
|
41 * @var \Drupal\Core\Menu\MenuActiveTrailInterface
|
Chris@0
|
42 */
|
Chris@0
|
43 protected $menuActiveTrail;
|
Chris@0
|
44
|
Chris@0
|
45 /**
|
Chris@0
|
46 * The controller resolver.
|
Chris@0
|
47 *
|
Chris@0
|
48 * @var \Drupal\Core\Controller\ControllerResolverInterface
|
Chris@0
|
49 */
|
Chris@0
|
50 protected $controllerResolver;
|
Chris@0
|
51
|
Chris@0
|
52 /**
|
Chris@0
|
53 * Constructs a \Drupal\Core\Menu\MenuLinkTree object.
|
Chris@0
|
54 *
|
Chris@0
|
55 * @param \Drupal\Core\Menu\MenuTreeStorageInterface $tree_storage
|
Chris@0
|
56 * The menu link tree storage.
|
Chris@0
|
57 * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
|
Chris@0
|
58 * The menu link plugin manager.
|
Chris@0
|
59 * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
Chris@0
|
60 * The route provider to load routes by name.
|
Chris@0
|
61 * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail
|
Chris@0
|
62 * The active menu trail service.
|
Chris@0
|
63 * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
Chris@0
|
64 * The controller resolver.
|
Chris@0
|
65 */
|
Chris@0
|
66 public function __construct(MenuTreeStorageInterface $tree_storage, MenuLinkManagerInterface $menu_link_manager, RouteProviderInterface $route_provider, MenuActiveTrailInterface $menu_active_trail, ControllerResolverInterface $controller_resolver) {
|
Chris@0
|
67 $this->treeStorage = $tree_storage;
|
Chris@0
|
68 $this->menuLinkManager = $menu_link_manager;
|
Chris@0
|
69 $this->routeProvider = $route_provider;
|
Chris@0
|
70 $this->menuActiveTrail = $menu_active_trail;
|
Chris@0
|
71 $this->controllerResolver = $controller_resolver;
|
Chris@0
|
72 }
|
Chris@0
|
73
|
Chris@0
|
74 /**
|
Chris@0
|
75 * {@inheritdoc}
|
Chris@0
|
76 */
|
Chris@0
|
77 public function getCurrentRouteMenuTreeParameters($menu_name) {
|
Chris@0
|
78 $active_trail = $this->menuActiveTrail->getActiveTrailIds($menu_name);
|
Chris@0
|
79
|
Chris@0
|
80 $parameters = new MenuTreeParameters();
|
Chris@0
|
81 $parameters->setActiveTrail($active_trail)
|
Chris@0
|
82 // We want links in the active trail to be expanded.
|
Chris@0
|
83 ->addExpandedParents($active_trail)
|
Chris@0
|
84 // We marked the links in the active trail to be expanded, but we also
|
Chris@0
|
85 // want their descendants that have the "expanded" flag enabled to be
|
Chris@0
|
86 // expanded.
|
Chris@0
|
87 ->addExpandedParents($this->treeStorage->getExpanded($menu_name, $active_trail));
|
Chris@0
|
88
|
Chris@0
|
89 return $parameters;
|
Chris@0
|
90 }
|
Chris@0
|
91
|
Chris@0
|
92 /**
|
Chris@0
|
93 * {@inheritdoc}
|
Chris@0
|
94 */
|
Chris@0
|
95 public function load($menu_name, MenuTreeParameters $parameters) {
|
Chris@0
|
96 $data = $this->treeStorage->loadTreeData($menu_name, $parameters);
|
Chris@0
|
97 // Pre-load all the route objects in the tree for access checks.
|
Chris@0
|
98 if ($data['route_names']) {
|
Chris@0
|
99 $this->routeProvider->getRoutesByNames($data['route_names']);
|
Chris@0
|
100 }
|
Chris@0
|
101 return $this->createInstances($data['tree']);
|
Chris@0
|
102 }
|
Chris@0
|
103
|
Chris@0
|
104 /**
|
Chris@0
|
105 * Returns a tree containing of MenuLinkTreeElement based upon tree data.
|
Chris@0
|
106 *
|
Chris@0
|
107 * This method converts the tree representation as array coming from the tree
|
Chris@0
|
108 * storage to a tree containing a list of MenuLinkTreeElement[].
|
Chris@0
|
109 *
|
Chris@0
|
110 * @param array $data_tree
|
Chris@0
|
111 * The tree data coming from the menu tree storage.
|
Chris@0
|
112 *
|
Chris@0
|
113 * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
Chris@0
|
114 * An array containing the elements of a menu tree.
|
Chris@0
|
115 */
|
Chris@0
|
116 protected function createInstances(array $data_tree) {
|
Chris@0
|
117 $tree = [];
|
Chris@0
|
118 foreach ($data_tree as $key => $element) {
|
Chris@0
|
119 $subtree = $this->createInstances($element['subtree']);
|
Chris@0
|
120 // Build a MenuLinkTreeElement out of the menu tree link definition:
|
Chris@0
|
121 // transform the tree link definition into a link definition and store
|
Chris@0
|
122 // tree metadata.
|
Chris@0
|
123 $tree[$key] = new MenuLinkTreeElement(
|
Chris@0
|
124 $this->menuLinkManager->createInstance($element['definition']['id']),
|
Chris@0
|
125 (bool) $element['has_children'],
|
Chris@0
|
126 (int) $element['depth'],
|
Chris@0
|
127 (bool) $element['in_active_trail'],
|
Chris@0
|
128 $subtree
|
Chris@0
|
129 );
|
Chris@0
|
130 }
|
Chris@0
|
131 return $tree;
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 /**
|
Chris@0
|
135 * {@inheritdoc}
|
Chris@0
|
136 */
|
Chris@0
|
137 public function transform(array $tree, array $manipulators) {
|
Chris@0
|
138 foreach ($manipulators as $manipulator) {
|
Chris@0
|
139 $callable = $manipulator['callable'];
|
Chris@0
|
140 $callable = $this->controllerResolver->getControllerFromDefinition($callable);
|
Chris@0
|
141 // Prepare the arguments for the menu tree manipulator callable; the first
|
Chris@0
|
142 // argument is always the menu link tree.
|
Chris@0
|
143 if (isset($manipulator['args'])) {
|
Chris@0
|
144 array_unshift($manipulator['args'], $tree);
|
Chris@0
|
145 $tree = call_user_func_array($callable, $manipulator['args']);
|
Chris@0
|
146 }
|
Chris@0
|
147 else {
|
Chris@0
|
148 $tree = call_user_func($callable, $tree);
|
Chris@0
|
149 }
|
Chris@0
|
150 }
|
Chris@0
|
151 return $tree;
|
Chris@0
|
152 }
|
Chris@0
|
153
|
Chris@0
|
154 /**
|
Chris@0
|
155 * {@inheritdoc}
|
Chris@0
|
156 */
|
Chris@0
|
157 public function build(array $tree) {
|
Chris@0
|
158 $tree_access_cacheability = new CacheableMetadata();
|
Chris@0
|
159 $tree_link_cacheability = new CacheableMetadata();
|
Chris@0
|
160 $items = $this->buildItems($tree, $tree_access_cacheability, $tree_link_cacheability);
|
Chris@0
|
161
|
Chris@0
|
162 $build = [];
|
Chris@0
|
163
|
Chris@0
|
164 // Apply the tree-wide gathered access cacheability metadata and link
|
Chris@0
|
165 // cacheability metadata to the render array. This ensures that the
|
Chris@0
|
166 // rendered menu is varied by the cache contexts that the access results
|
Chris@0
|
167 // and (dynamic) links depended upon, and invalidated by the cache tags
|
Chris@0
|
168 // that may change the values of the access results and links.
|
Chris@0
|
169 $tree_cacheability = $tree_access_cacheability->merge($tree_link_cacheability);
|
Chris@0
|
170 $tree_cacheability->applyTo($build);
|
Chris@0
|
171
|
Chris@0
|
172 if ($items) {
|
Chris@0
|
173 // Make sure drupal_render() does not re-order the links.
|
Chris@0
|
174 $build['#sorted'] = TRUE;
|
Chris@0
|
175 // Get the menu name from the last link.
|
Chris@0
|
176 $item = end($items);
|
Chris@0
|
177 $link = $item['original_link'];
|
Chris@0
|
178 $menu_name = $link->getMenuName();
|
Chris@0
|
179 // Add the theme wrapper for outer markup.
|
Chris@0
|
180 // Allow menu-specific theme overrides.
|
Chris@0
|
181 $build['#theme'] = 'menu__' . strtr($menu_name, '-', '_');
|
Chris@0
|
182 $build['#menu_name'] = $menu_name;
|
Chris@0
|
183 $build['#items'] = $items;
|
Chris@0
|
184 // Set cache tag.
|
Chris@0
|
185 $build['#cache']['tags'][] = 'config:system.menu.' . $menu_name;
|
Chris@0
|
186 }
|
Chris@0
|
187
|
Chris@0
|
188 return $build;
|
Chris@0
|
189 }
|
Chris@0
|
190
|
Chris@0
|
191 /**
|
Chris@0
|
192 * Builds the #items property for a menu tree's renderable array.
|
Chris@0
|
193 *
|
Chris@0
|
194 * Helper function for ::build().
|
Chris@0
|
195 *
|
Chris@0
|
196 * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
|
Chris@0
|
197 * A data structure representing the tree, as returned from
|
Chris@0
|
198 * MenuLinkTreeInterface::load().
|
Chris@0
|
199 * @param \Drupal\Core\Cache\CacheableMetadata &$tree_access_cacheability
|
Chris@0
|
200 * Internal use only. The aggregated cacheability metadata for the access
|
Chris@0
|
201 * results across the entire tree. Used when rendering the root level.
|
Chris@0
|
202 * @param \Drupal\Core\Cache\CacheableMetadata &$tree_link_cacheability
|
Chris@0
|
203 * Internal use only. The aggregated cacheability metadata for the menu
|
Chris@0
|
204 * links across the entire tree. Used when rendering the root level.
|
Chris@0
|
205 *
|
Chris@0
|
206 * @return array
|
Chris@0
|
207 * The value to use for the #items property of a renderable menu.
|
Chris@0
|
208 *
|
Chris@0
|
209 * @throws \DomainException
|
Chris@0
|
210 */
|
Chris@0
|
211 protected function buildItems(array $tree, CacheableMetadata &$tree_access_cacheability, CacheableMetadata &$tree_link_cacheability) {
|
Chris@0
|
212 $items = [];
|
Chris@0
|
213
|
Chris@0
|
214 foreach ($tree as $data) {
|
Chris@0
|
215 /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
|
Chris@0
|
216 $link = $data->link;
|
Chris@0
|
217 // Generally we only deal with visible links, but just in case.
|
Chris@0
|
218 if (!$link->isEnabled()) {
|
Chris@0
|
219 continue;
|
Chris@0
|
220 }
|
Chris@0
|
221
|
Chris@0
|
222 if ($data->access !== NULL && !$data->access instanceof AccessResultInterface) {
|
Chris@0
|
223 throw new \DomainException('MenuLinkTreeElement::access must be either NULL or an AccessResultInterface object.');
|
Chris@0
|
224 }
|
Chris@0
|
225
|
Chris@0
|
226 // Gather the access cacheability of every item in the menu link tree,
|
Chris@0
|
227 // including inaccessible items. This allows us to render cache the menu
|
Chris@0
|
228 // tree, yet still automatically vary the rendered menu by the same cache
|
Chris@0
|
229 // contexts that the access results vary by.
|
Chris@0
|
230 // However, if $data->access is not an AccessResultInterface object, this
|
Chris@0
|
231 // will still render the menu link, because this method does not want to
|
Chris@0
|
232 // require access checking to be able to render a menu tree.
|
Chris@0
|
233 if ($data->access instanceof AccessResultInterface) {
|
Chris@0
|
234 $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($data->access));
|
Chris@0
|
235 }
|
Chris@0
|
236
|
Chris@0
|
237 // Gather the cacheability of every item in the menu link tree. Some links
|
Chris@0
|
238 // may be dynamic: they may have a dynamic text (e.g. a "Hi, <user>" link
|
Chris@0
|
239 // text, which would vary by 'user' cache context), or a dynamic route
|
Chris@0
|
240 // name or route parameters.
|
Chris@0
|
241 $tree_link_cacheability = $tree_link_cacheability->merge(CacheableMetadata::createFromObject($data->link));
|
Chris@0
|
242
|
Chris@0
|
243 // Only render accessible links.
|
Chris@0
|
244 if ($data->access instanceof AccessResultInterface && !$data->access->isAllowed()) {
|
Chris@0
|
245 continue;
|
Chris@0
|
246 }
|
Chris@0
|
247 $element = [];
|
Chris@0
|
248
|
Chris@0
|
249 // Set a variable for the <li> tag. Only set 'expanded' to true if the
|
Chris@0
|
250 // link also has visible children within the current tree.
|
Chris@0
|
251 $element['is_expanded'] = FALSE;
|
Chris@0
|
252 $element['is_collapsed'] = FALSE;
|
Chris@0
|
253 if ($data->hasChildren && !empty($data->subtree)) {
|
Chris@0
|
254 $element['is_expanded'] = TRUE;
|
Chris@0
|
255 }
|
Chris@0
|
256 elseif ($data->hasChildren) {
|
Chris@0
|
257 $element['is_collapsed'] = TRUE;
|
Chris@0
|
258 }
|
Chris@0
|
259 // Set a helper variable to indicate whether the link is in the active
|
Chris@0
|
260 // trail.
|
Chris@0
|
261 $element['in_active_trail'] = FALSE;
|
Chris@0
|
262 if ($data->inActiveTrail) {
|
Chris@0
|
263 $element['in_active_trail'] = TRUE;
|
Chris@0
|
264 }
|
Chris@0
|
265
|
Chris@0
|
266 // Note: links are rendered in the menu.html.twig template; and they
|
Chris@0
|
267 // automatically bubble their associated cacheability metadata.
|
Chris@0
|
268 $element['attributes'] = new Attribute();
|
Chris@0
|
269 $element['title'] = $link->getTitle();
|
Chris@0
|
270 $element['url'] = $link->getUrlObject();
|
Chris@0
|
271 $element['url']->setOption('set_active_class', TRUE);
|
Chris@0
|
272 $element['below'] = $data->subtree ? $this->buildItems($data->subtree, $tree_access_cacheability, $tree_link_cacheability) : [];
|
Chris@0
|
273 if (isset($data->options)) {
|
Chris@0
|
274 $element['url']->setOptions(NestedArray::mergeDeep($element['url']->getOptions(), $data->options));
|
Chris@0
|
275 }
|
Chris@0
|
276 $element['original_link'] = $link;
|
Chris@0
|
277 // Index using the link's unique ID.
|
Chris@0
|
278 $items[$link->getPluginId()] = $element;
|
Chris@0
|
279 }
|
Chris@0
|
280
|
Chris@0
|
281 return $items;
|
Chris@0
|
282 }
|
Chris@0
|
283
|
Chris@0
|
284 /**
|
Chris@0
|
285 * {@inheritdoc}
|
Chris@0
|
286 */
|
Chris@0
|
287 public function maxDepth() {
|
Chris@0
|
288 return $this->treeStorage->maxDepth();
|
Chris@0
|
289 }
|
Chris@0
|
290
|
Chris@0
|
291 /**
|
Chris@0
|
292 * {@inheritdoc}
|
Chris@0
|
293 */
|
Chris@0
|
294 public function getSubtreeHeight($id) {
|
Chris@0
|
295 return $this->treeStorage->getSubtreeHeight($id);
|
Chris@0
|
296 }
|
Chris@0
|
297
|
Chris@0
|
298 /**
|
Chris@0
|
299 * {@inheritdoc}
|
Chris@0
|
300 */
|
Chris@0
|
301 public function getExpanded($menu_name, array $parents) {
|
Chris@0
|
302 return $this->treeStorage->getExpanded($menu_name, $parents);
|
Chris@0
|
303 }
|
Chris@0
|
304
|
Chris@0
|
305 }
|