Make it easier to decorate menu.default_tree_manipulators

Created on 19 September 2024, 7 months ago

Problem/Motivation

As per ✨ Allow MenuLinkTree manipulators to be altered Needs work 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?

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

✨ Feature request
Status

Active

Version

11.0 πŸ”₯

Component
Menu systemΒ  β†’

Last updated 1 day ago

Created by

πŸ‡¦πŸ‡ΊAustralia mstrelan

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024