Chris@0: treeStorage = $tree_storage; Chris@0: $this->menuLinkManager = $menu_link_manager; Chris@0: $this->routeProvider = $route_provider; Chris@0: $this->menuActiveTrail = $menu_active_trail; Chris@0: $this->controllerResolver = $controller_resolver; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCurrentRouteMenuTreeParameters($menu_name) { Chris@0: $active_trail = $this->menuActiveTrail->getActiveTrailIds($menu_name); Chris@0: Chris@0: $parameters = new MenuTreeParameters(); Chris@0: $parameters->setActiveTrail($active_trail) Chris@0: // We want links in the active trail to be expanded. Chris@0: ->addExpandedParents($active_trail) Chris@0: // We marked the links in the active trail to be expanded, but we also Chris@0: // want their descendants that have the "expanded" flag enabled to be Chris@0: // expanded. Chris@0: ->addExpandedParents($this->treeStorage->getExpanded($menu_name, $active_trail)); Chris@0: Chris@0: return $parameters; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function load($menu_name, MenuTreeParameters $parameters) { Chris@0: $data = $this->treeStorage->loadTreeData($menu_name, $parameters); Chris@0: // Pre-load all the route objects in the tree for access checks. Chris@0: if ($data['route_names']) { Chris@0: $this->routeProvider->getRoutesByNames($data['route_names']); Chris@0: } Chris@0: return $this->createInstances($data['tree']); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a tree containing of MenuLinkTreeElement based upon tree data. Chris@0: * Chris@0: * This method converts the tree representation as array coming from the tree Chris@0: * storage to a tree containing a list of MenuLinkTreeElement[]. Chris@0: * Chris@0: * @param array $data_tree Chris@0: * The tree data coming from the menu tree storage. Chris@0: * Chris@0: * @return \Drupal\Core\Menu\MenuLinkTreeElement[] Chris@0: * An array containing the elements of a menu tree. Chris@0: */ Chris@0: protected function createInstances(array $data_tree) { Chris@0: $tree = []; Chris@0: foreach ($data_tree as $key => $element) { Chris@0: $subtree = $this->createInstances($element['subtree']); Chris@0: // Build a MenuLinkTreeElement out of the menu tree link definition: Chris@0: // transform the tree link definition into a link definition and store Chris@0: // tree metadata. Chris@0: $tree[$key] = new MenuLinkTreeElement( Chris@0: $this->menuLinkManager->createInstance($element['definition']['id']), Chris@0: (bool) $element['has_children'], Chris@0: (int) $element['depth'], Chris@0: (bool) $element['in_active_trail'], Chris@0: $subtree Chris@0: ); Chris@0: } Chris@0: return $tree; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function transform(array $tree, array $manipulators) { Chris@0: foreach ($manipulators as $manipulator) { Chris@0: $callable = $manipulator['callable']; Chris@0: $callable = $this->controllerResolver->getControllerFromDefinition($callable); Chris@0: // Prepare the arguments for the menu tree manipulator callable; the first Chris@0: // argument is always the menu link tree. Chris@0: if (isset($manipulator['args'])) { Chris@0: array_unshift($manipulator['args'], $tree); Chris@0: $tree = call_user_func_array($callable, $manipulator['args']); Chris@0: } Chris@0: else { Chris@0: $tree = call_user_func($callable, $tree); Chris@0: } Chris@0: } Chris@0: return $tree; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function build(array $tree) { Chris@0: $tree_access_cacheability = new CacheableMetadata(); Chris@0: $tree_link_cacheability = new CacheableMetadata(); Chris@0: $items = $this->buildItems($tree, $tree_access_cacheability, $tree_link_cacheability); Chris@0: Chris@0: $build = []; Chris@0: Chris@0: // Apply the tree-wide gathered access cacheability metadata and link Chris@0: // cacheability metadata to the render array. This ensures that the Chris@0: // rendered menu is varied by the cache contexts that the access results Chris@0: // and (dynamic) links depended upon, and invalidated by the cache tags Chris@0: // that may change the values of the access results and links. Chris@0: $tree_cacheability = $tree_access_cacheability->merge($tree_link_cacheability); Chris@0: $tree_cacheability->applyTo($build); Chris@0: Chris@0: if ($items) { Chris@0: // Make sure drupal_render() does not re-order the links. Chris@0: $build['#sorted'] = TRUE; Chris@0: // Get the menu name from the last link. Chris@0: $item = end($items); Chris@0: $link = $item['original_link']; Chris@0: $menu_name = $link->getMenuName(); Chris@0: // Add the theme wrapper for outer markup. Chris@0: // Allow menu-specific theme overrides. Chris@0: $build['#theme'] = 'menu__' . strtr($menu_name, '-', '_'); Chris@0: $build['#menu_name'] = $menu_name; Chris@0: $build['#items'] = $items; Chris@0: // Set cache tag. Chris@0: $build['#cache']['tags'][] = 'config:system.menu.' . $menu_name; Chris@0: } Chris@0: Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds the #items property for a menu tree's renderable array. Chris@0: * Chris@0: * Helper function for ::build(). Chris@0: * Chris@0: * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree Chris@0: * A data structure representing the tree, as returned from Chris@0: * MenuLinkTreeInterface::load(). Chris@0: * @param \Drupal\Core\Cache\CacheableMetadata &$tree_access_cacheability Chris@0: * Internal use only. The aggregated cacheability metadata for the access Chris@0: * results across the entire tree. Used when rendering the root level. Chris@0: * @param \Drupal\Core\Cache\CacheableMetadata &$tree_link_cacheability Chris@0: * Internal use only. The aggregated cacheability metadata for the menu Chris@0: * links across the entire tree. Used when rendering the root level. Chris@0: * Chris@0: * @return array Chris@0: * The value to use for the #items property of a renderable menu. Chris@0: * Chris@0: * @throws \DomainException Chris@0: */ Chris@0: protected function buildItems(array $tree, CacheableMetadata &$tree_access_cacheability, CacheableMetadata &$tree_link_cacheability) { Chris@0: $items = []; Chris@0: Chris@0: foreach ($tree as $data) { Chris@0: /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ Chris@0: $link = $data->link; Chris@0: // Generally we only deal with visible links, but just in case. Chris@0: if (!$link->isEnabled()) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: if ($data->access !== NULL && !$data->access instanceof AccessResultInterface) { Chris@0: throw new \DomainException('MenuLinkTreeElement::access must be either NULL or an AccessResultInterface object.'); Chris@0: } Chris@0: Chris@0: // Gather the access cacheability of every item in the menu link tree, Chris@0: // including inaccessible items. This allows us to render cache the menu Chris@0: // tree, yet still automatically vary the rendered menu by the same cache Chris@0: // contexts that the access results vary by. Chris@0: // However, if $data->access is not an AccessResultInterface object, this Chris@0: // will still render the menu link, because this method does not want to Chris@0: // require access checking to be able to render a menu tree. Chris@0: if ($data->access instanceof AccessResultInterface) { Chris@0: $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($data->access)); Chris@0: } Chris@0: Chris@0: // Gather the cacheability of every item in the menu link tree. Some links Chris@0: // may be dynamic: they may have a dynamic text (e.g. a "Hi, " link Chris@0: // text, which would vary by 'user' cache context), or a dynamic route Chris@0: // name or route parameters. Chris@0: $tree_link_cacheability = $tree_link_cacheability->merge(CacheableMetadata::createFromObject($data->link)); Chris@0: Chris@0: // Only render accessible links. Chris@0: if ($data->access instanceof AccessResultInterface && !$data->access->isAllowed()) { Chris@0: continue; Chris@0: } Chris@0: $element = []; Chris@0: Chris@0: // Set a variable for the
  • tag. Only set 'expanded' to true if the Chris@0: // link also has visible children within the current tree. Chris@0: $element['is_expanded'] = FALSE; Chris@0: $element['is_collapsed'] = FALSE; Chris@0: if ($data->hasChildren && !empty($data->subtree)) { Chris@0: $element['is_expanded'] = TRUE; Chris@0: } Chris@0: elseif ($data->hasChildren) { Chris@0: $element['is_collapsed'] = TRUE; Chris@0: } Chris@0: // Set a helper variable to indicate whether the link is in the active Chris@0: // trail. Chris@0: $element['in_active_trail'] = FALSE; Chris@0: if ($data->inActiveTrail) { Chris@0: $element['in_active_trail'] = TRUE; Chris@0: } Chris@0: Chris@0: // Note: links are rendered in the menu.html.twig template; and they Chris@0: // automatically bubble their associated cacheability metadata. Chris@0: $element['attributes'] = new Attribute(); Chris@0: $element['title'] = $link->getTitle(); Chris@0: $element['url'] = $link->getUrlObject(); Chris@0: $element['url']->setOption('set_active_class', TRUE); Chris@0: $element['below'] = $data->subtree ? $this->buildItems($data->subtree, $tree_access_cacheability, $tree_link_cacheability) : []; Chris@0: if (isset($data->options)) { Chris@0: $element['url']->setOptions(NestedArray::mergeDeep($element['url']->getOptions(), $data->options)); Chris@0: } Chris@0: $element['original_link'] = $link; Chris@0: // Index using the link's unique ID. Chris@0: $items[$link->getPluginId()] = $element; Chris@0: } Chris@0: Chris@0: return $items; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function maxDepth() { Chris@0: return $this->treeStorage->maxDepth(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getSubtreeHeight($id) { Chris@0: return $this->treeStorage->getSubtreeHeight($id); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getExpanded($menu_name, array $parents) { Chris@0: return $this->treeStorage->getExpanded($menu_name, $parents); Chris@0: } Chris@0: Chris@0: }