Version 1
[yaffs-website] / web / core / modules / system / src / Plugin / Block / SystemMenuBlock.php
1 <?php
2
3 namespace Drupal\system\Plugin\Block;
4
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;
12
13 /**
14  * Provides a generic Menu block.
15  *
16  * @Block(
17  *   id = "system_menu_block",
18  *   admin_label = @Translation("Menu"),
19  *   category = @Translation("Menus"),
20  *   deriver = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
21  * )
22  */
23 class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
24
25   /**
26    * The menu link tree service.
27    *
28    * @var \Drupal\Core\Menu\MenuLinkTreeInterface
29    */
30   protected $menuTree;
31
32   /**
33    * The active menu trail service.
34    *
35    * @var \Drupal\Core\Menu\MenuActiveTrailInterface
36    */
37   protected $menuActiveTrail;
38
39   /**
40    * Constructs a new SystemMenuBlock.
41    *
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.
52    */
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;
57   }
58
59   /**
60    * {@inheritdoc}
61    */
62   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
63     return new static(
64       $configuration,
65       $plugin_id,
66       $plugin_definition,
67       $container->get('menu.link_tree'),
68       $container->get('menu.active_trail')
69     );
70   }
71
72   /**
73    * {@inheritdoc}
74    */
75   public function blockForm($form, FormStateInterface $form_state) {
76     $config = $this->configuration;
77
78     $defaults = $this->defaultConfiguration();
79     $form['menu_levels'] = [
80       '#type' => 'details',
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']],
85     ];
86
87     $options = range(0, $this->menuTree->maxDepth());
88     unset($options[0]);
89
90     $form['menu_levels']['level'] = [
91       '#type' => 'select',
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.'),
96       '#required' => TRUE,
97     ];
98
99     $options[0] = $this->t('Unlimited');
100
101     $form['menu_levels']['depth'] = [
102       '#type' => 'select',
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.'),
107       '#required' => TRUE,
108     ];
109
110     return $form;
111   }
112
113   /**
114    * Form API callback: Processes the menu_levels field element.
115    *
116    * Adjusts the #parents of menu_levels to save its children at the top level.
117    */
118   public static function processMenuLevelParents(&$element, FormStateInterface $form_state, &$complete_form) {
119     array_pop($element['#parents']);
120     return $element;
121   }
122
123   /**
124    * {@inheritdoc}
125    */
126   public function blockSubmit($form, FormStateInterface $form_state) {
127     $this->configuration['level'] = $form_state->getValue('level');
128     $this->configuration['depth'] = $form_state->getValue('depth');
129   }
130
131   /**
132    * {@inheritdoc}
133    */
134   public function build() {
135     $menu_name = $this->getDerivativeId();
136     $parameters = $this->menuTree->getCurrentRouteMenuTreeParameters($menu_name);
137
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.
146     if ($depth > 0) {
147       $parameters->setMaxDepth(min($level + $depth - 1, $this->menuTree->maxDepth()));
148     }
149
150     $tree = $this->menuTree->load($menu_name, $parameters);
151     $manipulators = [
152       ['callable' => 'menu.default_tree_manipulators:checkAccess'],
153       ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
154     ];
155     $tree = $this->menuTree->transform($tree, $manipulators);
156     return $this->menuTree->build($tree);
157   }
158
159   /**
160    * {@inheritdoc}
161    */
162   public function defaultConfiguration() {
163     return [
164       'level' => 1,
165       'depth' => 0,
166     ];
167   }
168
169   /**
170    * {@inheritdoc}
171    */
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();
179     return $cache_tags;
180   }
181
182   /**
183    * {@inheritdoc}
184    */
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]);
194   }
195
196 }