diff --git a/core/lib/Drupal/Core/Menu/LocalTaskDerivativeBase.php b/core/lib/Drupal/Core/Menu/LocalTaskDerivativeBase.php new file mode 100644 index 0000000..179817d --- /dev/null +++ b/core/lib/Drupal/Core/Menu/LocalTaskDerivativeBase.php @@ -0,0 +1,40 @@ + $local_task) { + if ($local_task['route_name'] == $route_name) { + $local_task_id = $plugin_id; + break; + } + } + + return $local_task_id; + } + +} diff --git a/core/modules/comment/comment.local_tasks.yml b/core/modules/comment/comment.local_tasks.yml index 3119823..baba52c 100644 --- a/core/modules/comment/comment.local_tasks.yml +++ b/core/modules/comment/comment.local_tasks.yml @@ -13,3 +13,21 @@ comment.confirm_delete_tab: tab_root_id: comment.permalink_tab weight: 10 +comment.admin: + title: Comments + route_name: comment.admin + tab_root_id: node.content_overview + +comment.admin_new: + title: 'Published comments' + route_name: comment.admin + tab_root_id: node.content_overview + tab_parent_id: comment.admin + +comment.admin_approval: + title: 'Unapproved comments' + route_name: comment.admin_approval + class: Drupal\comment\Plugin\Menu\LocalTask\UnapprovedComments + tab_root_id: node.content_overview + tab_parent_id: comment.admin + weight: 1 diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index 6fcbc2b..d889a73 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -210,18 +210,6 @@ function comment_menu() { 'title' => 'Comments', 'description' => 'List and edit site comments and the comment approval queue.', 'route_name' => 'comment.admin', - 'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM, - ); - // Tabs begin here. - $items['admin/content/comment/new'] = array( - 'title' => 'Published comments', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/content/comment/approval'] = array( - 'title' => 'Unapproved comments', - 'title callback' => 'comment_count_unpublished', - 'route_name' => 'comment.admin_approval', - 'type' => MENU_LOCAL_TASK, ); return $items; @@ -239,6 +227,8 @@ function comment_menu_alter(&$items) { /** * Returns a menu title which includes the number of unapproved comments. + * + * @todo Move to the comment manager and replace by a entity query? */ function comment_count_unpublished() { $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE status = :status', array( diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Menu/LocalTask/UnapprovedComments.php b/core/modules/comment/lib/Drupal/comment/Plugin/Menu/LocalTask/UnapprovedComments.php new file mode 100644 index 0000000..35d0033 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Plugin/Menu/LocalTask/UnapprovedComments.php @@ -0,0 +1,24 @@ +getOverviewRouteName(); $translation_tab = $this->basePluginId . ':' . $route_name; - $tab_root_id = $this->getTaskFromRoute($mapper->getBaseRouteName(), $local_tasks); + $tab_root_id = $this->getPluginIdFromRoute($mapper->getBaseRouteName(), $local_tasks); if (!empty($tab_root_id)) { $local_tasks[$translation_tab]['tab_root_id'] = $tab_root_id; } @@ -90,27 +89,5 @@ public function alterLocalTasks(array &$local_tasks) { } } - /** - * Find the local task ID of the parent route given the route name. - * - * @param string $route_name - * The route name of the parent local task. - * @param array $local_tasks - * An array of all local task definitions. - * - * @return bool|string - * Returns the local task ID of the parent task, otherwise return FALSE. - */ - protected function getTaskFromRoute($route_name, array &$local_tasks) { - $root_local_task = FALSE; - foreach ($local_tasks as $plugin_id => $local_task) { - if ($local_task['route_name'] == $route_name) { - $root_local_task = $plugin_id; - break; - } - } - - return $root_local_task; - } } diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php index 0d6e1ba..e906fd9 100644 --- a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php +++ b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php @@ -7,15 +7,15 @@ namespace Drupal\content_translation\Plugin\Derivative; -use Drupal\Component\Plugin\Derivative\DerivativeBase; use Drupal\content_translation\ContentTranslationManagerInterface; +use Drupal\Core\Menu\LocalTaskDerivativeBase; use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides dynamic local tasks for content translation. */ -class ContentTranslationLocalTasks extends DerivativeBase implements ContainerDerivativeInterface { +class ContentTranslationLocalTasks extends LocalTaskDerivativeBase implements ContainerDerivativeInterface { /** * The base plugin ID @@ -84,31 +84,8 @@ public function alterLocalTasks(array &$local_tasks) { $translation_route_name = $entity_info['links']['drupal:content-translation-overview']; $translation_tab = $this->basePluginId . ':' . $translation_route_name; - $local_tasks[$translation_tab]['tab_root_id'] = $this->getTaskFromRoute($entity_route_name, $local_tasks); + $local_tasks[$translation_tab]['tab_root_id'] = $this->getPluginIdFromRoute($entity_route_name, $local_tasks); } } - /** - * Find the local task ID of the parent route given the route name. - * - * @param string $route_name - * The route name of the parent local task. - * @param array $local_tasks - * An array of all local task definitions. - * - * @return bool|string - * Returns the local task ID of the parent task, otherwise return FALSE. - */ - protected function getTaskFromRoute($route_name, &$local_tasks) { - $parent_local_task = FALSE; - foreach ($local_tasks as $plugin_id => $local_task) { - if ($local_task['route_name'] == $route_name) { - $parent_local_task = $plugin_id; - break; - } - } - - return $parent_local_task; - } - } diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Plugin/Derivative/FieldUiLocalTask.php b/core/modules/field_ui/lib/Drupal/field_ui/Plugin/Derivative/FieldUiLocalTask.php index a75a495..9a7c208 100644 --- a/core/modules/field_ui/lib/Drupal/field_ui/Plugin/Derivative/FieldUiLocalTask.php +++ b/core/modules/field_ui/lib/Drupal/field_ui/Plugin/Derivative/FieldUiLocalTask.php @@ -7,8 +7,8 @@ namespace Drupal\field_ui\Plugin\Derivative; -use Drupal\Component\Plugin\Derivative\DerivativeBase; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Menu\LocalTaskDerivativeBase; use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface; use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\StringTranslation\TranslationInterface; @@ -17,7 +17,7 @@ /** * Provides local task definitions for all entity bundles. */ -class FieldUiLocalTask extends DerivativeBase implements ContainerDerivativeInterface { +class FieldUiLocalTask extends LocalTaskDerivativeBase implements ContainerDerivativeInterface { /** * The route provider. @@ -200,29 +200,6 @@ public function alterLocalTasks(&$local_tasks) { } /** - * Finds the local task ID of a route given the route name. - * - * @param string $route_name - * The route name. - * @param array $local_tasks - * An array of all local task definitions. - * - * @return string|null - * Returns the local task ID of the given route or NULL if none is found. - */ - protected function getPluginIdFromRoute($route_name, &$local_tasks) { - $local_task_id = NULL; - foreach ($local_tasks as $plugin_id => $local_task) { - if ($local_task['route_name'] == $route_name) { - $local_task_id = $plugin_id; - break; - } - } - - return $local_task_id; - } - - /** * Translates a string to the current language or to a given language. * * See the t() documentation for details. diff --git a/core/modules/node/node.local_tasks.yml b/core/modules/node/node.local_tasks.yml index ecf6298..c60dc0c 100644 --- a/core/modules/node/node.local_tasks.yml +++ b/core/modules/node/node.local_tasks.yml @@ -11,6 +11,10 @@ node.delete_confirm: tab_root_id: node.view title: Delete weight: 10 +node.content_overview: + title: Content + route_name: node.content_overview + tab_root_id: node.content_overview node.revision_overview: route_name: node.revision_overview tab_root_id: node.view diff --git a/core/modules/node/node.module b/core/modules/node/node.module index a860f3f..3b01193 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -935,10 +935,6 @@ function node_menu() { 'route_name' => 'node.content_overview', 'weight' => -10, ); - $items['admin/content/node'] = array( - 'title' => 'Content', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); $items['admin/structure/types'] = array( 'title' => 'Content types', diff --git a/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsLocalTask.php b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsLocalTask.php new file mode 100644 index 0000000..8dcf3da --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Plugin/Derivative/ViewsLocalTask.php @@ -0,0 +1,158 @@ +routeProvider = $route_provider; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, $base_plugin_id) { + return new static( + $container->get('router.route_provider'), + $container->get('state') + ); + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions(array $base_plugin_definition) { + $this->derivatives = array(); + + $view_route_names = $this->state->get('views.view_route_names'); + foreach ($this->getApplicableMenuViews() as $pair) { + /** @var $executable \Drupal\views\ViewExecutable */ + list($executable, $display_id) = $pair; + + $executable->setDisplay($display_id); + $menu = $executable->display_handler->getOption('menu'); + if (in_array($menu['type'], array('tab', 'default tab'))) { + $plugin_id = 'view.' . $executable->storage->id() . '.' . $display_id; + $route_name = $view_route_names[$executable->storage->id() . '.' . $display_id]; + + // Don't add a local task for views which override existing routes. + // @todo Alternative it could just change the existing entry. + if ($route_name != $plugin_id) { + continue; + } + + $this->derivatives[$plugin_id] = array( + 'route_name' => $route_name, + 'weight' => $menu['weight'], + 'title' => $menu['title'], + ) + $base_plugin_definition; + + // Default local tasks have themselves as tab root id. + if ($menu['type'] == 'default tab') { + $this->derivatives[$plugin_id]['tab_root_id'] = 'views_view:' . $plugin_id; + } + } + } + return $this->derivatives; + } + + /** + * Alters tab_root_id and tab_parent_id into the views local tasks. + */ + public function alterLocalTasks(&$local_tasks) { + $view_route_names = $this->state->get('views.view_route_names'); + + foreach ($this->getApplicableMenuViews() as $pair) { + /** @var $executable \Drupal\views\ViewExecutable */ + list($executable, $display_id) = $pair; + + $executable->setDisplay($display_id); + $menu = $executable->display_handler->getOption('menu'); + + // We already have set the tab_root_id for default tabs. + if (in_array($menu['type'], array('tab'))) { + $plugin_id = 'view.' . $executable->storage->id() . '.' . $display_id; + $view_route_name = $view_route_names[$executable->storage->id() . '.' . $display_id]; + + // Don't add a local task for views which override existing routes. + if ($view_route_name != $plugin_id) { + unset($local_tasks[$plugin_id]); + continue; + } + + // Find out the parent route. + // @todo Find out how to find both the root and parent tab. + $path = $executable->display_handler->getPath(); + $split = explode('/', $path); + array_pop($split); + $path = implode('/', $split); + + $pattern = '/' . str_replace('%', '{}', $path); + if ($routes = $this->routeProvider->getRoutesByPattern($pattern)) { + foreach ($routes->all() as $name => $route) { + if ($parent_task = $this->getPluginIdFromRoute($name, $local_tasks)) { + $local_tasks['views_view:' . $plugin_id]['tab_root_id'] = $parent_task; + } + // Skip after the first found route. + break; + } + } + } + } + } + + /** + * Return a list of all views and display IDs that have a menu entry. + * + * @return array + * A list of arrays containing the $view and $display_id. + * @code + * array( + * array($view, $display_id), + * array($view, $display_id), + * ); + * @endcode + */ + protected function getApplicableMenuViews() { + return Views::getApplicableViews('uses_hook_menu'); + } + +} diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php index 19893c7..f5fcc5a 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/PathPluginBase.php @@ -314,17 +314,20 @@ public function executeHookMenu($callbacks) { $items[$path]['menu_name'] = $menu['name']; break; case 'tab': - $items[$path]['type'] = MENU_LOCAL_TASK; + $items[$path]['type'] = MENU_CALLBACK; break; case 'default tab': - $items[$path]['type'] = MENU_DEFAULT_LOCAL_TASK; + $items[$path]['type'] = MENU_CALLBACK; break; } // Add context for contextual links. - if (!empty($menu['context'])) { - // @todo Make this work with the new contextual links system. - $items[$path]['context'] = TRUE; + if (in_array($menu['type'], array('tab', 'default tab'))) { + // @todo Remove once contextual links are ported to a new plugin based + // system. + if (!empty($menu['context'])) { + $items[$path]['context'] = TRUE; + } } // If this is a 'default' tab, check to see if we have to create the @@ -336,6 +339,11 @@ public function executeHookMenu($callbacks) { // Remove the last piece. $bit = array_pop($bits); + // Default tabs are handled by the local task plugins. + if ($tab_options['type'] == 'tab') { + return $items; + } + // we can't do this if they tried to make the last path bit variable. // @todo: We can validate this. if ($bit != '%views_arg' && !empty($bits)) { @@ -359,9 +367,6 @@ public function executeHookMenu($callbacks) { case 'normal': $items[$default_path]['type'] = MENU_NORMAL_ITEM; break; - case 'tab': - $items[$default_path]['type'] = MENU_LOCAL_TASK; - break; } if (isset($tab_options['weight'])) { $items[$default_path]['weight'] = intval($tab_options['weight']); diff --git a/core/modules/views/tests/Drupal/views/Tests/Plugin/Derivative/ViewsLocalTaskTest.php b/core/modules/views/tests/Drupal/views/Tests/Plugin/Derivative/ViewsLocalTaskTest.php new file mode 100644 index 0000000..2b7463d --- /dev/null +++ b/core/modules/views/tests/Drupal/views/Tests/Plugin/Derivative/ViewsLocalTaskTest.php @@ -0,0 +1,342 @@ + '\Drupal\views\Plugin\Menu\LocalTask\ViewsLocalTask', + 'derivative' => '\Drupal\views\Plugin\Derivative\ViewsLocalTask' + ); + + /** + * The tested local task derivative class. + * + * @var \Drupal\views\Plugin\Derivative\ViewsLocalTask + */ + protected $localTaskDerivative; + + public static function getInfo() { + return array( + 'name' => 'Views local task derivative', + 'description' => 'Tests the views local task derivative.', + 'group' => 'Views plugin', + ); + } + + protected function setUp() { + $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $this->state = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface'); + + $this->localTaskDerivative = new TestViewsLocalTask($this->routeProvider, $this->state); + } + + /** + * Tests fetching the derivatives on no view with hook menu. + * + * @see \Drupal\views\Plugin\Derivative\ViewsLocalTask::getDerivativeDefinitions() + */ + public function testGetDerivativeDefinitionsWithoutHookMenuViews() { + $result = array(); + $this->localTaskDerivative->setApplicableMenuViews($result); + + $definitions = $this->localTaskDerivative->getDerivativeDefinitions($this->baseDefinition); + $this->assertEquals(array(), $definitions); + } + + /** + * Tests fetching the derivatives on a view with without a local task. + */ + public function testGetDerivativeDefinitionsWithoutLocalTask() { + $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $display_plugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase') + ->setMethods(array('getOption')) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $display_plugin->expects($this->once()) + ->method('getOption') + ->with('menu') + ->will($this->returnValue(array('type' => 'normal'))); + $executable->display_handler = $display_plugin; + + $result = array(array($executable, 'page_1')); + $this->localTaskDerivative->setApplicableMenuViews($result); + + $definitions = $this->localTaskDerivative->getDerivativeDefinitions($this->baseDefinition); + $this->assertEquals(array(), $definitions); + } + + /** + * Tests fetching the derivatives on a view with a default local task. + */ + public function testGetDerivativeDefinitionsWithLocalTask() { + $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $storage = $this->getMockBuilder('Drupal\views\Entity\View') + ->disableOriginalConstructor() + ->getMock(); + $storage->expects($this->any()) + ->method('id') + ->will($this->returnValue('example_view')); + $executable->storage = $storage; + + $display_plugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase') + ->setMethods(array('getOption')) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $display_plugin->expects($this->once()) + ->method('getOption') + ->with('menu') + ->will($this->returnValue(array('type' => 'tab', 'weight' => 12, 'title' => 'Example title'))); + $executable->display_handler = $display_plugin; + + $result = array(array($executable, 'page_1')); + $this->localTaskDerivative->setApplicableMenuViews($result); + + // Mock the view route names state. + $view_route_names = array(); + $view_route_names['example_view.page_1'] = 'view.example_view.page_1'; + $this->state->expects($this->once()) + ->method('get') + ->with('views.view_route_names') + ->will($this->returnValue($view_route_names)); + + $definitions = $this->localTaskDerivative->getDerivativeDefinitions($this->baseDefinition); + $this->assertCount(1, $definitions); + $this->assertEquals('view.example_view.page_1', $definitions['view.example_view.page_1']['route_name']); + $this->assertEquals(12, $definitions['view.example_view.page_1']['weight']); + $this->assertEquals('Example title', $definitions['view.example_view.page_1']['title']); + $this->assertEquals($this->baseDefinition['class'], $definitions['view.example_view.page_1']['class']); + $this->assertTrue(empty($definitions['view.example_view.page_1']['tab_root_id'])); + } + + /** + * Tests fetching the derivatives on a view which overrides an existing route. + */ + public function testGetDerivativeDefinitionsWithOverrideRoute() { + $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $storage = $this->getMockBuilder('Drupal\views\Entity\View') + ->disableOriginalConstructor() + ->getMock(); + $storage->expects($this->any()) + ->method('id') + ->will($this->returnValue('example_view')); + $executable->storage = $storage; + + $display_plugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase') + ->setMethods(array('getOption')) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $display_plugin->expects($this->once()) + ->method('getOption') + ->with('menu') + ->will($this->returnValue(array('type' => 'tab', 'weight' => 12))); + $executable->display_handler = $display_plugin; + + $result = array(array($executable, 'page_1')); + $this->localTaskDerivative->setApplicableMenuViews($result); + + // Mock the view route names state. + $view_route_names = array(); + // Setup a view which overrides an existing route. + $view_route_names['example_view.page_1'] = 'example_overridden_route'; + $this->state->expects($this->once()) + ->method('get') + ->with('views.view_route_names') + ->will($this->returnValue($view_route_names)); + + $definitions = $this->localTaskDerivative->getDerivativeDefinitions($this->baseDefinition); + $this->assertCount(0, $definitions); + } + + /** + * Tests fetching the derivatives on a view with a default local task. + */ + public function testGetDerivativeDefinitionsWithDefaultLocalTask() { + $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $storage = $this->getMockBuilder('Drupal\views\Entity\View') + ->disableOriginalConstructor() + ->getMock(); + $storage->expects($this->any()) + ->method('id') + ->will($this->returnValue('example_view')); + $executable->storage = $storage; + + $display_plugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase') + ->setMethods(array('getOption')) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $display_plugin->expects($this->exactly(2)) + ->method('getOption') + ->with('menu') + ->will($this->returnValue(array('type' => 'default tab', 'weight' => 12, 'title' => 'Example title'))); + $executable->display_handler = $display_plugin; + + $result = array(array($executable, 'page_1')); + $this->localTaskDerivative->setApplicableMenuViews($result); + + // Mock the view route names state. + $view_route_names = array(); + $view_route_names['example_view.page_1'] = 'view.example_view.page_1'; + $this->state->expects($this->exactly(2)) + ->method('get') + ->with('views.view_route_names') + ->will($this->returnValue($view_route_names)); + + $definitions = $this->localTaskDerivative->getDerivativeDefinitions($this->baseDefinition); + $this->assertCount(1, $definitions); + $plugin = $definitions['view.example_view.page_1']; + $this->assertEquals('view.example_view.page_1', $plugin['route_name']); + $this->assertEquals(12, $plugin['weight']); + $this->assertEquals('Example title', $plugin['title']); + $this->assertEquals($this->baseDefinition['class'], $plugin['class']); + $this->assertEquals('views_view:view.example_view.page_1', $plugin['tab_root_id']); + + // Setup the prefix of the derivative. + $definitions['views_view:view.example_view.page_1'] = $definitions['view.example_view.page_1']; + unset($definitions['view.example_view.page_1']); + $this->localTaskDerivative->alterLocalTasks($definitions); + + $plugin = $definitions['views_view:view.example_view.page_1']; + $this->assertCount(1, $definitions); + $this->assertEquals('view.example_view.page_1', $plugin['route_name']); + $this->assertEquals(12, $plugin['weight']); + $this->assertEquals('Example title', $plugin['title']); + $this->assertEquals($this->baseDefinition['class'], $plugin['class']); + $this->assertEquals('views_view:view.example_view.page_1', $plugin['tab_root_id']); + } + + /** + * Tests fetching the derivatives on a view with a local task and a parent. + * + * The parent is defined by another module, not views. + */ + public function testGetDerivativeDefinitionsWithExistingLocalTask() { + $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') + ->disableOriginalConstructor() + ->getMock(); + $storage = $this->getMockBuilder('Drupal\views\Entity\View') + ->disableOriginalConstructor() + ->getMock(); + $storage->expects($this->any()) + ->method('id') + ->will($this->returnValue('example_view')); + $executable->storage = $storage; + + $display_plugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase') + ->setMethods(array('getOption', 'getPath')) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $display_plugin->expects($this->exactly(2)) + ->method('getOption') + ->with('menu') + ->will($this->returnValue(array('type' => 'tab', 'weight' => 12, 'title' => 'Example title'))); + $display_plugin->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('path/example')); + $executable->display_handler = $display_plugin; + + $result = array(array($executable, 'page_1')); + $this->localTaskDerivative->setApplicableMenuViews($result); + + // Mock the view route names state. + $view_route_names = array(); + $view_route_names['example_view.page_1'] = 'view.example_view.page_1'; + $this->state->expects($this->exactly(2)) + ->method('get') + ->with('views.view_route_names') + ->will($this->returnValue($view_route_names)); + + // Mock the route provider. + $route_collection = new RouteCollection(); + $route_collection->add('test_route', new Route('/path')); + $this->routeProvider->expects($this->any()) + ->method('getRoutesByPattern') + ->with('/path') + ->will($this->returnValue($route_collection)); + + // Setup the existing local task of the test_route. + $definitions['test_route_tab'] = $other_tab = array( + 'route_name' => 'test_route', + 'title' => 'Test route', + 'tab_root_id' => 'test_route_tab', + ); + + $definitions += $this->localTaskDerivative->getDerivativeDefinitions($this->baseDefinition); + + // Setup the prefix of the derivative. + $definitions['views_view:view.example_view.page_1'] = $definitions['view.example_view.page_1']; + unset($definitions['view.example_view.page_1']); + $this->localTaskDerivative->alterLocalTasks($definitions); + + $plugin = $definitions['views_view:view.example_view.page_1']; + $this->assertCount(2, $definitions); + + // Ensure the other local task was not changed. + $this->assertEquals($other_tab, $definitions['test_route_tab']); + + $this->assertEquals('view.example_view.page_1', $plugin['route_name']); + $this->assertEquals(12, $plugin['weight']); + $this->assertEquals('Example title', $plugin['title']); + $this->assertEquals($this->baseDefinition['class'], $plugin['class']); + $this->assertEquals('test_route_tab', $plugin['tab_root_id']); + } + +} + +/** + * Replaces the applicable views call for easier testability. + */ +class TestViewsLocalTask extends ViewsLocalTask { + + /** + * Sets applicable views result. + */ + public function setApplicableMenuViews($result) { + $this->result = $result; + } + + /** + * {@inheritdoc} + */ + protected function getApplicableMenuViews() { + return $this->result; + } + +} diff --git a/core/modules/views/views.local_tasks.yml b/core/modules/views/views.local_tasks.yml new file mode 100644 index 0000000..f050e64 --- /dev/null +++ b/core/modules/views/views.local_tasks.yml @@ -0,0 +1,3 @@ +views_view: + class: Drupal\Core\Menu\LocalTaskDefault + derivative: \Drupal\views\Plugin\Derivative\ViewsLocalTask diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 8445804..856b7af 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -12,6 +12,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Language\Language; +use Drupal\views\Plugin\Derivative\ViewsLocalTask; use Drupal\views\ViewExecutable; use Drupal\Component\Plugin\Exception\PluginException; use Drupal\views\Entity\View; @@ -1355,3 +1356,12 @@ function views_element_validate_tags($element, &$form_state) { } } } + +/** + * Implements hook_local_tasks_alter(). + */ +function views_local_tasks_alter(&$local_tasks) { + $container = \Drupal::getContainer(); + $local_task = ViewsLocalTask::create($container, 'views_view'); + $local_task->alterLocalTasks($local_tasks); +}