3 namespace Drupal\system\Plugin\Block;
5 use Drupal\Core\Block\BlockBase;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\Core\Menu\MenuActiveTrailInterface;
9 use Drupal\Core\Menu\MenuLinkTreeInterface;
10 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
14 * Provides a generic Menu block.
17 * id = "system_menu_block",
18 * admin_label = @Translation("Menu"),
19 * category = @Translation("Menus"),
20 * deriver = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
23 class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
26 * The menu link tree service.
28 * @var \Drupal\Core\Menu\MenuLinkTreeInterface
33 * The active menu trail service.
35 * @var \Drupal\Core\Menu\MenuActiveTrailInterface
37 protected $menuActiveTrail;
40 * Constructs a new SystemMenuBlock.
42 * @param array $configuration
43 * A configuration array containing information about the plugin instance.
44 * @param string $plugin_id
45 * The plugin_id for the plugin instance.
46 * @param array $plugin_definition
47 * The plugin implementation definition.
48 * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
49 * The menu tree service.
50 * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail
51 * The active menu trail service.
53 public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) {
54 parent::__construct($configuration, $plugin_id, $plugin_definition);
55 $this->menuTree = $menu_tree;
56 $this->menuActiveTrail = $menu_active_trail;
62 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
67 $container->get('menu.link_tree'),
68 $container->get('menu.active_trail')
75 public function blockForm($form, FormStateInterface $form_state) {
76 $config = $this->configuration;
78 $defaults = $this->defaultConfiguration();
79 $form['menu_levels'] = [
81 '#title' => $this->t('Menu levels'),
82 // Open if not set to defaults.
83 '#open' => $defaults['level'] !== $config['level'] || $defaults['depth'] !== $config['depth'],
84 '#process' => [[get_class(), 'processMenuLevelParents']],
87 $options = range(0, $this->menuTree->maxDepth());
90 $form['menu_levels']['level'] = [
92 '#title' => $this->t('Initial visibility level'),
93 '#default_value' => $config['level'],
94 '#options' => $options,
95 '#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.'),
99 $options[0] = $this->t('Unlimited');
101 $form['menu_levels']['depth'] = [
103 '#title' => $this->t('Number of levels to display'),
104 '#default_value' => $config['depth'],
105 '#options' => $options,
106 '#description' => $this->t('This maximum number includes the initial level.'),
114 * Form API callback: Processes the menu_levels field element.
116 * Adjusts the #parents of menu_levels to save its children at the top level.
118 public static function processMenuLevelParents(&$element, FormStateInterface $form_state, &$complete_form) {
119 array_pop($element['#parents']);
126 public function blockSubmit($form, FormStateInterface $form_state) {
127 $this->configuration['level'] = $form_state->getValue('level');
128 $this->configuration['depth'] = $form_state->getValue('depth');
134 public function build() {
135 $menu_name = $this->getDerivativeId();
136 $parameters = $this->menuTree->getCurrentRouteMenuTreeParameters($menu_name);
138 // Adjust the menu tree parameters based on the block's configuration.
139 $level = $this->configuration['level'];
140 $depth = $this->configuration['depth'];
141 $parameters->setMinDepth($level);
142 // When the depth is configured to zero, there is no depth limit. When depth
143 // is non-zero, it indicates the number of levels that must be displayed.
144 // Hence this is a relative depth that we must convert to an actual
145 // (absolute) depth, that may never exceed the maximum depth.
147 $parameters->setMaxDepth(min($level + $depth - 1, $this->menuTree->maxDepth()));
150 // For menu blocks with start level greater than 1, only show menu items
151 // from the current active trail. Adjust the root according to the current
152 // position in the menu in order to determine if we can show the subtree.
154 if (count($parameters->activeTrail) >= $level) {
155 // Active trail array is child-first. Reverse it, and pull the new menu
156 // root based on the parent of the configured start level.
157 $menu_trail_ids = array_reverse(array_values($parameters->activeTrail));
158 $menu_root = $menu_trail_ids[$level - 1];
159 $parameters->setRoot($menu_root)->setMinDepth(1);
161 $parameters->setMaxDepth(min($level - 1 + $depth - 1, $this->menuTree->maxDepth()));
169 $tree = $this->menuTree->load($menu_name, $parameters);
171 ['callable' => 'menu.default_tree_manipulators:checkAccess'],
172 ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
174 $tree = $this->menuTree->transform($tree, $manipulators);
175 return $this->menuTree->build($tree);
181 public function defaultConfiguration() {
191 public function getCacheTags() {
192 // Even when the menu block renders to the empty string for a user, we want
193 // the cache tag for this menu to be set: whenever the menu is changed, this
194 // menu block must also be re-rendered for that user, because maybe a menu
195 // link that is accessible for that user has been added.
196 $cache_tags = parent::getCacheTags();
197 $cache_tags[] = 'config:system.menu.' . $this->getDerivativeId();
204 public function getCacheContexts() {
205 // ::build() uses MenuLinkTreeInterface::getCurrentRouteMenuTreeParameters()
206 // to generate menu tree parameters, and those take the active menu trail
207 // into account. Therefore, we must vary the rendered menu by the active
208 // trail of the rendered menu.
209 // Additional cache contexts, e.g. those that determine link text or
210 // accessibility of a menu, will be bubbled automatically.
211 $menu_name = $this->getDerivativeId();
212 return Cache::mergeContexts(parent::getCacheContexts(), ['route.menu_active_trails:' . $menu_name]);