diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php index 171a8d48779eb165e2c2f2c44a0bdee50bc124e4..b70e77fbce5df31bded466dd333bf05e1673c216 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php @@ -360,6 +360,17 @@ public function getLocalTasks($route_name, $level = 0) { foreach ($local_tasks as $tab_level => $items) { $data[$tab_level] = empty($data[$tab_level]) ? $items : array_merge($data[$tab_level], $items); } + + // Sort by weight and alphabetically if weights are the same. + foreach ($data as $key => $values) { + $weights = array_column($values, '#weight'); + array_multisort( + $weights, SORT_ASC, SORT_NUMERIC, + array_keys($values), SORT_ASC, SORT_NATURAL, + $data[$key], + ); + } + $this->taskData[$route_name]['tabs'] = $data; // Allow modules to alter local tasks. $this->moduleHandler->alter('menu_local_tasks', $this->taskData[$route_name], $route_name, $cacheability); diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php index 6a35e40e1da07d0822e6f42537d5a7266e5ada4f..713cfb819c2730a25fd1752ee8be66651fbc5b5c 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php @@ -480,4 +480,117 @@ protected function setupNullCacheabilityMetadataValidation(): void { \Drupal::setContainer($container); } + /** + * Tests the getLocalTasksForRoute method. + * + * @dataProvider providerTestGetLocalTasks + */ + public function testGetLocalTasks($new_weights, $expected): void { + $definitions = $this->getLocalTaskFixtures(); + + // Add another child, that will be first in an alphabetical sort. + $definitions['menu_local_task_test_tasks_view_a_child'] = [ + 'route_name' => 'menu_local_task_test_tasks_a_child_page', + 'title' => 'Settings child a_child', + 'parent_id' => 'menu_local_task_test_tasks_view.tab', + 'id' => 'menu_local_task_test_tasks_view_a_child', + 'route_parameters' => [], + 'base_route' => '', + 'weight' => 0, + 'options' => [], + 'class' => 'Drupal\Core\Menu\LocalTaskDefault', + ]; + + // Update the task weights. + foreach ($new_weights as $local_task => $weight) { + $definitions[$local_task] = array_merge($definitions[$local_task], $weight); + } + + $this->pluginDiscovery->expects($this->once()) + ->method('getDefinitions') + ->willReturn($definitions); + + $this->setupFactoryAndLocalTaskPlugins($definitions, 'menu_local_task_test_tasks_view'); + $this->setupLocalTaskManager(); + + $this->argumentResolver->expects($this->any()) + ->method('getArguments') + ->willReturn([]); + + $this->routeMatch->expects($this->any()) + ->method('getRouteName') + ->willReturn('menu_local_task_test_tasks_view'); + $this->routeMatch->expects($this->any()) + ->method('getRawParameters') + ->willReturn(new InputBag()); + + $cacheability = new CacheableMetadata(); + $this->manager->getTasksBuild('menu_local_task_test_tasks_view', $cacheability); + + // Get the local tasks for each level and assert that the order is as + // expected. + foreach ([0, 1] as $level) { + $local_tasks = $this->manager->getLocalTasks('menu_local_task_test_tasks_view', $level); + $data = $local_tasks['tabs']; + $this->assertEquals($expected[$level], array_keys($data)); + } + } + + /** + * Data provider for testGetLocalTasks. + */ + public static function providerTestGetLocalTasks(): array { + return [ + // Weights as setup in getLocalTaskFixtures. + 'weights_from_fixture' => [ + 'new_weights' => [], + 'expected' => [ + // Level 0. + [ + 'menu_local_task_test_tasks_settings', + 'menu_local_task_test_tasks_view.tab', + 'menu_local_task_test_tasks_edit', + ], + // Level 1. All weights are 0, so the sort is alphabetical. + [ + 'menu_local_task_test_tasks_view_a_child', + 'menu_local_task_test_tasks_view_child1', + 'menu_local_task_test_tasks_view_child2', + ], + ], + ], + // Change the weights in both levels. + 'both_levels' => [ + 'new_weights' => [ + 'menu_local_task_test_tasks_view_a_child' => [ + 'weight' => 99, + ], + 'menu_local_task_test_tasks_view_child1' => [ + 'weight' => 100, + ], + 'menu_local_task_test_tasks_view_child2' => [ + 'weight' => -1, + ], + 'menu_local_task_test_tasks_settings' => [ + 'weight' => 100, + ], + ], + 'expected' => [ + // Level 0. + [ + 'menu_local_task_test_tasks_view.tab', + 'menu_local_task_test_tasks_edit', + 'menu_local_task_test_tasks_settings', + ], + // Level 1. + [ + 'menu_local_task_test_tasks_view_child2', + 'menu_local_task_test_tasks_view_a_child', + 'menu_local_task_test_tasks_view_child1', + ], + ], + ], + ]; + } + }