Chris@0: menuTree = $menu_tree; Chris@18: if ($menu_active_trail === NULL) { Chris@18: @trigger_error('The menu.active_trail service must be passed to SystemMenuBlock::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2669550.', E_USER_DEPRECATED); Chris@18: $menu_active_trail = \Drupal::service('menu.active_trail'); Chris@18: } Chris@18: $this->menuActiveTrail = $menu_active_trail; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { Chris@0: return new static( Chris@0: $configuration, Chris@0: $plugin_id, Chris@0: $plugin_definition, Chris@18: $container->get('menu.link_tree'), Chris@18: $container->get('menu.active_trail') Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function blockForm($form, FormStateInterface $form_state) { Chris@0: $config = $this->configuration; Chris@0: Chris@0: $defaults = $this->defaultConfiguration(); Chris@0: $form['menu_levels'] = [ Chris@0: '#type' => 'details', Chris@0: '#title' => $this->t('Menu levels'), Chris@0: // Open if not set to defaults. Chris@0: '#open' => $defaults['level'] !== $config['level'] || $defaults['depth'] !== $config['depth'], Chris@0: '#process' => [[get_class(), 'processMenuLevelParents']], Chris@0: ]; Chris@0: Chris@0: $options = range(0, $this->menuTree->maxDepth()); Chris@0: unset($options[0]); Chris@0: Chris@0: $form['menu_levels']['level'] = [ Chris@0: '#type' => 'select', Chris@0: '#title' => $this->t('Initial visibility level'), Chris@0: '#default_value' => $config['level'], Chris@0: '#options' => $options, Chris@0: '#description' => $this->t('The menu is only visible if the menu item for the current page is at this level or below it. Use level 1 to always display this menu.'), Chris@0: '#required' => TRUE, Chris@0: ]; Chris@0: Chris@0: $options[0] = $this->t('Unlimited'); Chris@0: Chris@0: $form['menu_levels']['depth'] = [ Chris@0: '#type' => 'select', Chris@0: '#title' => $this->t('Number of levels to display'), Chris@0: '#default_value' => $config['depth'], Chris@0: '#options' => $options, Chris@0: '#description' => $this->t('This maximum number includes the initial level.'), Chris@0: '#required' => TRUE, Chris@0: ]; Chris@0: Chris@18: $form['menu_levels']['expand_all_items'] = [ Chris@18: '#type' => 'checkbox', Chris@18: '#title' => $this->t('Expand all menu items'), Chris@18: '#default_value' => !empty($config['expand_all_items']), Chris@18: '#description' => $this->t('Override the option found on each menu link used for expanding children and instead display the whole menu tree as expanded.'), Chris@18: ]; Chris@18: Chris@0: return $form; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Form API callback: Processes the menu_levels field element. Chris@0: * Chris@0: * Adjusts the #parents of menu_levels to save its children at the top level. Chris@0: */ Chris@0: public static function processMenuLevelParents(&$element, FormStateInterface $form_state, &$complete_form) { Chris@0: array_pop($element['#parents']); Chris@0: return $element; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function blockSubmit($form, FormStateInterface $form_state) { Chris@0: $this->configuration['level'] = $form_state->getValue('level'); Chris@0: $this->configuration['depth'] = $form_state->getValue('depth'); Chris@18: $this->configuration['expand_all_items'] = $form_state->getValue('expand_all_items'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function build() { Chris@0: $menu_name = $this->getDerivativeId(); Chris@18: if ($this->configuration['expand_all_items']) { Chris@18: $parameters = new MenuTreeParameters(); Chris@18: $active_trail = $this->menuActiveTrail->getActiveTrailIds($menu_name); Chris@18: $parameters->setActiveTrail($active_trail); Chris@18: } Chris@18: else { Chris@18: $parameters = $this->menuTree->getCurrentRouteMenuTreeParameters($menu_name); Chris@18: } Chris@0: Chris@0: // Adjust the menu tree parameters based on the block's configuration. Chris@0: $level = $this->configuration['level']; Chris@0: $depth = $this->configuration['depth']; Chris@0: $parameters->setMinDepth($level); Chris@0: // When the depth is configured to zero, there is no depth limit. When depth Chris@0: // is non-zero, it indicates the number of levels that must be displayed. Chris@0: // Hence this is a relative depth that we must convert to an actual Chris@0: // (absolute) depth, that may never exceed the maximum depth. Chris@0: if ($depth > 0) { Chris@0: $parameters->setMaxDepth(min($level + $depth - 1, $this->menuTree->maxDepth())); Chris@0: } Chris@0: Chris@0: // For menu blocks with start level greater than 1, only show menu items Chris@0: // from the current active trail. Adjust the root according to the current Chris@0: // position in the menu in order to determine if we can show the subtree. Chris@0: if ($level > 1) { Chris@0: if (count($parameters->activeTrail) >= $level) { Chris@0: // Active trail array is child-first. Reverse it, and pull the new menu Chris@0: // root based on the parent of the configured start level. Chris@0: $menu_trail_ids = array_reverse(array_values($parameters->activeTrail)); Chris@0: $menu_root = $menu_trail_ids[$level - 1]; Chris@0: $parameters->setRoot($menu_root)->setMinDepth(1); Chris@0: if ($depth > 0) { Chris@0: $parameters->setMaxDepth(min($level - 1 + $depth - 1, $this->menuTree->maxDepth())); Chris@0: } Chris@0: } Chris@0: else { Chris@0: return []; Chris@0: } Chris@0: } Chris@0: Chris@0: $tree = $this->menuTree->load($menu_name, $parameters); Chris@0: $manipulators = [ Chris@0: ['callable' => 'menu.default_tree_manipulators:checkAccess'], Chris@0: ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'], Chris@0: ]; Chris@0: $tree = $this->menuTree->transform($tree, $manipulators); Chris@0: return $this->menuTree->build($tree); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function defaultConfiguration() { Chris@0: return [ Chris@0: 'level' => 1, Chris@0: 'depth' => 0, Chris@18: 'expand_all_items' => FALSE, Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheTags() { Chris@0: // Even when the menu block renders to the empty string for a user, we want Chris@0: // the cache tag for this menu to be set: whenever the menu is changed, this Chris@0: // menu block must also be re-rendered for that user, because maybe a menu Chris@0: // link that is accessible for that user has been added. Chris@0: $cache_tags = parent::getCacheTags(); Chris@0: $cache_tags[] = 'config:system.menu.' . $this->getDerivativeId(); Chris@0: return $cache_tags; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheContexts() { Chris@0: // ::build() uses MenuLinkTreeInterface::getCurrentRouteMenuTreeParameters() Chris@0: // to generate menu tree parameters, and those take the active menu trail Chris@0: // into account. Therefore, we must vary the rendered menu by the active Chris@0: // trail of the rendered menu. Chris@0: // Additional cache contexts, e.g. those that determine link text or Chris@0: // accessibility of a menu, will be bubbled automatically. Chris@0: $menu_name = $this->getDerivativeId(); Chris@0: return Cache::mergeContexts(parent::getCacheContexts(), ['route.menu_active_trails:' . $menu_name]); Chris@0: } Chris@0: Chris@0: }