diff --git a/core/modules/shortcut/tests/src/Functional/ShortcutCacheTagsTest.php b/core/modules/shortcut/tests/src/Functional/ShortcutCacheTagsTest.php index e479610aef..3d8ad12801 100644 --- a/core/modules/shortcut/tests/src/Functional/ShortcutCacheTagsTest.php +++ b/core/modules/shortcut/tests/src/Functional/ShortcutCacheTagsTest.php @@ -159,13 +159,13 @@ public function testToolbar() { $this->drupalLogin($site_configuration_user1); $this->verifyDynamicPageCache($test_page_url, 'MISS'); $this->verifyDynamicPageCache($test_page_url, 'HIT'); - $this->assertCacheContexts(['user', 'url.query_args:_wrapper_format']); + $this->assertCacheContexts(['user', 'url.query_args:_wrapper_format', 'route']); $this->assertSession()->linkExists('Shortcuts'); $this->assertSession()->linkExists('Cron'); $this->drupalLogin($site_configuration_user2); $this->verifyDynamicPageCache($test_page_url, 'HIT'); - $this->assertCacheContexts(['user', 'url.query_args:_wrapper_format']); + $this->assertCacheContexts(['user', 'url.query_args:_wrapper_format', 'route']); $this->assertSession()->linkExists('Shortcuts'); $this->assertSession()->linkExists('Cron'); diff --git a/core/modules/toolbar/src/Controller/ToolbarController.php b/core/modules/toolbar/src/Controller/ToolbarController.php index c94e35b307..6405955424 100644 --- a/core/modules/toolbar/src/Controller/ToolbarController.php +++ b/core/modules/toolbar/src/Controller/ToolbarController.php @@ -90,7 +90,11 @@ public static function preRenderAdministrationTray(array $element) { * @internal */ public static function preRenderGetRenderedSubtrees(array $data) { + $output = ''; + /** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */ $menu_tree = \Drupal::service('toolbar.menu_tree'); + /** @var \Drupal\Core\Menu\LocalTaskManagerInterface $local_task_manager */ + $local_task_manager = \Drupal::service('plugin.manager.menu.local_task'); // Load the administration menu. The first level is the "Administration" // link. In order to load the children of that link and the subsequent two // levels, start at the second level and end at the fourth. @@ -108,13 +112,97 @@ public static function preRenderGetRenderedSubtrees(array $data) { // Calculated the combined cacheability of all subtrees. $cacheability = new CacheableMetadata(); foreach ($tree as $element) { + // Get local tasks for this menu item and build render array. + $local_tasks = $local_task_manager->getTasksBuild($element->link->getRouteName(), $cacheability); /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ $link = $element->link; if ($element->subtree) { $subtree = $menu_tree->build($element->subtree); + if (count($local_tasks)) { + // Reduce the local tasks tree by elements already in the admin menu + // tree. + $local_tasks_subtree_reduced = []; + foreach ($local_tasks as $key => $tasks) { + foreach ($local_tasks[$key] as $task_key => $task) { + if (!isset($subtree['#items'][$task_key]) && $task_key != $element->link->getRouteName()) { + $local_tasks_subtree_reduced[$key][$task_key] = $task; + } + } + } + // Merge the local tasks into the existing 2nd level subtree. + if (count($local_tasks_subtree_reduced)) { + $local_tasks_subtree_build = _build_tasks_as_menu_tree($local_tasks_subtree_reduced); + $subtree['#items'] += $local_tasks_subtree_build['#items']; + } + } + foreach ($element->subtree as $sub_element) { + // Get local tasks for this sub menu item and build render array. + $sub_local_tasks = $local_task_manager->getTasksBuild($sub_element->link->getRouteName(), $cacheability); + $route_name = $sub_element->link->getRouteName(); + $route_name_parent = $element->link->getRouteName(); + if (count($sub_local_tasks)) { + $sub_local_tasks_subtree_reduced = []; + if (count($sub_element->subtree)) { + // Reduce the local tasks tree by elements already in the admin + // menu tree. + foreach ($sub_local_tasks as $key => $tasks) { + foreach ($sub_local_tasks[$key] as $task_key => $task) { + if (!isset($subtree['#items'][$task_key]) + && !isset($subtree['#items'][$route_name]['below'][$task_key]) + && $task_key != $route_name + && $task_key != $route_name_parent) { + $sub_local_tasks_subtree_reduced[$key][$task_key] = $task; + } + } + } + // Merge the local tasks into the existing 3rd level subtree. + if (count(current($sub_local_tasks_subtree_reduced))) { + $sub_local_tasks_subtree_build = _build_tasks_as_menu_tree($sub_local_tasks_subtree_reduced); + $subtree['#items'][$route_name]['below'] += $sub_local_tasks_subtree_build['#items']; + } + } + else { + // Reduce the local tasks tree by elements already in the admin + // menu tree. + foreach ($sub_local_tasks as $key => $tasks) { + foreach ($sub_local_tasks[$key] as $task_key => $task) { + if (!isset($subtree['#items'][$task_key]) && $task_key != $route_name && $task_key != $route_name_parent) { + $sub_local_tasks_subtree_reduced[$key][$task_key] = $task; + } + } + } + // Add the local tasks as new 3rd level subtree. + if (!empty(current($sub_local_tasks_subtree_reduced))) { + $sub_local_tasks_subtree_build = _build_tasks_as_menu_tree($sub_local_tasks_subtree_reduced); + $subtree['#items'][$route_name]['below'] = $sub_local_tasks_subtree_build['#items']; + } + } + } + } $output = \Drupal::service('renderer')->renderPlain($subtree); $cacheability = $cacheability->merge(CacheableMetadata::createFromRenderArray($subtree)); } + elseif (count($local_tasks)) { + // Reduce the local tasks tree by elements already in the admin menu + // tree. + $local_tasks_subtree_reduced = []; + foreach ($local_tasks as $key => $tasks) { + foreach ($local_tasks[$key] as $task_key => $task) { + if ($task_key != $element->link->getRouteName()) { + $local_tasks_subtree_reduced[$key][$task_key] = $task; + } + } + } + // Add the local tasks as new 2nd level subtree. + if (count($local_tasks_subtree_reduced)) { + $local_tasks_subtree_build = _build_tasks_as_menu_tree($local_tasks_subtree_reduced); + $output = \Drupal::service('renderer')->renderPlain($local_tasks_subtree_build); + $cacheability = $cacheability->merge(CacheableMetadata::createFromRenderArray($local_tasks_subtree_build)); + } + else { + $output = ''; + } + } else { $output = ''; } diff --git a/core/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.php b/core/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.php index 6f49871828..76441f6579 100644 --- a/core/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.php +++ b/core/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.php @@ -80,23 +80,23 @@ public function testCacheIntegration() { */ public function testToolbarCacheContextsCaller() { // Test with default combination and permission to see toolbar. - $this->assertToolbarCacheContexts(['user'], 'Expected cache contexts found for default combination and permission to see toolbar.'); + $this->assertToolbarCacheContexts(['user', 'route'], 'Expected cache contexts found for default combination and permission to see toolbar.'); // Test without user toolbar tab. User module is a required module so we have to // manually remove the user toolbar tab. $this->installExtraModules(['toolbar_disable_user_toolbar']); - $this->assertToolbarCacheContexts(['user.permissions'], 'Expected cache contexts found without user toolbar tab.'); + $this->assertToolbarCacheContexts(['user.permissions', 'route'], 'Expected cache contexts found without user toolbar tab.'); // Test with the toolbar and contextual enabled. $this->installExtraModules(['contextual']); $this->adminUser2 = $this->drupalCreateUser(array_merge($this->perms, ['access contextual links'])); - $this->assertToolbarCacheContexts(['user.permissions'], 'Expected cache contexts found with contextual module enabled.'); + $this->assertToolbarCacheContexts(['user.permissions', 'route'], 'Expected cache contexts found with contextual module enabled.'); \Drupal::service('module_installer')->uninstall(['contextual']); // Test with the tour module enabled. $this->installExtraModules(['tour']); $this->adminUser2 = $this->drupalCreateUser(array_merge($this->perms, ['access tour'])); - $this->assertToolbarCacheContexts(['user.permissions'], 'Expected cache contexts found with tour module enabled.'); + $this->assertToolbarCacheContexts(['user.permissions', 'route'], 'Expected cache contexts found with tour module enabled.'); \Drupal::service('module_installer')->uninstall(['tour']); }