annotate core/modules/system/src/Plugin/Block/SystemMenuBlock.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
rev   line source
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 }