Skip to content
Snippets Groups Projects
Commit 2c64eb5e authored by Pablo López's avatar Pablo López Committed by Cristina Chumillas
Browse files

Resolve #3415576 "Create menu navigation block deriver"

parent c9923730
No related branches found
No related tags found
1 merge request!167Resolve #3415576 "Create menu navigation block deriver"
Pipeline #96628 passed with warnings
langcode: en
status: true
dependencies: { }
id: admin
dependencies:
config:
- system.menu.admin
id: administration_menu
region: content
weight: 5
provider: navigation
plugin: admin
weight: 2
provider: null
plugin: 'system_menu_navigation_block:admin'
settings:
id: admin
id: 'system_menu_navigation_block:admin'
label: Administration
label_display: '0'
provider: navigation
status: true
info: ''
view_mode: default
level: 2
depth: 2
visibility: { }
langcode: en
status: true
dependencies: { }
id: content
dependencies:
config:
- system.menu.content
id: content_menu
region: content
weight: 2
provider: navigation
plugin: content
weight: 1
provider: null
plugin: 'system_menu_navigation_block:content'
settings:
id: content
id: 'system_menu_navigation_block:content'
label: Content
label_display: '0'
provider: navigation
status: true
info: ''
view_mode: default
level: 1
depth: 2
visibility: { }
......@@ -41,3 +41,14 @@ navigation.navigation_block.*:
sequence:
type: condition.plugin.[id]
label: 'Visibility Condition'
navigation_block.settings.system_menu_navigation_block:*:
type: navigation_block_settings
label: 'Menu navigation block'
mapping:
level:
type: integer
label: 'Starting level'
depth:
type: integer
label: 'Maximum number of levels'
......@@ -14,3 +14,10 @@ services:
class: Drupal\navigation\NavigationRenderer
arguments: ['@navigation_block.repository', '@config.factory', '@current_route_match', '@plugin.manager.menu.local_task', '@current_user', '@entity_type.manager']
Drupal\navigation\NavigationRenderer: '@navigation.renderer'
navigation.menu.invalidator:
class: Drupal\navigation\Cache\SystemMenuNavigationBlockCacheTagInvalidator
arguments: [ '@plugin.manager.navigation_block_manager', '@entity_type.manager' ]
public: false
tags:
- { name: cache_tags_invalidator }
<?php
declare(strict_types=1);
namespace Drupal\navigation\Cache;
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\navigation\NavigationBlockManagerInterface;
/**
* Invalidates navigation block cache when menus are updated.
*/
class SystemMenuNavigationBlockCacheTagInvalidator implements CacheTagsInvalidatorInterface {
/**
* Constructs a new SystemMenuNavigationBlockCacheTagInvalidator.
*
* @param \Drupal\navigation\NavigationBlockManagerInterface $navigationBlockManager
* The navigation block manager.
*/
public function __construct(protected NavigationBlockManagerInterface $navigationBlockManager, protected EntityTypeManagerInterface $entityTypeManager) {
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
// Ensure that definitions cache is cleared when menus are updated.
try {
$menu_tags = $this->entityTypeManager->getDefinition('menu')
->getListCacheTags();
}
catch (PluginNotFoundException $e) {
return;
}
if (!empty(array_intersect($menu_tags, $tags))) {
return;
}
if ($this->navigationBlockManager instanceof CachedDiscoveryInterface) {
$this->navigationBlockManager->clearCachedDefinitions();
}
}
}
<?php
declare(strict_types=1);
namespace Drupal\navigation\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides navigation block plugin definitions for custom menus.
*
* @see \Drupal\navigation\Plugin\NavigationBlock\SystemMenuNavigationBlock
*/
class SystemMenuNavigationBlock extends DeriverBase implements ContainerDeriverInterface {
/**
* Constructs new SystemMenuNavigationBlock.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $menuStorage
* The menu storage.
*/
public function __construct(protected EntityStorageInterface $menuStorage) {}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id): static {
return new static(
$container->get('entity_type.manager')->getStorage('menu')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition): array {
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'] = [$entity->getConfigDependencyName()];
}
return $this->derivatives;
}
}
<?php
namespace Drupal\navigation\Plugin\NavigationBlock;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\navigation\Attribute\NavigationBlock;
use Drupal\navigation\NavigationBlockPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a content navigation block.
*/
#[NavigationBlock(
id: 'content',
admin_label: new TranslatableMarkup('Content'),
)]
class ContentNavigationBlock extends NavigationBlockPluginBase implements ContainerFactoryPluginInterface {
/**
* Constructs a ContentNavigationBlock object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menuLinkTree
* The menu link tree.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, protected MenuLinkTreeInterface $menuLinkTree) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@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')
);
}
/**
* {@inheritdoc}
*/
public function build(): array {
$parameters = new MenuTreeParameters();
$parameters->setMinDepth(1)->setMaxDepth(4)->onlyEnabledLinks();
$tree = $this->menuLinkTree->load('content', $parameters);
$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
['callable' => 'navigation_menu_navigation_links'],
];
$tree = $this->menuLinkTree->transform($tree, $manipulators);
$build = $this->menuLinkTree->build($tree);
if (!empty($build['#items'])) {
/** @var \Drupal\Core\Menu\MenuLinkInterface $link */
$first_link = reset($tree)->link;
// Get the menu name of the first link.
$menu_name = $first_link->getMenuName();
$build['#menu_name'] = $menu_name;
$build['#theme'] = 'menu_region__content';
// Loop through menu items and add the plugin id as a class.
foreach ($tree as $item) {
if ($item->access->isAllowed()) {
$plugin_id = $item->link->getPluginId();
$plugin_class = str_replace('.', '_', $plugin_id);
$build['#items'][$plugin_id]['class'] = $plugin_class;
}
}
$build['#title'] = $this->configuration['label'];
}
return $build;
}
}
<?php
declare(strict_types=1);
namespace Drupal\navigation\Plugin\NavigationBlock;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuLinkTreeInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\navigation\Attribute\NavigationBlock;
use Drupal\navigation\NavigationBlockPluginBase;
use Drupal\navigation\Plugin\Derivative\SystemMenuNavigationBlock as SystemMenuNavigationBlockDeriver;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines an admin navigation block.
* Provides a generic menu navigation block.
*/
#[NavigationBlock(
id: 'admin',
admin_label: new TranslatableMarkup('Administration'),
id: "system_menu_navigation_block",
admin_label: new TranslatableMarkup("Menu"),
category: new TranslatableMarkup("Menus"),
deriver: SystemMenuNavigationBlockDeriver::class,
)]
class AdminNavigationBlock extends NavigationBlockPluginBase implements ContainerFactoryPluginInterface {
class SystemMenuNavigationBlock extends NavigationBlockPluginBase implements ContainerFactoryPluginInterface {
const NAVIGATION_MAX_DEPTH = 3;
/**
* Constructs a AdminNavigationBlock object.
* Constructs a new SystemMenuNavigationBlock.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Menu\MenuLinkTreeInterface $menuLinkTree
* The menu link tree.
* The menu tree service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, protected MenuLinkTreeInterface $menuLinkTree) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
......@@ -38,7 +46,7 @@ class AdminNavigationBlock extends NavigationBlockPluginBase implements Containe
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
......@@ -50,10 +58,81 @@ class AdminNavigationBlock extends NavigationBlockPluginBase implements Containe
/**
* {@inheritdoc}
*/
public function build(): array {
public function defaultConfiguration(): array {
return [
'level' => 1,
'depth' => 0,
];
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state): array {
$config = $this->configuration;
$defaults = $this->defaultConfiguration();
$form['menu_levels'] = [
'#type' => 'details',
'#title' => $this->t('Menu levels'),
// Open if not set to defaults.
'#open' => $defaults['level'] !== $config['level'] || $defaults['depth'] !== $config['depth'],
'#process' => [[self::class, 'processMenuLevelParents']],
];
$level_options = range(0, $this->menuLinkTree->maxDepth());
unset($level_options[0]);
$form['menu_levels']['level'] = [
'#type' => 'select',
'#title' => $this->t('Initial visibility level'),
'#default_value' => $config['level'],
'#options' => $level_options,
'#description' => $this->t('The menu is visible only starting from that level.'),
'#required' => TRUE,
];
$form['menu_levels']['depth'] = [
'#type' => 'select',
'#title' => $this->t('Number of levels to display'),
'#default_value' => $config['depth'],
'#options' => range(1, static::NAVIGATION_MAX_DEPTH),
'#description' => $this->t('This maximum number includes the initial level.'),
'#required' => TRUE,
];
return $form;
}
/**
* Form API callback: Processes the menu_levels field element.
*
* Adjusts the #parents of menu_levels to save its children at the top level.
*/
public static function processMenuLevelParents(&$element, FormStateInterface $form_state, &$complete_form): array {
array_pop($element['#parents']);
return $element;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state): void {
$this->configuration['level'] = $form_state->getValue('level');
$this->configuration['depth'] = $form_state->getValue('depth');
}
/**
* {@inheritdoc}
*/
public function build() {
$menu_name = $this->getDerivativeId();
$parameters = new MenuTreeParameters();
$parameters->setMinDepth(2)->setMaxDepth(4)->onlyEnabledLinks();
$tree = $this->menuLinkTree->load('admin', $parameters);
$parameters
->setMinDepth($this->configuration['level'])
->setMaxDepth($this->configuration['level'] + $this->configuration['depth'])
->onlyEnabledLinks();
$tree = $this->menuLinkTree->load($menu_name, $parameters);
$manipulators = [
['callable' => 'menu.default_tree_manipulators:checkAccess'],
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment