annotate core/lib/Drupal/Core/Menu/MenuLinkTree.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 4c8ae668cc8c
children
rev   line source
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 }