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