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