diff --git a/composer.json b/composer.json index b49efeed06b1310b03d0909d25749ad31475c2a4..352e0f859e00363aae27a065163dc28bc7a6938f 100644 --- a/composer.json +++ b/composer.json @@ -137,7 +137,7 @@ "drupal/mathjax": "2.7", "drupal/media_entity_browser": "2.0-alpha2", "drupal/media_entity_twitter": "2.4", - "drupal/menu_block": "1.4", + "drupal/menu_block": "1.6", "drupal/menu_block_title": "1.1", "drupal/menu_breadcrumb": "1.13", "drupal/metatag": "1.13", @@ -289,8 +289,8 @@ "2712951": "https://www.drupal.org/files/issues/2019-11-27/linkit_for_link_field-2712951-140.patch" }, "drupal/menu_block": { - "2809699": "https://www.drupal.org/files/issues/2018-10-26/menu_block-label_configuration-2809699-82.patch", - "2811337": "https://www.drupal.org/files/issues/menu_block-2_level_menu_block_not_limited_to_active_parent-2811337-58.patch" + "2809699": "https://www.drupal.org/files/issues/2020-05-03/2809699-162-dynamic-block-titles.patch", + "2950943": "https://www.drupal.org/files/issues/2020-05-21/2950943-40.patch" }, "drupal/smtp": { "2781157": "https://www.drupal.org/files/issues/2018-11-07/2781157-n10.patch" diff --git a/composer.lock b/composer.lock index 564c3cb59851b43243e46117b4daf00f8d7fe183..a3f57522d5bada4d96082ad91f5796803dcc67b5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c2365ed43ba16f074102a0f462effe00", + "content-hash": "e8aea629a5741f7d1a5bca99be342bbf", "packages": [ { "name": "alchemy/zippy", @@ -5814,37 +5814,34 @@ }, { "name": "drupal/menu_block", - "version": "1.4.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/menu_block.git", - "reference": "8.x-1.4" + "reference": "8.x-1.6" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/menu_block-8.x-1.4.zip", - "reference": "8.x-1.4", - "shasum": "fd8d559452a9e1e07de6fefd9955df6feadc943a" + "url": "https://ftp.drupal.org/files/projects/menu_block-8.x-1.6.zip", + "reference": "8.x-1.6", + "shasum": "3da96af15c3a5f5f1966e28b6e87b74228617998" }, "require": { - "drupal/core": "*" + "drupal/core": "^8 || ^9" }, "type": "drupal-module", "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - }, "drupal": { - "version": "8.x-1.4", - "datestamp": "1525200184", + "version": "8.x-1.6", + "datestamp": "1587721600", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" } }, "patches_applied": { - "2809699": "https://www.drupal.org/files/issues/2018-10-26/menu_block-label_configuration-2809699-82.patch", - "2811337": "https://www.drupal.org/files/issues/menu_block-2_level_menu_block_not_limited_to_active_parent-2811337-58.patch" + "2809699": "https://www.drupal.org/files/issues/2020-05-03/2809699-162-dynamic-block-titles.patch", + "2950943": "https://www.drupal.org/files/issues/2020-05-21/2950943-40.patch" } }, "notification-url": "https://packages.drupal.org/8/downloads", @@ -5876,7 +5873,7 @@ "description": "Provides configurable blocks of menu links.", "homepage": "https://www.drupal.org/project/menu_block", "support": { - "source": "http://cgit.drupalcode.org/menu_block" + "source": "https://git.drupalcode.org/project/menu_block" } }, { diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index a54bfe8f5b28d440cfd03a55f75cb3fede4c83e1..700662b013216d993748a1d8c464e534a9d1669b 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -5990,38 +5990,35 @@ }, { "name": "drupal/menu_block", - "version": "1.4.0", - "version_normalized": "1.4.0.0", + "version": "1.6.0", + "version_normalized": "1.6.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/menu_block.git", - "reference": "8.x-1.4" + "reference": "8.x-1.6" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/menu_block-8.x-1.4.zip", - "reference": "8.x-1.4", - "shasum": "fd8d559452a9e1e07de6fefd9955df6feadc943a" + "url": "https://ftp.drupal.org/files/projects/menu_block-8.x-1.6.zip", + "reference": "8.x-1.6", + "shasum": "3da96af15c3a5f5f1966e28b6e87b74228617998" }, "require": { - "drupal/core": "*" + "drupal/core": "^8 || ^9" }, "type": "drupal-module", "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - }, "drupal": { - "version": "8.x-1.4", - "datestamp": "1525200184", + "version": "8.x-1.6", + "datestamp": "1587721600", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" } }, "patches_applied": { - "2809699": "https://www.drupal.org/files/issues/2018-10-26/menu_block-label_configuration-2809699-82.patch", - "2811337": "https://www.drupal.org/files/issues/menu_block-2_level_menu_block_not_limited_to_active_parent-2811337-58.patch" + "2809699": "https://www.drupal.org/files/issues/2020-05-03/2809699-162-dynamic-block-titles.patch", + "2950943": "https://www.drupal.org/files/issues/2020-05-21/2950943-40.patch" } }, "installation-source": "dist", @@ -6054,7 +6051,7 @@ "description": "Provides configurable blocks of menu links.", "homepage": "https://www.drupal.org/project/menu_block", "support": { - "source": "http://cgit.drupalcode.org/menu_block" + "source": "https://git.drupalcode.org/project/menu_block" } }, { diff --git a/web/modules/menu_block/PATCHES.txt b/web/modules/menu_block/PATCHES.txt new file mode 100644 index 0000000000000000000000000000000000000000..a2df6a4fe1df24d51eb02d638e6fab204dfbd293 --- /dev/null +++ b/web/modules/menu_block/PATCHES.txt @@ -0,0 +1,11 @@ +This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches) +Patches applied to this directory: + +2809699 +Source: https://www.drupal.org/files/issues/2020-05-03/2809699-162-dynamic-block-titles.patch + + +2950943 +Source: https://www.drupal.org/files/issues/2020-05-21/2950943-40.patch + + diff --git a/web/modules/menu_block/README.txt b/web/modules/menu_block/README.txt index 307cb2afda2a9c2783952f62e015998f1b9c34fe..e863865ef64911c170963c681e343e6a4f70b8df 100644 --- a/web/modules/menu_block/README.txt +++ b/web/modules/menu_block/README.txt @@ -54,6 +54,9 @@ Advanced options: Alter the options in “Menu levels” to be relative to the fixed parent item. The block will only contain children of the selected menu link. + Use as title + Replace the block title with an item from the menu. + HTML and style options: Theme hook suggestion diff --git a/web/modules/menu_block/config/schema/menu_block.schema.yml b/web/modules/menu_block/config/schema/menu_block.schema.yml index ad081278cf1f43728573e7a758eae87b33b0f5e7..b652b8a46fe5919c86f039c6fc1b0f09eaafcb3a 100644 --- a/web/modules/menu_block/config/schema/menu_block.schema.yml +++ b/web/modules/menu_block/config/schema/menu_block.schema.yml @@ -2,13 +2,25 @@ block.settings.menu_block:*: type: block_settings label: 'Menu block' mapping: + follow: + type: boolean + label: 'The initial visibility level follows the active menu item' + follow_parent: + type: string + label: 'Set initial visibility level to the active menu item, or its children' + label_link: + type: boolean + label: 'If the block label should be a link' + label_type: + type: string + label: 'Label type' level: type: integer label: 'Starting level' depth: type: integer label: 'Maximum number of levels' - expanded: + expand: type: boolean label: 'Expand all menu links' parent: diff --git a/web/modules/menu_block/menu_block.info.yml b/web/modules/menu_block/menu_block.info.yml index 1dcefa2133a8cf878030bea2a9de66e841c8ed34..15254beb6732502ad9345753d3de956a852380c2 100644 --- a/web/modules/menu_block/menu_block.info.yml +++ b/web/modules/menu_block/menu_block.info.yml @@ -1,12 +1,12 @@ name: Menu Block description: Provides configurable blocks of menu links. -# core: 8.x +core: 8.x +core_version_requirement: ^8 || ^9 type: module dependencies: - - menu_ui + - drupal:menu_ui -# Information added by Drupal.org packaging script on 2016-09-05 -version: '8.x-1.4' -core: '8.x' +# Information added by Drupal.org packaging script on 2020-04-24 +version: '8.x-1.6' project: 'menu_block' -datestamp: 1473063240 +datestamp: 1587721602 diff --git a/web/modules/menu_block/menu_block.install b/web/modules/menu_block/menu_block.install new file mode 100644 index 0000000000000000000000000000000000000000..bdb6b4cad01e7fc1ad9d61f9a39fb57e31b79155 --- /dev/null +++ b/web/modules/menu_block/menu_block.install @@ -0,0 +1,35 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the Menu Block module. + */ + +use Drupal\menu_block\Plugin\Block\MenuBlock; + +/** + * Issue #2932048: Config schema mismatch for expand(ed). + */ +function menu_block_update_8101() { + $config_factory = \Drupal::configFactory(); + $config_factory->rename('expanded', 'expand'); +} + +/** + * Add default config. + */ +function menu_block_update_8102() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('block.block.') as $block_config_name) { + $block = $config_factory->getEditable($block_config_name); + $settings = $block->get('settings'); + // Only update system_menu_block config. + if (strpos($settings['id'], 'system_menu_block:') === 0) { + // Set default config for existing menu block config. + $settings['label_link'] ?? FALSE; + $settings['label_type'] ?? MenuBlock::LABEL_BLOCK; + $block->set('settings', $settings) + ->save(TRUE); + } + } +} diff --git a/web/modules/menu_block/menu_block.module b/web/modules/menu_block/menu_block.module index 0614e91a3facc6d7c58e11a62071b410f0745c66..43d7b45c8303c5a8cd2575c73138f534b0d5293a 100644 --- a/web/modules/menu_block/menu_block.module +++ b/web/modules/menu_block/menu_block.module @@ -1,5 +1,10 @@ <?php +/** + * @file + * Provides configurable blocks of menu links. + */ + /** * Implements hook_theme_suggestions_HOOK() for "block". */ @@ -40,7 +45,8 @@ function menu_block_theme_suggestions_block(array $variables) { function menu_block_theme_suggestions_block_alter(array &$suggestions, array $variables) { if ($suggestions[0] == 'block__menu_block') { if ($suggestions[1] == 'block__menu_block') { - // Since this first suggestion is a dupe, replace it with the system suggestion. + // Since this first suggestion is a dupe, replace it with the system + // suggestion. $suggestions[0] = 'block__system_menu_block'; } // If some other module has removed the duplicates, use array_unshift(). @@ -99,3 +105,23 @@ function menu_block_theme_suggestions_menu(array $variables) { return $suggestions; } + +/** + * Implements hook_preprocess_hook() for "block". + * + * Set the block label with the #menu_block_configuration label if it exists. + * + * @see template_preprocess_block() + */ +function menu_block_preprocess_block(&$variables) { + if (isset($variables['content']['#menu_block_configuration']['label'])) { + $config_label = $variables['content']['#menu_block_configuration']['label']; + // Set the 'label' template variable to an empty string if the block is + // configured not to display a label. + $variables['label'] = empty($variables['configuration']['label_display']) ? '' : $config_label; + // However, we always set the configuration label (regardless of the + // 'label_display' setting) so the label can be included in the markup as + // hidden text for assistive technologies (for templates that handle this). + $variables['configuration']['label'] = $config_label; + } +} diff --git a/web/modules/menu_block/menu_block.post_update.php b/web/modules/menu_block/menu_block.post_update.php new file mode 100644 index 0000000000000000000000000000000000000000..9135121e1014f9b7d14f456d6996c942cb482e7f --- /dev/null +++ b/web/modules/menu_block/menu_block.post_update.php @@ -0,0 +1,29 @@ +<?php + +/** + * @file + * Post update functions for Menu block. + */ + +use Drupal\block\BlockInterface; +use Drupal\Core\Config\Entity\ConfigEntityUpdater; + +/** + * Implement config schema for the menu_block settings follow. + */ +function menu_block_post_update_implement_schema_for_follow_and_follow_parent(&$sandbox = NULL) { + if (!\Drupal::moduleHandler()->moduleExists('block')) { + return; + } + + \Drupal::classResolver(ConfigEntityUpdater::class) + ->update($sandbox, 'block', function (BlockInterface $block) { + if (strpos($block->getPluginId(), 'menu_block:') === 0) { + $block_settings = $block->get('settings'); + $block_settings['follow'] = (bool) $block_settings['follow']; + $block->set('settings', $block_settings); + return TRUE; + } + return FALSE; + }); +} diff --git a/web/modules/menu_block/src/EventSubscriber/MenuBlockKernelViewSubscriber.php b/web/modules/menu_block/src/EventSubscriber/MenuBlockKernelViewSubscriber.php index 7ef552b2ea4dff8a522560cafe717584cccbaca3..633f284a13fa6e7c22c4e0194abaaa88a6f01e75 100644 --- a/web/modules/menu_block/src/EventSubscriber/MenuBlockKernelViewSubscriber.php +++ b/web/modules/menu_block/src/EventSubscriber/MenuBlockKernelViewSubscriber.php @@ -62,7 +62,7 @@ public function onView(GetResponseEvent $event) { /** * {@inheritdoc} */ - static function getSubscribedEvents() { + public static function getSubscribedEvents() { // Run before main_content_view_subscriber. $events[KernelEvents::VIEW][] = ['onView', 1]; return $events; diff --git a/web/modules/menu_block/src/Plugin/Block/MenuBlock.php b/web/modules/menu_block/src/Plugin/Block/MenuBlock.php index 39f8569a62e0b46c7d5055d738dde12da1538b03..98bec3b99b07b22879232fedc20fa09520565e28 100644 --- a/web/modules/menu_block/src/Plugin/Block/MenuBlock.php +++ b/web/modules/menu_block/src/Plugin/Block/MenuBlock.php @@ -2,14 +2,16 @@ namespace Drupal\menu_block\Plugin\Block; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Link; +use Drupal\Core\Menu\MenuParentFormSelectorInterface; +use Drupal\Core\Menu\MenuTreeParameters; +use Drupal\Core\Render\Markup; +use Drupal\Core\Session\AccountInterface; use Drupal\system\Entity\Menu; use Drupal\system\Plugin\Block\SystemMenuBlock; use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Menu\MenuActiveTrailInterface; -use Drupal\Core\Menu\MenuLinkTreeInterface; -use Drupal\Core\Menu\MenuTreeParameters; /** * Provides an extended Menu block. @@ -18,7 +20,10 @@ * id = "menu_block", * admin_label = @Translation("Menu block"), * category = @Translation("Menus"), - * deriver = "Drupal\menu_block\Plugin\Derivative\MenuBlock" + * deriver = "Drupal\menu_block\Plugin\Derivative\MenuBlock", + * forms = { + * "settings_tray" = "\Drupal\system\Form\SystemMenuOffCanvasForm", + * }, * ) */ class MenuBlock extends SystemMenuBlock { @@ -31,7 +36,7 @@ class MenuBlock extends SystemMenuBlock { const LABEL_ACTIVE_ITEM = 'active_item'; const LABEL_PARENT = 'parent'; const LABEL_ROOT = 'root'; - const LABEL_FIXED_PARENT = 'fixed_parent'; + const LABEL_FIXED = 'fixed'; /** * Entity type manager. @@ -41,42 +46,33 @@ class MenuBlock extends SystemMenuBlock { protected $entityTypeManager; /** - * The active menu trail service. + * The menu parent form selector service. * - * @var \Drupal\Core\Menu\MenuActiveTrailInterface + * @var \Drupal\Core\Menu\MenuParentFormSelectorInterface */ - protected $menuActiveTrail; + protected $menuParentFormSelector; /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('menu.link_tree'), - $container->get('menu.active_trail'), - $container->get('entity_type.manager') - ); - } - - /** - * {@inheritdoc} - */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, MenuLinkTreeInterface $menu_tree, MenuActiveTrailInterface $active_trail, EntityTypeManagerInterface $entity_type_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $menu_tree); - $this->menuActiveTrail = $active_trail; - $this->entityTypeManager = $entity_type_manager; + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->setMenuParentFormSelector($container->get('menu.parent_form_selector')); + $instance->entityTypeManager = $container->get('entity_type.manager'); + return $instance; } /** - * {@inheritdoc} + * Set menu parent form selector service. + * + * @param \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_form_selector + * The menu parent form selector service. + * + * @return $this */ - public function getConfiguration() { - $label = $this->getBlockLabel() ?: $this->label(); - $this->setConfigurationValue('label', $label); - return $this->configuration; + public function setMenuParentFormSelector(MenuParentFormSelectorInterface $menu_parent_form_selector) { + $this->menuParentFormSelector = $menu_parent_form_selector; + return $this; } /** @@ -103,12 +99,10 @@ public function blockForm($form, FormStateInterface $form_state) { ]; $menu_name = $this->getDerivativeId(); - $menus = Menu::loadMultiple(array($menu_name)); + $menus = Menu::loadMultiple([$menu_name]); $menus[$menu_name] = $menus[$menu_name]->label(); - /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */ - $menu_parent_selector = \Drupal::service('menu.parent_form_selector'); - $form['advanced']['parent'] = $menu_parent_selector->parentSelectElement($config['parent'], '', $menus); + $form['advanced']['parent'] = $this->menuParentFormSelector->parentSelectElement($config['parent'], '', $menus); $form['advanced']['parent'] += [ '#title' => $this->t('Fixed parent item'), @@ -122,7 +116,7 @@ public function blockForm($form, FormStateInterface $form_state) { '#options' => [ self::LABEL_BLOCK => $this->t('Block title'), self::LABEL_MENU => $this->t('Menu title'), - self::LABEL_FIXED_PARENT => $this->t("Fixed parent item's title"), + self::LABEL_FIXED => $this->t("Fixed parent item's title"), self::LABEL_ACTIVE_ITEM => $this->t("Active item's title"), self::LABEL_PARENT => $this->t("Active trail's parent title"), self::LABEL_ROOT => $this->t("Active trail's root title"), @@ -135,6 +129,23 @@ public function blockForm($form, FormStateInterface $form_state) { ], ]; + $form['advanced']['label_link'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Link the title?'), + '#default_value' => $config['label_link'], + '#states' => [ + 'visible' => [ + ':input[name="settings[label_display]"]' => ['checked' => TRUE], + ':input[name="settings[label_type]"]' => [ + ['value' => self::LABEL_ACTIVE_ITEM], + ['value' => self::LABEL_PARENT], + ['value' => self::LABEL_ROOT], + ['value' => self::LABEL_FIXED], + ], + ], + ], + ]; + $form['style'] = [ '#type' => 'details', '#title' => $this->t('HTML and style options'), @@ -142,6 +153,29 @@ public function blockForm($form, FormStateInterface $form_state) { '#process' => [[get_class(), 'processMenuBlockFieldSets']], ]; + $form['advanced']['follow'] = [ + '#type' => 'checkbox', + '#title' => $this->t('<strong>Make the initial visibility level follow the active menu item.</strong>'), + '#default_value' => $config['follow'], + '#description' => $this->t('If the active menu item is deeper than the initial visibility level set above, the initial visibility level will be relative to the active menu item. Otherwise, the initial visibility level of the tree will remain fixed.'), + ]; + + $form['advanced']['follow_parent'] = [ + '#type' => 'radios', + '#title' => $this->t('Initial visibility level will be'), + '#description' => $this->t('When following the active menu item, select whether the initial visibility level should be set to the active menu item, or its children.'), + '#default_value' => $config['follow_parent'], + '#options' => [ + 'active' => $this->t('Active menu item'), + 'child' => $this->t('Children of active menu item'), + ], + '#states' => [ + 'visible' => [ + ':input[name="settings[follow]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['style']['suggestion'] = [ '#type' => 'machine_name', '#title' => $this->t('Theme hook suggestion'), @@ -150,11 +184,12 @@ public function blockForm($form, FormStateInterface $form_state) { '#description' => $this->t('A theme hook suggestion can be used to override the default HTML and CSS classes for menus found in <code>menu.html.twig</code>.'), '#machine_name' => [ 'error' => $this->t('The theme hook suggestion must contain only lowercase letters, numbers, and underscores.'), + 'exists' => [$this, 'suggestionExists'], ], ]; // Open the details field sets if their config is not set to defaults. - foreach(['menu_levels', 'advanced', 'style'] as $fieldSet) { + foreach (['menu_levels', 'advanced', 'style'] as $fieldSet) { foreach (array_keys($form[$fieldSet]) as $field) { if (isset($defaults[$field]) && $defaults[$field] !== $config[$field]) { $form[$fieldSet]['#open'] = TRUE; @@ -179,12 +214,15 @@ public static function processMenuBlockFieldSets(&$element, FormStateInterface $ * {@inheritdoc} */ public function blockSubmit($form, FormStateInterface $form_state) { + $this->configuration['follow'] = $form_state->getValue('follow'); + $this->configuration['follow_parent'] = $form_state->getValue('follow_parent'); $this->configuration['level'] = $form_state->getValue('level'); $this->configuration['depth'] = $form_state->getValue('depth'); $this->configuration['expand'] = $form_state->getValue('expand'); $this->configuration['parent'] = $form_state->getValue('parent'); $this->configuration['suggestion'] = $form_state->getValue('suggestion'); $this->configuration['label_type'] = $form_state->getValue('label_type'); + $this->configuration['label_link'] = $form_state->getValue('label_link'); } /** @@ -199,9 +237,20 @@ public function build() { $depth = $this->configuration['depth']; $expand = $this->configuration['expand']; $parent = $this->configuration['parent']; - $suggestion = $this->configuration['suggestion']; + $follow = $this->configuration['follow']; + $follow_parent = $this->configuration['follow_parent']; + $following = FALSE; $parameters->setMinDepth($level); + + // If we're following the active trail and the active trail is deeper than + // the initial starting level, we update the level to match the active menu + // item's level in the menu. + if ($follow && count($parameters->activeTrail) > $level) { + $level = count($parameters->activeTrail); + $following = TRUE; + } + // When the depth is configured to zero, there is no depth limit. When depth // is non-zero, it indicates the number of levels that must be displayed. // Hence this is a relative depth that we must convert to an actual @@ -210,92 +259,142 @@ public function build() { $parameters->setMaxDepth(min($level + $depth - 1, $this->menuTree->maxDepth())); } - // For menu blocks with start level greater than 1, only show menu items - // from the current active trail. Adjust the root according to the current - // position in the menu in order to determine if we can show the subtree. - // If we're using a fixed parent item, we'll skip this step. + // If we're currently following an active menu item, or for menu blocks with + // start level greater than 1, only show menu items from the current active + // trail. Adjust the root according to the current position in the menu in + // order to determine if we can show the subtree. If we're not following an + // active trail and using a fixed parent item, we'll skip this step. $fixed_parent_menu_link_id = str_replace($menu_name . ':', '', $parent); - if ($level > 1 && !$fixed_parent_menu_link_id) { + if ($following || ($level > 1 && !$fixed_parent_menu_link_id)) { if (count($parameters->activeTrail) >= $level) { // Active trail array is child-first. Reverse it, and pull the new menu // root based on the parent of the configured start level. $menu_trail_ids = array_reverse(array_values($parameters->activeTrail)); - $menu_root = $menu_trail_ids[$level - 1]; + $offset = ($following && $follow_parent == 'active') ? 2 : 1; + $menu_root = $menu_trail_ids[$level - $offset]; $parameters->setRoot($menu_root)->setMinDepth(1); if ($depth > 0) { - $max_depth = min($level - 1 + $depth - 1, $this->menuTree->maxDepth()); - $parameters->setMaxDepth($max_depth); + $parameters->setMaxDepth(min($depth, $this->menuTree->maxDepth())); } } else { - return array(); + return []; } } // If expandedParents is empty, the whole menu tree is built. if ($expand) { - $parameters->expandedParents = array(); + $parameters->expandedParents = []; } + // When a fixed parent item is set, root the menu tree at the given ID. if ($fixed_parent_menu_link_id) { - $parameters->setRoot($fixed_parent_menu_link_id); - - // If the starting level is 1, we always want the child links to appear, - // but the requested tree may be empty if the tree does not contain the - // active trail. - if ($level === 1 || $level === '1') { - // Check if the tree contains links. - $tree = $this->menuTree->load(NULL, $parameters); - if (empty($tree)) { + // Clone the parameters so we can fall back to using them if we're + // following the active menu item and the current page is part of the + // active menu trail. + $fixed_parameters = clone $parameters; + $fixed_parameters->setRoot($fixed_parent_menu_link_id); + $tree = $this->menuTree->load($menu_name, $fixed_parameters); + + // Check if the tree contains links. + if (empty($tree)) { + // If the starting level is 1, we always want the child links to appear, + // but the requested tree may be empty if the tree does not contain the + // active trail. We're accessing the configuration directly since the + // $level variable may have changed by this point. + if ($this->configuration['level'] === 1 || $this->configuration['level'] === '1') { // Change the request to expand all children and limit the depth to // the immediate children of the root. - $parameters->expandedParents = array(); - $parameters->setMinDepth(1); - $parameters->setMaxDepth(1); + $fixed_parameters->expandedParents = []; + $fixed_parameters->setMinDepth(1); + $fixed_parameters->setMaxDepth(1); // Re-load the tree. - $tree = $this->menuTree->load(NULL, $parameters); + $tree = $this->menuTree->load($menu_name, $fixed_parameters); } } + elseif ($following) { + // If we're following the active menu item, and the tree isn't empty + // (which indicates we're currently in the active trail), we unset + // the tree we made and just let the active menu parameters from before + // do their thing. + unset($tree); + } } // Load the tree if we haven't already. if (!isset($tree)) { $tree = $this->menuTree->load($menu_name, $parameters); } - $manipulators = array( - array('callable' => 'menu.default_tree_manipulators:checkAccess'), - array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'), - ); + $manipulators = [ + ['callable' => 'menu.default_tree_manipulators:checkAccess'], + ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'], + ]; $tree = $this->menuTree->transform($tree, $manipulators); $build = $this->menuTree->build($tree); + $label = $this->getBlockLabel() ?: $this->label(); + // Set the block's #title (label) to the dynamic value. + $build['#title'] = [ + '#markup' => $label, + ]; if (!empty($build['#theme'])) { // Add the configuration for use in menu_block_theme_suggestions_menu(). $build['#menu_block_configuration'] = $this->configuration; + // Set the generated label into the configuration array so it is + // propagated to the theme preprocessor and template(s) as needed. + $build['#menu_block_configuration']['label'] = $label; // Remove the menu name-based suggestion so we can control its precedence // better in menu_block_theme_suggestions_menu(). $build['#theme'] = 'menu'; } + $build['#contextual_links']['menu'] = [ + 'route_parameters' => ['menu' => $menu_name], + ]; + return $build; } + /** + * {@inheritdoc} + */ + public function blockAccess(AccountInterface $account) { + $build = $this->build(); + if (empty($build['#items'])) { + return AccessResult::forbidden(); + } + return parent::blockAccess($account); + } + /** * {@inheritdoc} */ public function defaultConfiguration() { return [ + 'follow' => 0, + 'follow_parent' => 'child', 'level' => 1, 'depth' => 0, 'expand' => 0, 'parent' => $this->getDerivativeId() . ':', 'suggestion' => strtr($this->getDerivativeId(), '-', '_'), 'label_type' => self::LABEL_BLOCK, + 'label_link' => FALSE, ]; } /** - * Get the configured block label. + * Checks for an existing theme hook suggestion. + * + * @return bool + * Returns FALSE because there is no need of validation by unique value. + */ + public function suggestionExists() { + return FALSE; + } + + /** + * Gets the configured block label. * * @return string * The configured label. @@ -314,8 +413,8 @@ public function getBlockLabel() { case self::LABEL_ROOT: return $this->getActiveTrailRootTitle(); - case self::LABEL_FIXED_PARENT: - return $this->getFixedParentItemTitle(); + case self::LABEL_FIXED: + return $this->getFixedMenuItemTitle(); default: return $this->label(); @@ -323,10 +422,10 @@ public function getBlockLabel() { } /** - * Get the label of the configured menu. + * Gets the label of the configured menu. * * @return string|null - * Menu label or null if no menu exists. + * Menu label or NULL if no menu exists. */ protected function getMenuTitle() { try { @@ -341,24 +440,28 @@ protected function getMenuTitle() { } /** - * @return string + * Gets the title of a fixed parent item. + * + * @return string|null + * Title of the configured (fixed) parent item, or NULL if there is none. */ - protected function getFixedParentItemTitle() { + protected function getFixedMenuItemTitle() { $parent = $this->configuration['parent']; if ($parent) { - $fixed_parent_menu_link_id = str_replace($this->getDerivativeId() . ':', '', $parent); - return $this->getLinkTitleFromLink($fixed_parent_menu_link_id); + $fixed_menu_link_id = str_replace($this->getDerivativeId() . ':', '', $parent); + return $this->getLinkTitleFromLink($fixed_menu_link_id); } } /** - * Get the active menu item's title. + * Gets the active menu item's title. * - * @return string - * Current menu item title. + * @return string|null + * Currently active menu item title or NULL if there's nothing active. */ protected function getActiveItemTitle() { + /** @var array $active_trail_ids */ $active_trail_ids = $this->getDerivativeActiveTrailIds(); if ($active_trail_ids) { return $this->getLinkTitleFromLink(reset($active_trail_ids)); @@ -366,14 +469,15 @@ protected function getActiveItemTitle() { } /** - * Get the current menu item's parent menu title. + * Gets the title of the parent of the active menu item. * - * @return string - * The menu item title. + * @return string|null + * The title of the parent of the active menu item, the title of the active + * item if it has no parent, or NULL if there's no active menu item. */ protected function getActiveTrailParentTitle() { + /** @var array $active_trail_ids */ $active_trail_ids = $this->getDerivativeActiveTrailIds(); - if ($active_trail_ids) { if (count($active_trail_ids) === 1) { return $this->getActiveItemTitle(); @@ -383,12 +487,13 @@ protected function getActiveTrailParentTitle() { } /** - * Get the current menu item's root menu item title. + * Gets the current menu item's root menu item title. * - * @return string - * The menu item title. + * @return string|null + * The root menu item title or NULL if there's no active item. */ protected function getActiveTrailRootTitle() { + /** @var array $active_trail_ids */ $active_trail_ids = $this->getDerivativeActiveTrailIds(); if ($active_trail_ids) { @@ -397,10 +502,10 @@ protected function getActiveTrailRootTitle() { } /** - * Get an array of the active trail menu link items. + * Gets an array of the active trail menu link items. * * @return array - * The active trail. + * The active trail menu item IDs. */ protected function getDerivativeActiveTrailIds() { $menu_id = $this->getDerivativeId(); @@ -408,32 +513,37 @@ protected function getDerivativeActiveTrailIds() { } /** - * Given a menu item ID, get that item's title. + * Gets the title of a given menu item ID. * * @param string $link_id - * Menu Item ID. + * The menu item ID. * - * @return string - * The menu item title. + * @return string|null + * The menu item title or NULL if the given menu item can't be found. */ protected function getLinkTitleFromLink($link_id) { $parameters = new MenuTreeParameters(); $menu = $this->menuTree->load($this->getDerivativeId(), $parameters); - if ($link = $this->findLinkInTree($menu, $link_id)) { + $link = $this->findLinkInTree($menu, $link_id); + if ($link) { + if ($this->configuration['label_link']) { + $block_link = Link::fromTextAndUrl($link->link->getTitle(), $link->link->getUrlObject())->toString(); + return Markup::create($block_link); + } return $link->link->getTitle(); } } /** - * Find and return the menu link item from the menu tree. + * Gets the menu link item from the menu tree. * * @param array $menu_tree * Associative array containing the menu link tree data. * @param string $link_id * Menu link id to find. * - * @return \Drupal\Core\Menu\MenuLinkTreeElement - * The link element from the given menu tree. + * @return \Drupal\Core\Menu\MenuLinkTreeElement|null + * The link element from the given menu tree or NULL if it can't be found. */ protected function findLinkInTree(array $menu_tree, $link_id) { if (isset($menu_tree[$link_id])) { @@ -441,7 +551,8 @@ protected function findLinkInTree(array $menu_tree, $link_id) { } /** @var \Drupal\Core\Menu\MenuLinkTreeElement $link */ foreach ($menu_tree as $link) { - if ($link = $this->findLinkInTree($link->subtree, $link_id)) { + $link = $this->findLinkInTree($link->subtree, $link_id); + if ($link) { return $link; } } diff --git a/web/modules/menu_block/src/Plugin/Derivative/MenuBlock.php b/web/modules/menu_block/src/Plugin/Derivative/MenuBlock.php index cf37822f54b8757dc44ffc34b3c72696fed17bac..9d913c389c8e71b38b5ef46eba6c3e6473d75ba1 100644 --- a/web/modules/menu_block/src/Plugin/Derivative/MenuBlock.php +++ b/web/modules/menu_block/src/Plugin/Derivative/MenuBlock.php @@ -18,7 +18,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { foreach ($this->menuStorage->loadMultiple() as $menu => $entity) { $this->derivatives[$menu] = $base_plugin_definition; $this->derivatives[$menu]['admin_label'] = $entity->label(); - $this->derivatives[$menu]['config_dependencies']['config'] = array($entity->getConfigDependencyName()); + $this->derivatives[$menu]['config_dependencies']['config'] = [$entity->getConfigDependencyName()]; } return $this->derivatives; } diff --git a/web/modules/menu_block/tests/modules/menu_block_test/menu_block_test.info.yml b/web/modules/menu_block/tests/modules/menu_block_test/menu_block_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..640d8985571e4a4ae0f852c233d5c1faf6688771 --- /dev/null +++ b/web/modules/menu_block/tests/modules/menu_block_test/menu_block_test.info.yml @@ -0,0 +1,10 @@ +name: 'Menu block test module' +type: module +description: 'Support module for menu_block testing.' +package: Testing +core: 8.x + +# Information added by Drupal.org packaging script on 2020-04-24 +version: '8.x-1.6' +project: 'menu_block' +datestamp: 1587721602 diff --git a/web/modules/menu_block/tests/modules/menu_block_test/menu_block_test.routing.yml b/web/modules/menu_block/tests/modules/menu_block_test/menu_block_test.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..cc269d643056c9752ca87c25dac42e70ba9c7a3f --- /dev/null +++ b/web/modules/menu_block/tests/modules/menu_block_test/menu_block_test.routing.yml @@ -0,0 +1,39 @@ +menu_test.hierarchy.parent: + path: '/menu-block-test/hierarchy/parent' + defaults: + _title: 'parent page' + _controller: '\Drupal\menu_block_test\Controller\MenuBlockTestController::menuBlockTestCallback' + requirements: + _access: 'TRUE' + +menu_test.hierarchy.parent.child_1: + path: '/menu-block-test/hierarchy/parent/child-1' + defaults: + _title: 'child-1 page' + _controller: '\Drupal\menu_block_test\Controller\MenuBlockTestController::menuBlockTestCallback' + requirements: + _access: 'TRUE' + +menu_test.hierarchy.parent.child_1.child_1_1: + path: '/menu-block-test/hierarchy/parent/child-1/child-1-1' + defaults: + _title: 'child-1-1 page' + _controller: '\Drupal\menu_block_test\Controller\MenuBlockTestController::menuBlockTestCallback' + requirements: + _access: 'TRUE' + +menu_test.hierarchy.parent.child_1.child_1_2: + path: '/menu-block-test/hierarchy/parent/child-1/child-1-2' + defaults: + _title: 'child-1-2 page' + _controller: '\Drupal\menu_block_test\Controller\MenuBlockTestController::menuBlockTestCallback' + requirements: + _access: 'TRUE' + +menu_test.hierarchy.parent.child_2: + path: '/menu-block-test/hierarchy/parent/child-2' + defaults: + _title: 'child-2 page' + _controller: '\Drupal\menu_block_test\Controller\MenuBlockTestController::menuBlockTestCallback' + requirements: + _access: 'TRUE' diff --git a/web/modules/menu_block/tests/modules/menu_block_test/src/Controller/MenuBlockTestController.php b/web/modules/menu_block/tests/modules/menu_block_test/src/Controller/MenuBlockTestController.php new file mode 100644 index 0000000000000000000000000000000000000000..c72e8514b5d4b5e5f771fd273f6d98965ffd68af --- /dev/null +++ b/web/modules/menu_block/tests/modules/menu_block_test/src/Controller/MenuBlockTestController.php @@ -0,0 +1,22 @@ +<?php + +namespace Drupal\menu_block_test\Controller; + +use Drupal\Core\Controller\ControllerBase; + +/** + * Controller routines for menu_block_test routes. + */ +class MenuBlockTestController extends ControllerBase { + + /** + * Returns placeholder page content which can be used for testing. + * + * @return string + * A string that can be used for comparison. + */ + public function menuBlockTestCallback() { + return ['#markup' => 'This is the menuBlockTestCallback() content.']; + } + +} diff --git a/web/modules/menu_block/tests/src/Functional/MenuBlockTest.php b/web/modules/menu_block/tests/src/Functional/MenuBlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ae38e952c91c9707509bc429f1d29f6881cdca47 --- /dev/null +++ b/web/modules/menu_block/tests/src/Functional/MenuBlockTest.php @@ -0,0 +1,590 @@ +<?php + +namespace Drupal\Tests\menu_block\Functional; + +use Drupal\menu_block\Plugin\Block\MenuBlock; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests for the menu_block module. + * + * @group menu_block + */ +class MenuBlockTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = [ + 'block', + 'menu_block', + 'menu_block_test', + ]; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * An administrative user to configure the test environment. + * + * @var \Drupal\user\Entity\User|false + */ + protected $adminUser; + + /** + * The menu link plugin manager. + * + * @var \Drupal\Core\Menu\MenuLinkManagerInterface + */ + protected $menuLinkManager; + + /** + * The block storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $blockStorage; + + /** + * The block view builder. + * + * @var \Drupal\Core\Entity\EntityViewBuilderInterface + */ + protected $blockViewBuilder; + + /** + * The menu link content storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $menuLinkContentStorage; + + /** + * The module handler service. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * An array containing the menu link plugin ids. + * + * @var array + */ + protected $links; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->menuLinkManager = \Drupal::service('plugin.manager.menu.link'); + $this->blockStorage = \Drupal::service('entity_type.manager') + ->getStorage('block'); + $this->blockViewBuilder = \Drupal::service('entity_type.manager') + ->getViewBuilder('block'); + $this->menuLinkContentStorage = \Drupal::service('entity_type.manager') + ->getStorage('menu_link_content'); + $this->moduleHandler = \Drupal::moduleHandler(); + + $this->links = $this->createLinkHierarchy(); + + // Create and log in an administrative user. + $this->adminUser = $this->drupalCreateUser([ + 'administer blocks', + 'access administration pages', + ]); + $this->drupalLogin($this->adminUser); + } + + /** + * Creates a simple hierarchy of links. + */ + protected function createLinkHierarchy() { + // First remove all the menu links in the menu. + $this->menuLinkManager->deleteLinksInMenu('main'); + + // Then create a simple link hierarchy: + // - parent menu item + // - child-1 menu item + // - child-1-1 menu item + // - child-1-2 menu item + // - child-2 menu item. + $base_options = [ + 'provider' => 'menu_block', + 'menu_name' => 'main', + ]; + + $parent = $base_options + [ + 'title' => 'parent menu item', + 'link' => ['uri' => 'internal:/menu-block-test/hierarchy/parent'], + ]; + /** @var \Drupal\menu_link_content\MenuLinkContentInterface $link */ + $link = $this->menuLinkContentStorage->create($parent); + $link->save(); + $links['parent'] = $link->getPluginId(); + + $child_1 = $base_options + [ + 'title' => 'child-1 menu item', + 'link' => ['uri' => 'internal:/menu-block-test/hierarchy/parent/child-1'], + 'parent' => $links['parent'], + ]; + $link = $this->menuLinkContentStorage->create($child_1); + $link->save(); + $links['child-1'] = $link->getPluginId(); + + $child_1_1 = $base_options + [ + 'title' => 'child-1-1 menu item', + 'link' => ['uri' => 'internal:/menu-block-test/hierarchy/parent/child-1/child-1-1'], + 'parent' => $links['child-1'], + ]; + $link = $this->menuLinkContentStorage->create($child_1_1); + $link->save(); + $links['child-1-1'] = $link->getPluginId(); + + $child_1_2 = $base_options + [ + 'title' => 'child-1-2 menu item', + 'link' => ['uri' => 'internal:/menu-block-test/hierarchy/parent/child-1/child-1-2'], + 'parent' => $links['child-1'], + ]; + $link = $this->menuLinkContentStorage->create($child_1_2); + $link->save(); + $links['child-1-2'] = $link->getPluginId(); + + $child_2 = $base_options + [ + 'title' => 'child-2 menu item', + 'link' => ['uri' => 'internal:/menu-block-test/hierarchy/parent/child-2'], + 'parent' => $links['parent'], + ]; + $link = $this->menuLinkContentStorage->create($child_2); + $link->save(); + $links['child-2'] = $link->getPluginId(); + + return $links; + } + + /** + * Checks if all menu block configuration options are available. + */ + public function testMenuBlockFormDisplay() { + $this->drupalGet('admin/structure/block/add/menu_block:main'); + $this->assertSession()->pageTextContains('Initial visibility level'); + $this->assertSession()->pageTextContains('Number of levels to display'); + $this->assertSession()->pageTextContains('Expand all menu links'); + $this->assertSession()->pageTextContains('Fixed parent item'); + $this->assertSession() + ->pageTextContains('Make the initial visibility level follow the active menu item.'); + $this->assertSession()->pageTextContains('Theme hook suggestion'); + } + + /** + * Checks if all menu block settings are saved correctly. + */ + public function testMenuBlockUi() { + $block_id = 'main'; + $this->drupalPostForm('admin/structure/block/add/menu_block:main', [ + 'id' => $block_id, + 'settings[label]' => 'Main navigation', + 'settings[label_display]' => FALSE, + 'settings[level]' => 2, + 'settings[depth]' => 6, + 'settings[expand]' => TRUE, + 'settings[parent]' => 'main:', + 'settings[follow]' => TRUE, + 'settings[follow_parent]' => 'active', + 'settings[suggestion]' => 'main', + 'region' => 'primary_menu', + ], 'Save block'); + + $block = $this->blockStorage->load($block_id); + $block_settings = $block->get('settings'); + $this->assertSame(2, $block_settings['level']); + $this->assertSame(6, $block_settings['depth']); + $this->assertTrue($block_settings['expand']); + $this->assertSame('main:', $block_settings['parent']); + $this->assertTrue($block_settings['follow']); + $this->assertSame('active', $block_settings['follow_parent']); + $this->assertSame('main', $block_settings['suggestion']); + } + + /** + * Tests the menu_block level option. + */ + public function testMenuBlockLevel() { + // Add new menu block. + $block_id = 'main'; + $this->drupalPostForm('admin/structure/block/add/menu_block:main', [ + 'id' => $block_id, + 'settings[label]' => 'Main navigation', + 'settings[label_display]' => FALSE, + 'settings[level]' => 1, + 'region' => 'primary_menu', + ], 'Save block'); + + // Check if the parent menu item is visible, but the child menu items not. + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextNotContains('child-1 menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + $this->assertSession()->pageTextNotContains('child-2 menu item'); + + $this->drupalPostForm('admin/structure/block/manage/' . $block_id, [ + 'settings[level]' => 2, + ], 'Save block'); + + // Check if the menu items of level 2 are visible, but not the parent menu + // item. + $this->drupalGet('menu-block-test/hierarchy/parent'); + $this->assertSession()->pageTextContains('child-1 menu item'); + $this->assertSession()->pageTextContains('child-2 menu item'); + $this->assertSession()->pageTextNotContains('parent menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + } + + /** + * Tests the menu_block depth option. + */ + public function testMenuBlockDepth() { + // Add new menu block. + $block_id = 'main'; + $this->drupalPostForm('admin/structure/block/add/menu_block:main', [ + 'id' => $block_id, + 'settings[label]' => 'Main navigation', + 'settings[label_display]' => FALSE, + 'settings[level]' => 1, + 'settings[depth]' => 1, + 'settings[expand]' => TRUE, + 'region' => 'primary_menu', + ], 'Save block'); + + // Check if the parent menu item is visible, but the child menu items not. + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextNotContains('child-1 menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + $this->assertSession()->pageTextNotContains('child-2 menu item'); + + $this->drupalPostForm('admin/structure/block/manage/' . $block_id, [ + 'settings[depth]' => 2, + ], 'Save block'); + + // Check if the menu items of level 2 are visible, but not the parent menu + // item. + $this->drupalGet('menu-block-test/hierarchy/parent'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextContains('child-1 menu item'); + $this->assertSession()->pageTextContains('child-2 menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + + $this->drupalPostForm('admin/structure/block/manage/' . $block_id, [ + 'settings[depth]' => 0, + ], 'Save block'); + + // Check if the menu items of level 2 are visible, but not the parent menu + // item. + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextContains('child-1 menu item'); + $this->assertSession()->pageTextContains('child-2 menu item'); + $this->assertSession()->pageTextContains('child-1-1 menu item'); + } + + /** + * Tests the menu_block expand option. + */ + public function testMenuBlockExpand() { + $block_id = 'main'; + $this->drupalPostForm('admin/structure/block/add/menu_block:main', [ + 'id' => $block_id, + 'settings[label]' => 'Main navigation', + 'settings[label_display]' => FALSE, + 'settings[level]' => 1, + 'settings[expand]' => TRUE, + 'region' => 'primary_menu', + ], 'Save block'); + + // Check if the parent menu item is visible, but the child menu items not. + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextContains('child-1 menu item'); + $this->assertSession()->pageTextContains('child-1-1 menu item'); + $this->assertSession()->pageTextContains('child-2 menu item'); + + $this->drupalPostForm('admin/structure/block/manage/' . $block_id, [ + 'settings[expand]' => FALSE, + ], 'Save block'); + + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextNotContains('child-1 menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + $this->assertSession()->pageTextNotContains('child-2 menu item'); + } + + /** + * Tests the menu_block parent option. + */ + public function testMenuBlockParent() { + $block_id = 'main'; + $this->drupalPostForm('admin/structure/block/add/menu_block:main', [ + 'id' => $block_id, + 'settings[label]' => 'Main navigation', + 'settings[label_display]' => FALSE, + 'settings[parent]' => 'main:' . $this->links['parent'], + 'region' => 'primary_menu', + ], 'Save block'); + + $this->drupalGet('<front>'); + $this->assertSession()->pageTextNotContains('parent menu item'); + $this->assertSession()->pageTextContains('child-1 menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + + $this->drupalPostForm('admin/structure/block/manage/' . $block_id, [ + 'settings[parent]' => 'main:' . $this->links['child-1'], + ], 'Save block'); + + $this->assertSession()->pageTextNotContains('parent menu item'); + $this->assertSession()->pageTextNotContains('child-1 menu item'); + $this->assertSession()->pageTextContains('child-1-1 menu item'); + $this->assertSession()->pageTextContains('child-1-2 menu item'); + } + + /** + * Tests the menu_block follow and follow_parent option. + */ + public function testMenuBlockFollow() { + $block_id = 'main'; + $this->drupalPostForm('admin/structure/block/add/menu_block:main', [ + 'id' => $block_id, + 'settings[label]' => 'Main navigation', + 'settings[label_display]' => FALSE, + 'settings[follow]' => TRUE, + 'settings[follow_parent]' => 'child', + 'region' => 'primary_menu', + ], 'Save block'); + + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextNotContains('child-1 menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + + $this->drupalGet('menu-block-test/hierarchy/parent'); + $this->assertSession()->pageTextNotContains('parent menu item'); + $this->assertSession()->pageTextContains('child-1 menu item'); + $this->assertSession()->pageTextContains('child-2 menu item'); + + $this->drupalGet('menu-block-test/hierarchy/parent/child-1'); + $this->assertSession()->pageTextNotContains('parent menu item'); + $this->assertSession()->pageTextNotContains('child-1 menu item'); + $this->assertSession()->pageTextContains('child-1-1 menu item'); + $this->assertSession()->pageTextContains('child-1-2 menu item'); + $this->assertSession()->pageTextNotContains('child-2 menu item'); + + $this->drupalPostForm('admin/structure/block/manage/' . $block_id, [ + 'settings[follow_parent]' => 'active', + ], 'Save block'); + + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextNotContains('child-1 menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + + $this->drupalGet('menu-block-test/hierarchy/parent'); + $this->assertSession()->pageTextContains('parent menu item'); + $this->assertSession()->pageTextContains('child-1 menu item'); + $this->assertSession()->pageTextNotContains('child-1-1 menu item'); + } + + /** + * Tests the menu_block suggestion option. + */ + public function testMenuBlockSuggestion() { + $block_id = 'main'; + $this->drupalPostForm('admin/structure/block/add/menu_block:main', [ + 'id' => $block_id, + 'settings[label]' => 'Main navigation', + 'settings[label_display]' => FALSE, + 'settings[suggestion]' => 'mainnav', + 'region' => 'primary_menu', + ], 'Save block'); + + /** @var \Drupal\block\BlockInterface $block */ + $block = $this->blockStorage->load($block_id); + $plugin = $block->getPlugin(); + + // Check theme suggestions for block template. + $variables = []; + $variables['elements']['#configuration'] = $plugin->getConfiguration(); + $variables['elements']['#plugin_id'] = $plugin->getPluginId(); + $variables['elements']['#id'] = $block->id(); + $variables['elements']['#base_plugin_id'] = $plugin->getBaseId(); + $variables['elements']['#derivative_plugin_id'] = $plugin->getDerivativeId(); + $variables['elements']['content'] = []; + $suggestions = $this->moduleHandler->invokeAll('theme_suggestions_block', [$variables]); + + $base_theme_hook = 'menu_block'; + $hooks = [ + 'theme_suggestions', + 'theme_suggestions_' . $base_theme_hook, + ]; + $this->moduleHandler->alter($hooks, $suggestions, $variables, $base_theme_hook); + + $this->assertSame($suggestions, [ + 'block__menu_block', + 'block__menu_block', + 'block__menu_block__main', + 'block__main', + 'block__menu_block__mainnav', + ], 'Found expected block suggestions.'); + + // Check theme suggestions for menu template. + $variables = [ + 'menu_name' => 'main', + 'menu_block_configuration' => $plugin->getConfiguration(), + ]; + $suggestions = $this->moduleHandler->invokeAll('theme_suggestions_menu', [$variables]); + + $base_theme_hook = 'menu'; + $hooks = [ + 'theme_suggestions', + 'theme_suggestions_' . $base_theme_hook, + ]; + $this->moduleHandler->alter($hooks, $suggestions, $variables, $base_theme_hook); + $this->assertSame($suggestions, [ + 'menu__main', + 'menu__mainnav', + ], 'Found expected menu suggestions.'); + } + + /** + * Test menu block label type options. + */ + public function testMenuBlockTitleOptions() { + // Create a block, and edit it repeatedly to test the title display options. + $block_id = 'main'; + $this->drupalPostForm('admin/structure/block/add/menu_block:main', [ + 'id' => $block_id, + 'settings[label]' => 'Block title', + 'settings[label_display]' => TRUE, + 'settings[label_link]' => FALSE, + 'settings[parent]' => 'main:' . $this->links['child-1'], + 'region' => 'primary_menu', + ], 'Save block'); + + $options = [ + 'block label' => [ + 'option' => MenuBlock::LABEL_BLOCK, + 'title' => 'Block title', + ], + 'menu label' => [ + 'option' => MenuBlock::LABEL_MENU, + 'title' => 'Main navigation', + ], + 'fixed menu item' => [ + 'option' => MenuBlock::LABEL_FIXED, + 'title' => 'child-1 menu item', + ], + 'fixed menu item as link' => [ + 'option' => MenuBlock::LABEL_FIXED, + 'title' => 'child-1 menu item', + 'label_link' => TRUE, + ], + 'fixed menu item parent' => [ + 'option' => MenuBlock::LABEL_FIXED, + 'title' => 'child-1 menu item', + 'test_link' => 'menu-block-test/hierarchy/parent', + ], + 'active item' => [ + 'option' => MenuBlock::LABEL_ACTIVE_ITEM, + 'title' => 'child-1-1 menu item', + ], + 'active item as link' => [ + 'option' => MenuBlock::LABEL_ACTIVE_ITEM, + 'title' => 'child-1-1 menu item', + 'label_link' => TRUE, + ], + 'parent item' => [ + 'option' => MenuBlock::LABEL_PARENT, + 'title' => 'child-1 menu item', + ], + 'parent item as link' => [ + 'option' => MenuBlock::LABEL_PARENT, + 'title' => 'child-1 menu item', + 'label_link' => TRUE, + ], + 'parent item top level' => [ + 'option' => MenuBlock::LABEL_PARENT, + 'title' => 'parent menu item', + 'test_link' => 'menu-block-test/hierarchy/parent', + ], + 'parent item 2' => [ + 'option' => MenuBlock::LABEL_PARENT, + 'title' => 'parent menu item', + 'test_link' => 'menu-block-test/hierarchy/parent/child-1', + ], + 'parent item 3' => [ + 'option' => MenuBlock::LABEL_PARENT, + 'title' => 'child-1 menu item', + 'test_link' => 'menu-block-test/hierarchy/parent/child-1/child-1-2', + ], + 'menu root' => [ + 'option' => MenuBlock::LABEL_ROOT, + 'title' => 'parent menu item', + ], + 'menu root as link' => [ + 'option' => MenuBlock::LABEL_ROOT, + 'title' => 'parent menu item', + 'label_link' => TRUE, + ], + 'menu root 2' => [ + 'option' => MenuBlock::LABEL_ROOT, + 'title' => 'parent menu item', + 'test_link' => 'menu-block-test/hierarchy/parent/child-1', + ], + 'menu root 3' => [ + 'option' => MenuBlock::LABEL_ROOT, + 'title' => 'parent menu item', + 'test_link' => 'menu-block-test/hierarchy/parent/child-1/child-1-2', + ], + 'menu root hidden title' => [ + 'option' => MenuBlock::LABEL_ROOT, + 'title' => 'parent menu item', + 'label_display' => FALSE, + ], + ]; + + foreach ($options as $case_id => $option) { + // The 'label_display' setting should be TRUE if not defined explicitly. + $label_display = $option['label_display'] ?? TRUE; + // The 'label_link' setting should default to FALSE. + $label_link = $option['label_link'] ?? FALSE; + $this->drupalPostForm('admin/structure/block/manage/main', [ + 'settings[label_type]' => $option['option'], + 'settings[label_display]' => $label_display, + 'settings[label_link]' => $label_link, + ], 'Save block'); + $test_link = empty($option['test_link']) ? 'menu-block-test/hierarchy/parent/child-1/child-1-1' : $option['test_link']; + $this->drupalGet($test_link); + + // Find the h2 associated with the main menu block. + $block_label = $this->assertSession()->elementExists('css', 'h2#block-main-menu'); + // Check that the title is correct. + $this->assertEquals($option['title'], $block_label->getText(), "Test case '$case_id' should have the right title."); + // There is no notHasClass(), so we check for the "visually-hidden" class + // and invert it to determine if the block title is visible or not. + $visible = !$block_label->hasClass('visually-hidden'); + $this->assertEquals($label_display, $visible, "Test case '$case_id' should have the right visibility."); + + if ($label_link) { + $this->assertStringContainsString('<a href="', $block_label->getHtml(), "Test case '$case_id' should have a link in the block title."); + } + else { + $this->assertStringNotContainsString('<a href="', $block_label->getHtml(), "Test case '$case_id' should not have a link in the block title."); + } + } + } + +}