Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\system\Plugin\Block;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Core\Block\BlockBase;
|
Chris@0
|
6 use Drupal\Core\Cache\Cache;
|
Chris@0
|
7 use Drupal\Core\Form\FormStateInterface;
|
Chris@0
|
8 use Drupal\Core\Menu\MenuActiveTrailInterface;
|
Chris@0
|
9 use Drupal\Core\Menu\MenuLinkTreeInterface;
|
Chris@0
|
10 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
Chris@0
|
11 use Symfony\Component\DependencyInjection\ContainerInterface;
|
Chris@0
|
12
|
Chris@0
|
13 /**
|
Chris@0
|
14 * Provides a generic Menu block.
|
Chris@0
|
15 *
|
Chris@0
|
16 * @Block(
|
Chris@0
|
17 * id = "system_menu_block",
|
Chris@0
|
18 * admin_label = @Translation("Menu"),
|
Chris@0
|
19 * category = @Translation("Menus"),
|
Chris@0
|
20 * deriver = "Drupal\system\Plugin\Derivative\SystemMenuBlock"
|
Chris@0
|
21 * )
|
Chris@0
|
22 */
|
Chris@0
|
23 class SystemMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
Chris@0
|
24
|
Chris@0
|
25 /**
|
Chris@0
|
26 * The menu link tree service.
|
Chris@0
|
27 *
|
Chris@0
|
28 * @var \Drupal\Core\Menu\MenuLinkTreeInterface
|
Chris@0
|
29 */
|
Chris@0
|
30 protected $menuTree;
|
Chris@0
|
31
|
Chris@0
|
32 /**
|
Chris@0
|
33 * The active menu trail service.
|
Chris@0
|
34 *
|
Chris@0
|
35 * @var \Drupal\Core\Menu\MenuActiveTrailInterface
|
Chris@0
|
36 */
|
Chris@0
|
37 protected $menuActiveTrail;
|
Chris@0
|
38
|
Chris@0
|
39 /**
|
Chris@0
|
40 * Constructs a new SystemMenuBlock.
|
Chris@0
|
41 *
|
Chris@0
|
42 * @param array $configuration
|
Chris@0
|
43 * A configuration array containing information about the plugin instance.
|
Chris@0
|
44 * @param string $plugin_id
|
Chris@0
|
45 * The plugin_id for the plugin instance.
|
Chris@0
|
46 * @param array $plugin_definition
|
Chris@0
|
47 * The plugin implementation definition.
|
Chris@0
|
48 * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree
|
Chris@0
|
49 * The menu tree service.
|
Chris@0
|
50 * @param \Drupal\Core\Menu\MenuActiveTrailInterface $menu_active_trail
|
Chris@0
|
51 * The active menu trail service.
|
Chris@0
|
52 */
|
Chris@0
|
53 public function __construct(array $configuration, $plugin_id, $plugin_definition, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $menu_active_trail) {
|
Chris@0
|
54 parent::__construct($configuration, $plugin_id, $plugin_definition);
|
Chris@0
|
55 $this->menuTree = $menu_tree;
|
Chris@0
|
56 $this->menuActiveTrail = $menu_active_trail;
|
Chris@0
|
57 }
|
Chris@0
|
58
|
Chris@0
|
59 /**
|
Chris@0
|
60 * {@inheritdoc}
|
Chris@0
|
61 */
|
Chris@0
|
62 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
Chris@0
|
63 return new static(
|
Chris@0
|
64 $configuration,
|
Chris@0
|
65 $plugin_id,
|
Chris@0
|
66 $plugin_definition,
|
Chris@0
|
67 $container->get('menu.link_tree'),
|
Chris@0
|
68 $container->get('menu.active_trail')
|
Chris@0
|
69 );
|
Chris@0
|
70 }
|
Chris@0
|
71
|
Chris@0
|
72 /**
|
Chris@0
|
73 * {@inheritdoc}
|
Chris@0
|
74 */
|
Chris@0
|
75 public function blockForm($form, FormStateInterface $form_state) {
|
Chris@0
|
76 $config = $this->configuration;
|
Chris@0
|
77
|
Chris@0
|
78 $defaults = $this->defaultConfiguration();
|
Chris@0
|
79 $form['menu_levels'] = [
|
Chris@0
|
80 '#type' => 'details',
|
Chris@0
|
81 '#title' => $this->t('Menu levels'),
|
Chris@0
|
82 // Open if not set to defaults.
|
Chris@0
|
83 '#open' => $defaults['level'] !== $config['level'] || $defaults['depth'] !== $config['depth'],
|
Chris@0
|
84 '#process' => [[get_class(), 'processMenuLevelParents']],
|
Chris@0
|
85 ];
|
Chris@0
|
86
|
Chris@0
|
87 $options = range(0, $this->menuTree->maxDepth());
|
Chris@0
|
88 unset($options[0]);
|
Chris@0
|
89
|
Chris@0
|
90 $form['menu_levels']['level'] = [
|
Chris@0
|
91 '#type' => 'select',
|
Chris@0
|
92 '#title' => $this->t('Initial visibility level'),
|
Chris@0
|
93 '#default_value' => $config['level'],
|
Chris@0
|
94 '#options' => $options,
|
Chris@0
|
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.'),
|
Chris@0
|
96 '#required' => TRUE,
|
Chris@0
|
97 ];
|
Chris@0
|
98
|
Chris@0
|
99 $options[0] = $this->t('Unlimited');
|
Chris@0
|
100
|
Chris@0
|
101 $form['menu_levels']['depth'] = [
|
Chris@0
|
102 '#type' => 'select',
|
Chris@0
|
103 '#title' => $this->t('Number of levels to display'),
|
Chris@0
|
104 '#default_value' => $config['depth'],
|
Chris@0
|
105 '#options' => $options,
|
Chris@0
|
106 '#description' => $this->t('This maximum number includes the initial level.'),
|
Chris@0
|
107 '#required' => TRUE,
|
Chris@0
|
108 ];
|
Chris@0
|
109
|
Chris@0
|
110 return $form;
|
Chris@0
|
111 }
|
Chris@0
|
112
|
Chris@0
|
113 /**
|
Chris@0
|
114 * Form API callback: Processes the menu_levels field element.
|
Chris@0
|
115 *
|
Chris@0
|
116 * Adjusts the #parents of menu_levels to save its children at the top level.
|
Chris@0
|
117 */
|
Chris@0
|
118 public static function processMenuLevelParents(&$element, FormStateInterface $form_state, &$complete_form) {
|
Chris@0
|
119 array_pop($element['#parents']);
|
Chris@0
|
120 return $element;
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 /**
|
Chris@0
|
124 * {@inheritdoc}
|
Chris@0
|
125 */
|
Chris@0
|
126 public function blockSubmit($form, FormStateInterface $form_state) {
|
Chris@0
|
127 $this->configuration['level'] = $form_state->getValue('level');
|
Chris@0
|
128 $this->configuration['depth'] = $form_state->getValue('depth');
|
Chris@0
|
129 }
|
Chris@0
|
130
|
Chris@0
|
131 /**
|
Chris@0
|
132 * {@inheritdoc}
|
Chris@0
|
133 */
|
Chris@0
|
134 public function build() {
|
Chris@0
|
135 $menu_name = $this->getDerivativeId();
|
Chris@0
|
136 $parameters = $this->menuTree->getCurrentRouteMenuTreeParameters($menu_name);
|
Chris@0
|
137
|
Chris@0
|
138 // Adjust the menu tree parameters based on the block's configuration.
|
Chris@0
|
139 $level = $this->configuration['level'];
|
Chris@0
|
140 $depth = $this->configuration['depth'];
|
Chris@0
|
141 $parameters->setMinDepth($level);
|
Chris@0
|
142 // When the depth is configured to zero, there is no depth limit. When depth
|
Chris@0
|
143 // is non-zero, it indicates the number of levels that must be displayed.
|
Chris@0
|
144 // Hence this is a relative depth that we must convert to an actual
|
Chris@0
|
145 // (absolute) depth, that may never exceed the maximum depth.
|
Chris@0
|
146 if ($depth > 0) {
|
Chris@0
|
147 $parameters->setMaxDepth(min($level + $depth - 1, $this->menuTree->maxDepth()));
|
Chris@0
|
148 }
|
Chris@0
|
149
|
Chris@0
|
150 // For menu blocks with start level greater than 1, only show menu items
|
Chris@0
|
151 // from the current active trail. Adjust the root according to the current
|
Chris@0
|
152 // position in the menu in order to determine if we can show the subtree.
|
Chris@0
|
153 if ($level > 1) {
|
Chris@0
|
154 if (count($parameters->activeTrail) >= $level) {
|
Chris@0
|
155 // Active trail array is child-first. Reverse it, and pull the new menu
|
Chris@0
|
156 // root based on the parent of the configured start level.
|
Chris@0
|
157 $menu_trail_ids = array_reverse(array_values($parameters->activeTrail));
|
Chris@0
|
158 $menu_root = $menu_trail_ids[$level - 1];
|
Chris@0
|
159 $parameters->setRoot($menu_root)->setMinDepth(1);
|
Chris@0
|
160 if ($depth > 0) {
|
Chris@0
|
161 $parameters->setMaxDepth(min($level - 1 + $depth - 1, $this->menuTree->maxDepth()));
|
Chris@0
|
162 }
|
Chris@0
|
163 }
|
Chris@0
|
164 else {
|
Chris@0
|
165 return [];
|
Chris@0
|
166 }
|
Chris@0
|
167 }
|
Chris@0
|
168
|
Chris@0
|
169 $tree = $this->menuTree->load($menu_name, $parameters);
|
Chris@0
|
170 $manipulators = [
|
Chris@0
|
171 ['callable' => 'menu.default_tree_manipulators:checkAccess'],
|
Chris@0
|
172 ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
|
Chris@0
|
173 ];
|
Chris@0
|
174 $tree = $this->menuTree->transform($tree, $manipulators);
|
Chris@0
|
175 return $this->menuTree->build($tree);
|
Chris@0
|
176 }
|
Chris@0
|
177
|
Chris@0
|
178 /**
|
Chris@0
|
179 * {@inheritdoc}
|
Chris@0
|
180 */
|
Chris@0
|
181 public function defaultConfiguration() {
|
Chris@0
|
182 return [
|
Chris@0
|
183 'level' => 1,
|
Chris@0
|
184 'depth' => 0,
|
Chris@0
|
185 ];
|
Chris@0
|
186 }
|
Chris@0
|
187
|
Chris@0
|
188 /**
|
Chris@0
|
189 * {@inheritdoc}
|
Chris@0
|
190 */
|
Chris@0
|
191 public function getCacheTags() {
|
Chris@0
|
192 // Even when the menu block renders to the empty string for a user, we want
|
Chris@0
|
193 // the cache tag for this menu to be set: whenever the menu is changed, this
|
Chris@0
|
194 // menu block must also be re-rendered for that user, because maybe a menu
|
Chris@0
|
195 // link that is accessible for that user has been added.
|
Chris@0
|
196 $cache_tags = parent::getCacheTags();
|
Chris@0
|
197 $cache_tags[] = 'config:system.menu.' . $this->getDerivativeId();
|
Chris@0
|
198 return $cache_tags;
|
Chris@0
|
199 }
|
Chris@0
|
200
|
Chris@0
|
201 /**
|
Chris@0
|
202 * {@inheritdoc}
|
Chris@0
|
203 */
|
Chris@0
|
204 public function getCacheContexts() {
|
Chris@0
|
205 // ::build() uses MenuLinkTreeInterface::getCurrentRouteMenuTreeParameters()
|
Chris@0
|
206 // to generate menu tree parameters, and those take the active menu trail
|
Chris@0
|
207 // into account. Therefore, we must vary the rendered menu by the active
|
Chris@0
|
208 // trail of the rendered menu.
|
Chris@0
|
209 // Additional cache contexts, e.g. those that determine link text or
|
Chris@0
|
210 // accessibility of a menu, will be bubbled automatically.
|
Chris@0
|
211 $menu_name = $this->getDerivativeId();
|
Chris@0
|
212 return Cache::mergeContexts(parent::getCacheContexts(), ['route.menu_active_trails:' . $menu_name]);
|
Chris@0
|
213 }
|
Chris@0
|
214
|
Chris@0
|
215 }
|