Skip to content

Instantly share code, notes, and snippets.

@opi
Created March 4, 2025 08:39
Show Gist options
  • Save opi/a1f0a1467e3a8ab40c4c5f1fc0c4cccd to your computer and use it in GitHub Desktop.
Save opi/a1f0a1467e3a8ab40c4c5f1fc0c4cccd to your computer and use it in GitHub Desktop.
Drupal block display menu siblings and children, with config
<?php
namespace Drupal\MY_MODULE_NAME\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Menu\MenuLinkTree;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Render\Markup;
use Drupal\Core\Url;
use Drupal\Core\Link;
/**
* Provides a 'Menu Siblings' block.
*
* @Block(
* id = "menu_siblings",
* admin_label = @Translation("Menu Siblings"),
* )
*/
class MenuSiblings extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The entity type manager service.
*
* @var \Drupal\Core\Menu\MenuLinkTree
*/
protected $menuTree;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = new static($configuration, $plugin_id, $plugin_definition);
$instance->menuTree = $container->get('menu.link_tree');
return $instance;
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
$form = parent::blockForm($form, $form_state);
$config = $this->getConfiguration();
$form['display_children'] = [
'#title' => $this->t('Display children ?'),
'#type' => 'checkbox',
'#default_value' => $config['display_children'] ?? FALSE,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
parent::blockSubmit($form, $form_state);
$values = $form_state->getValues();
$this->configuration['display_children'] = $values['display_children'];
}
/**
* {@inheritdoc}
*/
public function build() {
$config = $this->getConfiguration();
$depth = (!empty($config['display_children']) && $config['display_children']) ? 2 : 1;
$menu = $this->getMainMenu($depth);
if (!empty($menu)) {
// Override theme suggestions
$menu['#theme'] = 'menu__arrow';
return [
'menu' => $menu,
];
}
}
private function getMainMenu($depth = 1) {
// This one will give us the active trail in *reverse order*.
// Our current active link always will be the first array element.
$parameters = $this->menuTree->getCurrentRouteMenuTreeParameters('main');
$active_trail = array_keys($parameters->activeTrail);
// But actually we need its parent.
// Except for <front>. Which has no parent.
$parent_link_id = isset($active_trail[1]) ? $active_trail[1] : $active_trail[0];
// Having the parent now we set it as starting point to build our custom
// tree.
$parameters->onlyEnabledLinks();
$parameters->setRoot($parent_link_id);
$parameters->setMaxDepth($depth);
$parameters->excludeRoot();
$tree = $this->menuTree->load('main', $parameters);
// Sort by weight
$manipulators = [
['callable' => 'menu.default_tree_manipulators:generateIndexAndSort']
];
$tree = $this->menuTree->transform($tree, $manipulators);
// Finally, build a renderable array and enable proper caching.
return $this->menuTree->build($tree);
}
public function getCacheTags() {
return Cache::mergeTags(parent::getCacheTags(), []);
}
public function getCacheContexts() {
return Cache::mergeContexts(parent::getCacheContexts(), [
'languages:language_content',
'route',
'url.query_args'
]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment