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 $tree = $this->menuTree->load($menu_name, $parameters);
152 ['callable' => 'menu.default_tree_manipulators:checkAccess'],
153 ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
155 $tree = $this->menuTree->transform($tree, $manipulators);
156 return $this->menuTree->build($tree);
162 public function defaultConfiguration() {
172 public function getCacheTags() {
173 // Even when the menu block renders to the empty string for a user, we want
174 // the cache tag for this menu to be set: whenever the menu is changed, this
175 // menu block must also be re-rendered for that user, because maybe a menu
176 // link that is accessible for that user has been added.
177 $cache_tags = parent::getCacheTags();
178 $cache_tags[] = 'config:system.menu.' . $this->getDerivativeId();
185 public function getCacheContexts() {
186 // ::build() uses MenuLinkTreeInterface::getCurrentRouteMenuTreeParameters()
187 // to generate menu tree parameters, and those take the active menu trail
188 // into account. Therefore, we must vary the rendered menu by the active
189 // trail of the rendered menu.
190 // Additional cache contexts, e.g. those that determine link text or
191 // accessibility of a menu, will be bubbled automatically.
192 $menu_name = $this->getDerivativeId();
193 return Cache::mergeContexts(parent::getCacheContexts(), ['route.menu_active_trails:' . $menu_name]);