Join us at DrupalCon Singapore from 9-11 December 2024, for three exciting days of Drupal content, training, contributions, networking, and the inaugural DrupalCon Splash Awards! Be part of this landmark event as we celebrate and expand Drupal's impact across Asia.
Problem/Motivation
As per #3091246: Allow MenuLinkTree manipulators to be altered there are a number of use cases for altering MenuLinkTree manipulators. Sometimes you just want to provide custom access controls to menu links.
One way to achieve this is to decorate the menu.default_tree_manipulators
service. This works pretty well, but it means you need to implement all the public methods, including checkAccess, checkNodeAccess, generateIndexAndSort, and flatten. There is no guarantee that there won't be another method added in the future.
Steps to reproduce
mymodule.services.yml:
services:
Drupal\my_module\Menu\MyModuleMenuLinkTreeManipulators:
decorates: 'menu.default_tree_manipulators'
autowire: true
MyModuleMenuLinkTreeManipulators.php
<?php
declare(strict_types=1);
namespace Drupal\my_module\Menu;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Menu\DefaultMenuLinkTreeManipulators;
use Drupal\Core\Menu\MenuLinkTreeElement;
use Drupal\Core\Session\AccountInterface;
/**
* Decorates the menu.default_tree_manipulators service.
*
* @see \Drupal\Core\Menu\DefaultMenuLinkTreeManipulators
*/
final class MyModuleMenuLinkTreeManipulators {
public function __construct(
protected DefaultMenuLinkTreeManipulators $inner,
protected AccountInterface $account,
) {}
/**
* @see DefaultMenuLinkTreeManipulators::checkAccess()
*/
public function checkAccess(array $tree): array {
$tree = $this->inner->checkAccess($tree);
foreach ($tree as $element) {
$this->menuLinkCheckAccess($element);
}
return $tree;
}
/**
* Custom access check.
*/
protected function menuLinkCheckAccess(MenuLinkTreeElement $element): void {
if (isset($element->access) && $element->access->isForbidden()) {
return;
}
if ($element->link->getPluginId() === 'my_module.custom_menu_link') {
$element->access = AccessResult::allowedIfHasPermission($this->account, 'my module permission');
}
foreach ($element->subtree as $branch) {
$this->menuLinkCheckAccess($branch);
}
}
public function checkNodeAccess(array $tree): array {
return $this->inner->checkNodeAccess($tree);
}
public function generateIndexAndSort(array $tree): array {
return $this->inner->generateIndexAndSort($tree);
}
public function flatten(array $tree): array {
return $this->inner->flatten($tree);
}
}
Proposed resolution
- Add an interface for MenuLinkTreeManipulators
- Split each manipulator to its own class implementing this interface
Remaining tasks
Is this even viable?
Comments