Skip to content
Snippets Groups Projects
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
No related branches found
No related tags found
25 merge requests!13399Edit robots.txt to disallow search pages without a trailing slash,!13385Issue #3549904 by xiff: Taxonomy term [term:published_status] token not available,!13375Issue #3535199 by alexpott, godotislate: Saving untrusted config results in memory leak,!13373ckeditor5.api.php fix @encode,!13234Issue #3533030 by catch, mondrake: Move QuickStart tests to build tests,!13231Issue #3533030 by catch, mondrake: Move QuickStart tests to build tests,!13182Provide offending asset path in asset preprocess error messages.,!13164Issue #3545027 Fixed typehints in ContextDefinition's $label and $description.,!13121Issue #3530262 by mohit_aghera, kim.pepper, quietone, xjm: Add mohit_aghera as...,!13086Issue #3525642 by amateescu, smustgrave: The active workspace is not persisted...,!13015Issue #3465041 by prudloff, xjm, smustgrave, larowlan:...,!12812Issue #3527142 by andypost, catch, longwave, xjm: Update Composer and...,!12811Issue #3527142 by andypost, catch, longwave, xjm: Update Composer and...,!12686Draft: Issue #3535330: Assets paths in CSS no longer rewritten when aggregation is enabled,!12661Issue #3255804 by godotislate, longwave, berdir, alexpott, catch, andypost,...,!12660Issue #3255804 by godotislate, longwave, berdir, alexpott, catch, andypost,...,!12618Issue #3522970 by longwave, smustgrave: Remove unused BrowserTestBase::$originalContainer,!12486Resolve #3532360 "Check for session",!12473Issue #3521639 by mstrelan, smustgrave for 11.2,!12462Issue #3523109 by ghost of drupal past, donquixote, nicxvan, dww, larowlan,...,!9182Representative Node Views fails due to invalid SQL,!9103Issue #3150540 by kriboogh: Configuration langcode is forced to site default language,!8811Issue #3129179: Provide some way to rebuild the persistent bundle field map,!7916Remove taxonomy dependency on node module - 11.x,!4367Issue #2671162: Also use text editor (CKEditor) for "summary" of a text field
Pipeline #521362 passed with warnings
Pipeline: drupal

#521367

    ......@@ -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
    <?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;
    }
    }
    ......@@ -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);
    ......
    ......@@ -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);
    ......
    <?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;
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Please to comment