Commit 7a689709 authored by catch's avatar catch
Browse files

Issue #3480321 by scott_euser, godotislate, catch, grimreaper, kensae,...

Issue #3480321 by scott_euser, godotislate, catch, grimreaper, kensae, joelpittet, pameeela, lauriii, m4olivei, michaellander, aaronmchale, smustgrave, anruether, ckrina, plopesc, finnsky, xjm, berdir: Second level menu items can't be reached if they have children

(cherry picked from commit 25f4ab84)
parent b1240e88
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -45,3 +45,6 @@ services:
    class: Drupal\navigation\TopBarItemManager
    parent: default_plugin_manager
  Drupal\navigation\TopBarItemManagerInterface: '@plugin.manager.top_bar_item'

  Drupal\navigation\Menu\NavigationMenuLinkTreeManipulators:
    autowire: true
+159 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\navigation\Menu;

use Drupal\Core\Menu\MenuLinkDefault;
use Drupal\Core\Menu\MenuLinkTreeElement;
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\system\Controller\SystemController;
use Symfony\Component\Routing\Exception\RouteNotFoundException;

/**
 * Provides a menu link tree manipulator for the navigation menu block.
 */
class NavigationMenuLinkTreeManipulators {

  use StringTranslationTrait;

  public function __construct(
    protected readonly RouteProviderInterface $routeProvider,
    protected readonly StaticMenuLinkOverridesInterface $overrides,
    TranslationInterface $translation,
  ) {
    $this->setStringTranslation($translation);
  }

  /**
   * Adds an "overview" child link to second level menu links with children.
   *
   * In the navigation menu, a second-level menu item is a link if it does not
   * have children, but if it does, it instead becomes a button that opens
   * its child menu. To provide a way to access the page a second-level menu
   * item links to, add an "overview" link that links to the page as a child
   * (third-level) menu item.
   *
   * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
   *   The menu link tree to manipulate.
   *
   * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
   *   The manipulated menu link tree.
   */
  public function addSecondLevelOverviewLinks(array $tree): array {
    if (!$tree) {
      return [];
    }

    foreach ($tree as $item) {
      if (!$this->isEnabledAndAccessible($item)) {
        continue;
      }
      foreach ($item->subtree as $sub_item) {
        if ($this->shouldAddOverviewLink($sub_item)) {
          $this->addOverviewLink($sub_item);
        }
      }
    }

    return $tree;
  }

  /**
   * Whether a menu tree element should have an overview link added to it.
   *
   * @param \Drupal\Core\Menu\MenuLinkTreeElement $element
   *   The menu link tree element to check.
   *
   * @return bool
   *   TRUE if menu tree element should have a child overview link added.
   */
  protected function shouldAddOverviewLink(MenuLinkTreeElement $element): bool {
    if (empty($element->subtree) || !$this->isEnabledAndAccessible($element)) {
      return FALSE;
    }

    $route_name = $element->link->getRouteName();
    if (in_array($route_name, ['<nolink>', '<button>'])) {
      return FALSE;
    }

    $has_visible_children = FALSE;
    foreach ($element->subtree as $sub_element) {
      // Do not add overview link if there are no accessible or enabled
      // children.
      if ($this->isEnabledAndAccessible($sub_element)) {
        $has_visible_children = TRUE;
      }

      // Do not add overview link if there is already a child linking to the
      // same URL.
      if ($sub_element->link->getRouteName() === $route_name) {
        return FALSE;
      }
    }

    if (!$has_visible_children) {
      return FALSE;
    }

    // The systemAdminMenuBlockPage() method in SystemController returns a list
    // of child menu links for the page. If the second-level menu item link's
    // route uses that controller, do not add the overview link, because that
    // duplicates what is already in the navigation menu.
    try {
      $controller = ltrim($this->routeProvider->getRouteByName($route_name)->getDefault('_controller') ?? '', "\\");
      return $controller !== SystemController::class . '::systemAdminMenuBlockPage';
    }
    catch (RouteNotFoundException) {
      return TRUE;
    }
  }

  /**
   * Checks whether the menu link tree element is accessible and enabled.
   *
   * Generally, the 'checkAccess' manipulator should run before this manipulator
   * does, so the access objects should be set on all the links, but if it is
   * not, treat the link as accessible for the purpose of adding the overview
   * child link.
   *
   * @param \Drupal\Core\Menu\MenuLinkTreeElement $element
   *   The menu link tree element to be checked.
   *
   * @return bool
   *   TRUE if the menu link tree element is enabled and has access allowed.
   */
  protected function isEnabledAndAccessible(MenuLinkTreeElement $element): bool {
    return $element->link->isEnabled() && (!isset($element->access) || $element->access->isAllowed());
  }

  /**
   * Adds "overview" menu tree element as child of a menu tree element.
   *
   * @param \Drupal\Core\Menu\MenuLinkTreeElement $element
   *   The menu link tree element to add the overview child element to.
   */
  protected function addOverviewLink(MenuLinkTreeElement $element): void {
    // Copy the menu link for the menu link element to a new menu link
    // definition, except with overrides to make 'Overview' the title, set the
    // parent to the original menu link, and set weight to a low number so that
    // it likely goes to the top.
    $definition = [
      'title' => $this->t('Overview', [
        '@title' => $element->link->getTitle(),
      ]),
      'parent' => $element->link->getPluginId(),
      'provider' => 'navigation',
      'weight' => -1000,
    ] + $element->link->getPluginDefinition();
    $link = new MenuLinkDefault([], $element->link->getPluginId() . '.navigation_overview', $definition, $this->overrides);
    $overview_element = new MenuLinkTreeElement($link, FALSE, $element->depth + 1, $element->inActiveTrail, []);
    $overview_element->access = $element->access ? clone $element->access : NULL;
    $element->subtree[$element->link->getPluginId() . '.navigation_overview'] = $overview_element;
  }

}
+2 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\navigation\Menu\NavigationMenuLinkTreeManipulators;
use Drupal\navigation\Plugin\Derivative\SystemMenuNavigationBlock as SystemMenuNavigationBlockDeriver;
use Drupal\system\Plugin\Block\SystemMenuBlock;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -85,6 +86,7 @@ public function build(): array {
    $tree = $this->menuTree->load($menu_name, $parameters);
    $manipulators = [
      ['callable' => 'menu.default_tree_manipulators:checkAccess'],
      ['callable' => NavigationMenuLinkTreeManipulators::class . ':addSecondLevelOverviewLinks'],
      ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
    ];
    $tree = $this->menuTree->transform($tree, $manipulators);
+60 −4
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
use Drupal\Core\Routing\UrlGenerator;
use Drupal\KernelTests\KernelTestBase;
use Drupal\navigation\Plugin\Block\NavigationMenuBlock;
use Drupal\system\Controller\SystemController;
use Drupal\system\Entity\Menu;
use Drupal\system\Tests\Routing\MockRouteProvider;
use Drupal\Tests\Core\Menu\MenuLinkMock;
@@ -106,9 +107,16 @@ protected function setUp(): void {
    $options = ['_access_checks' => ['access_check.default']];
    $special_options = $options + ['_no_path' => TRUE];
    $routes->add('example2', new Route('/example2', [], $requirements, $options));
    $routes->add('example4', new Route('/example4', [], $requirements, $options));
    $routes->add('example4', new Route('/example4', ['_controller' => SystemController::class . '::systemAdminMenuBlockPage'], $requirements, $options));
    $routes->add('example9', new Route('/example9', [], $requirements, $options));
    $routes->add('example11', new Route('/example11', [], $requirements, $options));
    $routes->add('example11', new Route('/example11', ['_controller' => SystemController::class . '::systemAdminMenuBlockPage'], $requirements, $options));
    $routes->add('example13', new Route('/example13', [], $requirements, $options));
    $routes->add('example14', new Route('/example14', [], $requirements, $options));
    $routes->add('example15', new Route('/example15', [], $requirements, $options));
    $routes->add('example16', new Route('/example16', [], $requirements, $options));
    $routes->add('example17', new Route('/example17', [], $requirements, $options));
    $routes->add('example18', new Route('/example18', [], $requirements, $options));
    $routes->add('example19', new Route('/example19', [], ['_access' => 'FALSE'], $options));

    // Mock special routes defined in system.routing.yml.
    $routes->add('<nolink>', new Route('', [], $requirements, $special_options));
@@ -144,15 +152,23 @@ protected function setUp(): void {
    // - 1 (nolink)
    // - 2
    //   - 3 (nolink)
    //     - 4
    //     - 4 (list of child links)
    //       - 9
    // - 5 (button)
    //   - 7 (button)
    //     - 10 (nolink)
    // - 6
    // - 8 (nolink)
    //   - 11
    //   - 11 (list of child links)
    //     - 12 (button)
    // - 13
    //   - 14 (not a list of child links)
    //     - 15
    // - 16
    //   - 17
    //     - 18 (disabled)
    //     - 19 (access denied)
    //     - 20 (links to same routed URL as 17)
    // With link 6 being the only external link.
    // phpcs:disable
    $links = [
@@ -168,6 +184,14 @@ protected function setUp(): void {
      10 => MenuLinkMock::create(['id' => 'test.example10', 'route_name' => '<nolink>', 'title' => 'title 10', 'parent' => 'test.example7', 'weight' => 7]),
      11 => MenuLinkMock::create(['id' => 'test.example11', 'route_name' => 'example11', 'title' => 'title 11', 'parent' => 'test.example8', 'weight' => 7]),
      12 => MenuLinkMock::create(['id' => 'test.example12', 'route_name' => '<button>', 'title' => 'title 12', 'parent' => 'test.example11', 'weight' => 7]),
      13 => MenuLinkMock::create(['id' => 'test.example13', 'route_name' => 'example13', 'title' => 'title 13', 'parent' => '', 'weight' => 8]),
      14 => MenuLinkMock::create(['id' => 'test.example14', 'route_name' => 'example14', 'title' => 'title 14', 'parent' => 'test.example13', 'weight' => 8]),
      15 => MenuLinkMock::create(['id' => 'test.example15', 'route_name' => 'example15', 'title' => 'title 15', 'parent' => 'test.example14', 'weight' => 8]),
      16 => MenuLinkMock::create(['id' => 'test.example16', 'route_name' => 'example16', 'title' => 'title 16', 'parent' => '', 'weight' => 9]),
      17 => MenuLinkMock::create(['id' => 'test.example17', 'route_name' => 'example17', 'title' => 'title 17', 'parent' => 'test.example16', 'weight' => 9]),
      18 => MenuLinkMock::create(['id' => 'test.example18', 'route_name' => 'example18', 'title' => 'title 18', 'parent' => 'test.example17', 'weight' => 9, 'enabled' => FALSE]),
      19 => MenuLinkMock::create(['id' => 'test.example19', 'route_name' => 'example19', 'title' => 'title 19', 'parent' => 'test.example17', 'weight' => 9]),
      20 => MenuLinkMock::create(['id' => 'test.example20', 'route_name' => 'example17', 'title' => 'title 20', 'parent' => 'test.example17', 'weight' => 9]),
    ];
    // phpcs:enable
    foreach ($links as $instance) {
@@ -234,16 +258,22 @@ public function testConfigLevelDepth(): void {
      'test.example5' => [],
      'test.example6' => [],
      'test.example8' => [],
      'test.example13' => [],
      'test.example16' => [],
    ];
    $expectations['level_2_only'] = [
      'test.example3' => [],
      'test.example7' => [],
      'test.example11' => [],
      'test.example14' => [],
      'test.example17' => [],
    ];
    $expectations['level_3_only'] = [
      'test.example4' => [],
      'test.example10' => [],
      'test.example12' => [],
      'test.example15' => [],
      'test.example20' => [],
    ];
    $expectations['level_1_and_beyond'] = [
      'test.example1' => [],
@@ -263,6 +293,20 @@ public function testConfigLevelDepth(): void {
          'test.example12' => [],
        ],
      ],
      'test.example13' => [
        'test.example14' => [
          'test.example14.navigation_overview' => [],
          'test.example15' => [],
        ],
      ],
      'test.example16' => [
        // 17 only has inaccessible and disabled child links, and a child item
        // that links to the same url as 17, so there should be no overview link
        // child added.
        'test.example17' => [
          'test.example20' => [],
        ],
      ],
    ];
    $expectations['level_2_and_beyond'] = [
      'test.example3' => [
@@ -276,6 +320,12 @@ public function testConfigLevelDepth(): void {
      'test.example11' => [
        'test.example12' => [],
      ],
      'test.example14' => [
        'test.example15' => [],
      ],
      'test.example17' => [
        'test.example20' => [],
      ],
    ];
    $expectations['level_3_and_beyond'] = [
      'test.example4' => [
@@ -283,6 +333,8 @@ public function testConfigLevelDepth(): void {
      ],
      'test.example10' => [],
      'test.example12' => [],
      'test.example15' => [],
      'test.example20' => [],
    ];
    // Scenario 1: test all navigation block instances when there's no active
    // trail.
@@ -346,6 +398,10 @@ public function testHtmlMarkup(): void {
      "//li[contains(@class,'toolbar-menu__item--level-2')]/span[text()='title 10']",
      "//li[contains(@class,'toolbar-menu__item--level-1')]/button/span[text()='title 11']",
      "//li[contains(@class,'toolbar-menu__item--level-2')]/button[text()='title 12']",
      "//li[contains(@class,'toolbar-block__list-item')]/button/span[text()='title 13']",
      "//li[contains(@class,'toolbar-menu__item--level-1')]/button/span[text()='title 14']",
      "//li[contains(@class,'toolbar-menu__item--level-2')]/a[text()='Overview']",
      "//li[contains(@class,'toolbar-menu__item--level-1')]/button/span[text()='title 17']",
    ];
    foreach ($items_query as $query) {
      $span = $xpath->query($query);
+308 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\navigation\Unit;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Menu\MenuLinkTreeElement;
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\navigation\Menu\NavigationMenuLinkTreeManipulators;
use Drupal\system\Controller\SystemController;
use Drupal\Tests\Core\Menu\MenuLinkMock;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Routing\Route;

/**
 * Tests the navigation menu link tree manipulator.
 *
 * @group navigation
 *
 * @coversDefaultClass \Drupal\navigation\Menu\NavigationMenuLinkTreeManipulators
 */
class NavigationMenuLinkTreeManipulatorsTest extends UnitTestCase {

  /**
   * Tests the addSecondLevelOverviewLinks() tree manipulator.
   *
   * @covers ::addSecondLevelOverviewLinks
   */
  public function testAddSecondLevelOverviewLinks(): void {
    $routeProvider = $this->createMock(RouteProviderInterface::class);
    // For only the route named 'child_list', return a route object with the
    // SystemController::systemAdminMenuBlockPage as the controller.
    $childListRoute = new Route('/test-child-list', ['_controller' => SystemController::class . '::systemAdminMenuBlockPage']);
    $routeProvider->expects($this->any())
      ->method('getRouteByName')
      ->willReturnCallback(static fn ($name) => $name === 'child_list' ? $childListRoute : new Route("/$name"));
    $overrides = $this->createMock(StaticMenuLinkOverridesInterface::class);
    $translation = $this->createMock(TranslationInterface::class);
    $translation
      ->method('translateString')
      ->willReturnCallback(static fn ($string) => $string);
    $manipulator = new NavigationMenuLinkTreeManipulators($routeProvider, $overrides, $translation);

    $originalTree = $this->mockTree();
    // Make sure overview links do not already exist.
    $this->assertArrayNotHasKey('test.example3.navigation_overview', $originalTree[2]->subtree[3]->subtree);
    $this->assertArrayNotHasKey('test.example6.navigation_overview', $originalTree[5]->subtree[6]->subtree);
    $tree = $manipulator->addSecondLevelOverviewLinks($originalTree);

    // First level menu items should not have any children added.
    $this->assertEmpty($tree[1]->subtree);
    $this->assertEquals($originalTree[2]->subtree, $tree[2]->subtree);
    $this->assertEquals($originalTree[5]->subtree, $tree[5]->subtree);
    $this->assertEquals($originalTree[8]->subtree, $tree[8]->subtree);
    $this->assertEquals($originalTree[11]->subtree, $tree[11]->subtree);
    $this->assertEquals($originalTree[13]->subtree, $tree[13]->subtree);
    $this->assertEquals($originalTree[16]->subtree, $tree[16]->subtree);
    $this->assertEquals($originalTree[19]->subtree, $tree[19]->subtree);

    // Leaves should not have any children added.
    $this->assertEmpty($tree[2]->subtree[3]->subtree[4]->subtree);
    $this->assertEmpty($tree[5]->subtree[6]->subtree[7]->subtree);
    $this->assertEmpty($tree[8]->subtree[9]->subtree[10]->subtree);
    $this->assertEmpty($tree[11]->subtree[12]->subtree);
    $this->assertEmpty($tree[13]->subtree[14]->subtree[15]->subtree);
    $this->assertEmpty($tree[16]->subtree[17]->subtree[18]->subtree);
    $this->assertEmpty($tree[19]->subtree[20]->subtree[21]->subtree);
    $this->assertEmpty($tree[19]->subtree[20]->subtree[22]->subtree);

    // Links 3 and 6 should have overview children, even though 6 is unrouted.
    $this->assertArrayHasKey('test.example3.navigation_overview', $tree[2]->subtree[3]->subtree);
    $this->assertArrayHasKey('test.example6.navigation_overview', $tree[5]->subtree[6]->subtree);

    // Link 9 is a child list page, so it should not have an overview child.
    $this->assertArrayNotHasKey('test.example9.navigation_overview', $tree[8]->subtree[9]->subtree);

    // Link 14 and Link 17 are <nolink> and <button> routes, so they should not
    // have overview children.
    $this->assertArrayNotHasKey('test.example14.navigation_overview', $tree[13]->subtree[14]->subtree);
    $this->assertArrayNotHasKey('test.example17.navigation_overview', $tree[16]->subtree[17]->subtree);

    // Link 20's child links are either inaccessible, disabled, or link to the
    // same route as 20, so it should not have an overview child.
    $this->assertArrayNotHasKey('test.example20.navigation_overview', $tree[19]->subtree[20]->subtree);
  }

  /**
   * Creates a mock tree.
   *
   * This mocks a tree with the following structure:
   * - 1
   * - 2
   *   - 3
   *     - 4
   * - 5
   *   - 6 (external)
   *     - 7
   * - 8
   *   - 9
   *     - 10
   * - 11
   *   - 12
   * - 13
   *   - 14 (nolink)
   *     - 15
   * - 16
   *   - 17 (button)
   *     - 18
   * - 19
   *   - 20
   *    - 21 (disabled)
   *    - 22 (access denied)
   *    - 23 (links to same routed URL as 20)
   *
   * With link 9 linking to a page that contains a list of child menu links.
   *
   * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
   *   The mock menu tree.
   */
  protected function mockTree(): array {
    $links = [
      1 => MenuLinkMock::create([
        'id' => 'test.example1',
        'route_name' => 'example1',
        'title' => 'foo',
        'parent' => '',
      ]),
      2 => MenuLinkMock::create([
        'id' => 'test.example2',
        'route_name' => 'example2',
        'title' => 'foo',
        'parent' => '',
      ]),
      3 => MenuLinkMock::create([
        'id' => 'test.example3',
        'route_name' => 'example3',
        'title' => 'baz',
        'parent' => 'test.example2',
      ]),
      4 => MenuLinkMock::create([
        'id' => 'test.example4',
        'route_name' => 'example4',
        'title' => 'qux',
        'parent' => 'test.example3',
      ]),
      5 => MenuLinkMock::create([
        'id' => 'test.example5',
        'route_name' => 'example5',
        'title' => 'title5',
        'parent' => '',
      ]),
      6 => MenuLinkMock::create([
        'id' => 'test.example6',
        'route_name' => '',
        'url' => 'https://www.drupal.org/',
        'title' => 'bar_bar',
        'parent' => 'test.example5',
      ]),
      7 => MenuLinkMock::create([
        'id' => 'test.example7',
        'route_name' => 'example7',
        'title' => 'title7',
        'parent' => 'test.example6',
      ]),
      8 => MenuLinkMock::create([
        'id' => 'test.example8',
        'route_name' => 'example8',
        'title' => 'title8',
        'parent' => '',
      ]),
      9 => MenuLinkMock::create([
        'id' => 'test.example9',
        'route_name' => 'child_list',
        'title' => 'title9',
        'parent' => 'test.example8',
      ]),
      10 => MenuLinkMock::create([
        'id' => 'test.example10',
        'route_name' => 'example9',
        'title' => 'title10',
        'parent' => 'test.example9',
      ]),
      11 => MenuLinkMock::create([
        'id' => 'test.example11',
        'route_name' => 'example11',
        'title' => 'title11',
        'parent' => '',
      ]),
      12 => MenuLinkMock::create([
        'id' => 'test.example12',
        'route_name' => 'example12',
        'title' => 'title12',
        'parent' => 'text.example11',
      ]),
      13 => MenuLinkMock::create([
        'id' => 'test.example13',
        'route_name' => 'example13',
        'title' => 'title13',
        'parent' => '',
      ]),
      14 => MenuLinkMock::create([
        'id' => 'test.example14',
        'route_name' => '<nolink>',
        'title' => 'title14',
        'parent' => 'text.example13',
      ]),
      15 => MenuLinkMock::create([
        'id' => 'test.example15',
        'route_name' => 'example15',
        'title' => 'title15',
        'parent' => 'text.example14',
      ]),
      16 => MenuLinkMock::create([
        'id' => 'test.example16',
        'route_name' => 'example16',
        'title' => 'title16',
        'parent' => '',
      ]),
      17 => MenuLinkMock::create([
        'id' => 'test.example17',
        'route_name' => '<button>',
        'title' => 'title17',
        'parent' => 'text.example16',
      ]),
      18 => MenuLinkMock::create([
        'id' => 'test.example18',
        'route_name' => 'example18',
        'title' => 'title18',
        'parent' => 'text.example17',
      ]),
      19 => MenuLinkMock::create([
        'id' => 'test.example19',
        'route_name' => 'example19',
        'title' => 'title19',
        'parent' => '',
      ]),
      20 => MenuLinkMock::create([
        'id' => 'test.example20',
        'route_name' => 'example20',
        'title' => 'title20',
        'parent' => 'test.example19',
      ]),
      21 => MenuLinkMock::create([
        'id' => 'test.example21',
        'route_name' => 'example21',
        'title' => 'title21',
        'parent' => 'test.example20',
        'enabled' => FALSE,
      ]),
      22 => MenuLinkMock::create([
        'id' => 'test.example22',
        'route_name' => 'no_access',
        'title' => 'title22',
        'parent' => 'test.example20',
      ]),
      23 => MenuLinkMock::create([
        'id' => 'test.example23',
        'route_name' => 'example20',
        'title' => 'title23',
        'parent' => 'test.example20',
      ]),
    ];
    $tree = [];
    $tree[1] = new MenuLinkTreeElement($links[1], FALSE, 1, FALSE, []);
    $tree[2] = new MenuLinkTreeElement($links[2], TRUE, 1, FALSE, [
      3 => new MenuLinkTreeElement($links[3], TRUE, 2, FALSE, [
        4 => new MenuLinkTreeElement($links[4], FALSE, 3, FALSE, []),
      ]),
    ]);
    $tree[5] = new MenuLinkTreeElement($links[5], TRUE, 1, FALSE, [
      6 => new MenuLinkTreeElement($links[6], TRUE, 2, FALSE, [
        7 => new MenuLinkTreeElement($links[7], FALSE, 3, FALSE, []),
      ]),
    ]);
    $tree[8] = new MenuLinkTreeElement($links[8], TRUE, 1, FALSE, [
      9 => new MenuLinkTreeElement($links[9], TRUE, 2, FALSE, [
        10 => new MenuLinkTreeElement($links[10], FALSE, 3, FALSE, []),
      ]),
    ]);
    $tree[11] = new MenuLinkTreeElement($links[11], TRUE, 1, FALSE, [
      12 => new MenuLinkTreeElement($links[12], FALSE, 2, FALSE, []),
    ]);
    $tree[13] = new MenuLinkTreeElement($links[13], TRUE, 1, FALSE, [
      14 => new MenuLinkTreeElement($links[14], TRUE, 2, FALSE, [
        15 => new MenuLinkTreeElement($links[15], FALSE, 3, FALSE, []),
      ]),
    ]);
    $tree[16] = new MenuLinkTreeElement($links[16], TRUE, 1, FALSE, [
      17 => new MenuLinkTreeElement($links[17], TRUE, 2, FALSE, [
        18 => new MenuLinkTreeElement($links[18], FALSE, 3, FALSE, []),
      ]),
    ]);
    $tree[19] = new MenuLinkTreeElement($links[19], TRUE, 1, FALSE, [
      20 => new MenuLinkTreeElement($links[20], TRUE, 2, FALSE, [
        21 => new MenuLinkTreeElement($links[21], FALSE, 3, FALSE, []),
        22 => new MenuLinkTreeElement($links[22], FALSE, 3, FALSE, []),
        23 => new MenuLinkTreeElement($links[23], FALSE, 3, FALSE, []),
      ]),
    ]);
    $tree[19]->subtree[20]->subtree[22]->access = AccessResult::forbidden();

    return $tree;
  }

}