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