diff --git a/core/core.services.yml b/core/core.services.yml index 9afeed2..b52199b 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -388,7 +388,7 @@ services: class: Drupal\Core\EventSubscriber\ViewSubscriber tags: - { name: event_subscriber } - arguments: ['@content_negotiation'] + arguments: ['@content_negotiation', '@title_resolver'] private_key: class: Drupal\Core\PrivateKey arguments: ['@state'] diff --git a/core/includes/theme.inc b/core/includes/theme.inc index e49bb76..2a8f846 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1503,6 +1503,7 @@ function theme_enable($theme_list) { \Drupal::service('router.builder')->rebuild(); menu_router_rebuild(); + \Drupal::cache('cache')->deleteTags(array('local_task' => 1)); drupal_theme_rebuild(); // Invoke hook_themes_enabled() after the themes have been enabled. @@ -1539,6 +1540,7 @@ function theme_disable($theme_list) { list_themes(TRUE); \Drupal::service('router.builder')->rebuild(); menu_router_rebuild(); + \Drupal::cache('cache')->deleteTags(array('local_task' => 1)); drupal_theme_rebuild(); // Invoke hook_themes_disabled after the themes have been disabled. diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php index ae1d083..7e4318d 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php @@ -7,6 +7,8 @@ namespace Drupal\Core\EventSubscriber; +use Drupal\Core\Controller\TitleResolverInterface; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\KernelEvents; @@ -27,8 +29,24 @@ class ViewSubscriber implements EventSubscriberInterface { protected $negotiation; - public function __construct(ContentNegotiation $negotiation) { + /** + * The title resolver. + * + * @var \Drupal\Core\Controller\TitleResolverInterface + */ + protected $titleResolver; + + /** + * Constructs a new ViewSubscriber. + * + * @param \Drupal\Core\ContentNegotiation $negotiation + * The content negotiation. + * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver + * The title resolver. + */ + public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver) { $this->negotiation = $negotiation; + $this->titleResolver = $title_resolver; } /** @@ -154,6 +172,13 @@ public function onIframeUpload(GetResponseForControllerResultEvent $event) { */ public function onHtml(GetResponseForControllerResultEvent $event) { $page_callback_result = $event->getControllerResult(); + $request = $event->getRequest(); + + // If no title was returned fall back to one defined in the route. + if (!isset($page_callback_result['#title'])) { + $page_callback_result['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); + } + return new Response(drupal_render_page($page_callback_result)); } diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php index 016bddd..0d17aa3 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php @@ -115,7 +115,7 @@ public function __construct(ControllerResolverInterface $controller_resolver, Re $this->routeProvider = $route_provider; $this->accessManager = $access_manager; $this->alterInfo($module_handler, 'local_tasks'); - $this->setCacheBackend($cache, $language_manager, 'local_task_plugins', array('local_task' => TRUE)); + $this->setCacheBackend($cache, $language_manager, 'local_task_plugins', array('local_task' => 1)); } /** @@ -200,7 +200,7 @@ public function getLocalTasksForRoute($route_name) { 'parents' => $parents, 'children' => $children, ); - $this->cacheBackend->set($this->cacheKey . ':' . $route_name, $data, CacheBackendInterface::CACHE_PERMANENT, array('local_task')); + $this->cacheBackend->set($this->cacheKey . ':' . $route_name, $data, CacheBackendInterface::CACHE_PERMANENT, $this->cacheTags); } // Create a plugin instance for each element of the hierarchy. foreach ($tab_root_ids as $root_id) { diff --git a/core/modules/action/action.local_tasks.yml b/core/modules/action/action.local_tasks.yml new file mode 100644 index 0000000..b1d0b61 --- /dev/null +++ b/core/modules/action/action.local_tasks.yml @@ -0,0 +1,4 @@ +action.admin: + route_name: action.admin + title: 'Manage actions' + tab_root_id: action.admin diff --git a/core/modules/action/action.module b/core/modules/action/action.module index a1f2a14..39fd855 100644 --- a/core/modules/action/action.module +++ b/core/modules/action/action.module @@ -53,26 +53,6 @@ function action_menu() { 'description' => 'Manage the actions defined for your site.', 'route_name' => 'action.admin', ); - $items['admin/config/system/actions/manage'] = array( - 'title' => 'Manage actions', - 'description' => 'Manage the actions defined for your site.', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/config/system/actions/add'] = array( - 'title' => 'Create an advanced action', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - 'route_name' => 'action.admin_add', - ); - $items['admin/config/system/actions/configure'] = array( - 'title' => 'Configure an advanced action', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - 'route_name' => 'action.admin_configure', - ); - $items['admin/config/system/actions/configure/%/delete'] = array( - 'title' => 'Delete action', - 'description' => 'Delete an action.', - 'route_name' => 'action.delete', - ); return $items; } diff --git a/core/modules/action/tests/Drupal/action/Tests/Menu/ActionLocalTasksTest.php b/core/modules/action/tests/Drupal/action/Tests/Menu/ActionLocalTasksTest.php new file mode 100644 index 0000000..891db5b --- /dev/null +++ b/core/modules/action/tests/Drupal/action/Tests/Menu/ActionLocalTasksTest.php @@ -0,0 +1,40 @@ + 'Action local tasks test', + 'description' => 'Test action local tasks.', + 'group' => 'Action', + ); + } + + public function setUp() { + $this->moduleList = array('action' => 'core/modules/action/action.info.yml'); + parent::setUp(); + } + + /** + * Tests local task existence. + */ + public function testActionLocalTasks() { + $this->assertLocalTasks('action.admin', array(array('action.admin'))); + } + +} diff --git a/core/modules/aggregator/aggregator.local_tasks.yml b/core/modules/aggregator/aggregator.local_tasks.yml new file mode 100644 index 0000000..4de9e51 --- /dev/null +++ b/core/modules/aggregator/aggregator.local_tasks.yml @@ -0,0 +1,37 @@ +aggregator.admin_overview: + route_name: aggregator.admin_overview + title: 'List' + tab_root_id: aggregator.admin_overview +aggregator.admin_settings: + route_name: aggregator.admin_settings + title: 'Settings' + weight: 100 + tab_root_id: aggregator.admin_overview + +aggregator.category_view: + route_name: aggregator.category_view + tab_root_id: aggregator.category_view + title: 'View' +aggregator.categorize_category_form: + route_name: aggregator.categorize_category_form + tab_root_id: aggregator.category_view + title: Categorize +aggregator.category_edit: + route_name: aggregator.category_edit + tab_root_id: aggregator.category_view + title: Configure + weight: 10 + +aggregator.feed_view: + route_name: aggregator.feed_view + tab_root_id: aggregator.feed_view + title: View +aggregator.categorize_feed_form: + route_name: aggregator.categorize_feed_form + tab_root_id: aggregator.feed_view + title: 'Categorize' +aggregator.feed_configure: + route_name: aggregator.feed_configure + tab_root_id: aggregator.feed_view + title: 'Configure' + weight: 10 diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 8938c19..045c156 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -120,17 +120,6 @@ function aggregator_menu() { 'title' => 'Update items', 'route_name' => 'aggregator.feed_refresh', ); - $items['admin/config/services/aggregator/list'] = array( - 'title' => 'List', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/config/services/aggregator/settings'] = array( - 'title' => 'Settings', - 'description' => 'Configure the behavior of the feed aggregator, including when to discard feed items and how to present feed items and categories.', - 'route_name' => 'aggregator.admin_settings', - 'type' => MENU_LOCAL_TASK, - 'weight' => 100, - ); $items['aggregator'] = array( 'title' => 'Feed aggregator', 'weight' => 5, @@ -149,41 +138,11 @@ function aggregator_menu() { 'title arguments' => array(2), 'route_name' => 'aggregator.category_view', ); - $items['aggregator/categories/%aggregator_category/view'] = array( - 'title' => 'View', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['aggregator/categories/%aggregator_category/categorize'] = array( - 'title' => 'Categorize', - 'type' => MENU_LOCAL_TASK, - 'route_name' => 'aggregator.categorize_category_form', - ); - $items['aggregator/categories/%aggregator_category/configure'] = array( - 'title' => 'Configure', - 'type' => MENU_LOCAL_TASK, - 'weight' => 10, - 'route_name' => 'aggregator.category_edit', - ); $items['aggregator/sources/%aggregator_feed'] = array( 'title callback' => 'entity_page_label', 'title arguments' => array(2), 'route_name' => 'aggregator.feed_view', ); - $items['aggregator/sources/%aggregator_feed/view'] = array( - 'title' => 'View', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['aggregator/sources/%aggregator_feed/categorize'] = array( - 'title' => 'Categorize', - 'route_name' => 'aggregator.categorize_feed_form', - 'type' => MENU_LOCAL_TASK, - ); - $items['aggregator/sources/%aggregator_feed/configure'] = array( - 'title' => 'Configure', - 'route_name' => 'aggregator.feed_configure', - 'type' => MENU_LOCAL_TASK, - 'weight' => 10, - ); $items['admin/config/services/aggregator/edit/feed/%aggregator_feed'] = array( 'title' => 'Edit feed', 'route_name' => 'aggregator.feed_edit', diff --git a/core/modules/aggregator/tests/Drupal/aggregator/Tests/Menu/AggregatorLocalTasksTest.php b/core/modules/aggregator/tests/Drupal/aggregator/Tests/Menu/AggregatorLocalTasksTest.php new file mode 100644 index 0000000..67bedd8 --- /dev/null +++ b/core/modules/aggregator/tests/Drupal/aggregator/Tests/Menu/AggregatorLocalTasksTest.php @@ -0,0 +1,100 @@ + 'Aggregator local tasks test', + 'description' => 'Test existence of aggregator local tasks.', + 'group' => 'Aggregator', + ); + } + + public function setUp() { + $this->moduleList = array('aggregator' => 'core/modules/aggregator/aggregator.info.yml'); + parent::setUp(); + } + + /** + * Tests local task existence. + * + * @dataProvider getAggregatorAdminRoutes + */ + public function testAggregatorAdminLocalTasks($route) { + $this->assertLocalTasks($route, array( + 0 => array('aggregator.admin_overview', 'aggregator.admin_settings'), + )); + } + + /** + * Provides a list of routes to test. + */ + public function getAggregatorAdminRoutes() { + return array( + array('aggregator.admin_overview'), + array('aggregator.admin_settings'), + ); + } + + /** + * Checks aggregator category tasks. + * + * @dataProvider getAggregatorCategoryRoutes + */ + public function testAggregatorCategoryLocalTasks($route) { + $this->assertLocalTasks($route, array( + 0 => array('aggregator.category_view', 'aggregator.categorize_category_form', 'aggregator.category_edit'), + )); + ; + } + + /** + * Provides a list of category routes to test. + */ + public function getAggregatorCategoryRoutes() { + return array( + array('aggregator.category_view'), + array('aggregator.categorize_category_form'), + array('aggregator.category_edit'), + ); + } + + /** + * Checks aggregator source tasks. + * + * @dataProvider getAggregatorSourceRoutes + */ + public function testAggregatorSourceLocalTasks($route) { + $this->assertLocalTasks($route, array( + 0 => array('aggregator.feed_view', 'aggregator.categorize_feed_form', 'aggregator.feed_configure'), + )); + ; + } + + /** + * Provides a list of source routes to test. + */ + public function getAggregatorSourceRoutes() { + return array( + array('aggregator.feed_view'), + array('aggregator.categorize_feed_form'), + array('aggregator.feed_configure'), + ); + } + +} diff --git a/core/modules/block/block.local_tasks.yml b/core/modules/block/block.local_tasks.yml new file mode 100644 index 0000000..3fdea89 --- /dev/null +++ b/core/modules/block/block.local_tasks.yml @@ -0,0 +1,16 @@ +block.admin_edit: + title: 'Configure block' + route_name: block.admin_edit + tab_root_id: block.admin_edit + +# Per theme block layout pages. +block.admin_display: + title: 'Block Layout' + route_name: block.admin_display + tab_root_id: block.admin_display +block.admin_display_theme: + title: 'Block Layout' + route_name: block.admin_display_theme + tab_root_id: block.admin_display + tab_parent_id: block.admin_display + derivative: 'Drupal\block\Plugin\Derivative\ThemeLocalTask' diff --git a/core/modules/block/block.module b/core/modules/block/block.module index ce1ecb7..07d2c17 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -99,16 +99,11 @@ function block_permission() { * @todo Clarify the documentation for the per-plugin block admin links. */ function block_menu() { - $default_theme = \Drupal::config('system.theme')->get('default'); $items['admin/structure/block'] = array( 'title' => 'Block layout', 'description' => 'Configure what block content appears in your site\'s sidebars and other regions.', 'route_name' => 'block.admin_display', ); - $items['admin/structure/block/list'] = array( - 'title' => 'Block layout', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); $items['admin/structure/block/manage/%block'] = array( 'title' => 'Configure block', 'route_name' => 'block.admin_edit', @@ -116,7 +111,7 @@ function block_menu() { $items['admin/structure/block/manage/%block/configure'] = array( 'title' => 'Configure block', 'type' => MENU_DEFAULT_LOCAL_TASK, - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'context' => MENU_CONTEXT_INLINE, ); $items['admin/structure/block/add/%/%'] = array( 'title' => 'Place block', @@ -126,15 +121,9 @@ function block_menu() { // Block administration is tied to the theme and plugin definition so // that the plugin can appropriately attach to this URL structure. // @todo D8: Use dynamic % arguments instead of static, hard-coded theme names - // and plugin IDs to decouple the routes from these dependencies and allow - // hook_menu_local_tasks() to check for the untranslated tab_parent path. + // and plugin IDs to decouple the routes from these dependencies. // @see http://drupal.org/node/1067408 foreach (list_themes() as $key => $theme) { - $items["admin/structure/block/list/$key"] = array( - 'title' => check_plain($theme->info['name']), - 'type' => $key == $default_theme ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, - 'route_name' => "block.admin_display_$key", - ); $items["admin/structure/block/demo/$key"] = array( 'route_name' => 'block.admin_demo', 'type' => MENU_CALLBACK, diff --git a/core/modules/block/block.routing.yml b/core/modules/block/block.routing.yml index 87ed0ba..9c5a380 100644 --- a/core/modules/block/block.routing.yml +++ b/core/modules/block/block.routing.yml @@ -28,6 +28,14 @@ block.admin_display: requirements: _permission: 'administer blocks' +block.admin_display_theme: + path: 'admin/structure/block/list/{theme}' + defaults: + _controller: '\Drupal\block\Controller\BlockListController::listing' + requirements: + _access_theme: 'TRUE' + _permission: 'administer blocks' + block.admin_add: path: '/admin/structure/block/add/{plugin_id}/{theme}' defaults: diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml index c6704bc..b6bf1f4 100644 --- a/core/modules/block/block.services.yml +++ b/core/modules/block/block.services.yml @@ -9,7 +9,3 @@ services: factory_method: get factory_service: cache_factory arguments: [block] - block.route_subscriber: - class: Drupal\block\Routing\RouteSubscriber - tags: - - { name: event_subscriber} diff --git a/core/modules/block/custom_block/custom_block.local_actions.yml b/core/modules/block/custom_block/custom_block.local_actions.yml index 531668e..b12329c 100644 --- a/core/modules/block/custom_block/custom_block.local_actions.yml +++ b/core/modules/block/custom_block/custom_block.local_actions.yml @@ -9,3 +9,6 @@ custom_block_add_action: title: 'Add custom block' appears_on: - block.admin_display + - block.admin_display_theme + class: \Drupal\custom_block\Plugin\Menu\LocalAction\CustomBlockAddLocalAction + diff --git a/core/modules/block/custom_block/custom_block.local_tasks.yml b/core/modules/block/custom_block/custom_block.local_tasks.yml new file mode 100644 index 0000000..9bee9a4 --- /dev/null +++ b/core/modules/block/custom_block/custom_block.local_tasks.yml @@ -0,0 +1,29 @@ +custom_block.list: + title: 'Custom block library' + route_name: custom_block.list + tab_root_id: block.admin_display +custom_block.list_sub: + title: Blocks + route_name: custom_block.list + tab_root_id: block.admin_display + tab_parent_id: custom_block.list +custom_block.type_list: + title: Types + route_name: custom_block.type_list + tab_root_id: block.admin_display + tab_parent_id: custom_block.list + +custom_block.edit: + title: Edit + route_name: custom_block.edit + tab_root_id: custom_block.edit +custom_block.delete: + title: Delete + route_name: custom_block.delete + tab_root_id: custom_block.edit + +# Default tab for custom block type editing. +custom_block.type_edit: + title: 'Edit' + route_name: custom_block.type_edit + tab_root_id: custom_block.type_edit diff --git a/core/modules/block/custom_block/custom_block.module b/core/modules/block/custom_block/custom_block.module index d55d279..03d9840 100644 --- a/core/modules/block/custom_block/custom_block.module +++ b/core/modules/block/custom_block/custom_block.module @@ -54,23 +54,6 @@ function custom_block_menu_local_tasks(&$data, $route_name) { ); } } - - $routes = array_map(function ($theme) { - return "block.admin_display_$theme"; - }, array_keys(list_themes())); - if (in_array($route_name, $routes)) { - // @todo Move to a LocalAction plugin when https://drupal.org/node/2045267 - // allows local actions to work with query strings. - $item = menu_get_item('block/add'); - if ($item['access']) { - // Add a destination parameter. - $item['localized_options']['query']['theme'] = \Drupal::request()->attributes->get('theme'); - $data['actions']['block/add'] = array( - '#theme' => 'menu_local_action', - '#link' => $item, - ); - } - } } /** @@ -81,42 +64,22 @@ function custom_block_menu() { 'title' => 'Custom block library', 'description' => 'Manage custom blocks.', 'route_name' => 'custom_block.list', - 'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM, - ); - $items['admin/structure/block/custom-blocks/list'] = array( - 'title' => 'Blocks', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/structure/block/custom-blocks/types'] = array( - 'title' => 'Types', - 'route_name' => 'custom_block.type_list', - 'type' => MENU_LOCAL_TASK, - ); - $items['admin/structure/block/custom-blocks/types/add'] = array( - 'route_name' => 'custom_block.type_add', - 'type' => MENU_SIBLING_LOCAL_TASK, - 'weight' => 1, + 'type' => MENU_NORMAL_ITEM, ); + $items['admin/structure/block/custom-blocks/manage/%custom_block_type'] = array( 'title' => 'Edit custom block type', 'title callback' => 'entity_page_label', 'title arguments' => array(5), 'route_name' => 'custom_block.type_edit', ); - $items['admin/structure/block/custom-blocks/manage/%custom_block_type/edit'] = array( - 'title' => 'Edit', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => -10, - ); + $items['block/add'] = array( 'title' => 'Add custom block', - 'route_name' => 'custom_block.add_page', - ); - $items['block/add/%custom_block_type'] = array( - 'title' => 'Add custom block', 'description' => 'Add custom block', - 'route_name' => 'custom_block.add_form' + 'route_name' => 'custom_block.add_page', ); + // There has to be a base-item in order for contextual links to work. $items['block/%custom_block'] = array( 'title' => 'Edit', @@ -126,7 +89,7 @@ function custom_block_menu() { 'title' => 'Edit', 'weight' => 0, 'type' => MENU_DEFAULT_LOCAL_TASK, - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'context' => MENU_CONTEXT_INLINE, ); $items['block/%custom_block/delete'] = array( 'title' => 'Delete', diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php index e37328d..12f2b72 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php @@ -8,6 +8,7 @@ namespace Drupal\custom_block\Plugin\Derivative; use Drupal\Component\Plugin\Derivative\DerivativeBase; +use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface; /** * Retrieves block plugin definitions for all custom blocks. diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Menu/LocalAction/CustomBlockAddLocalAction.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Menu/LocalAction/CustomBlockAddLocalAction.php new file mode 100644 index 0000000..c2b773a --- /dev/null +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Menu/LocalAction/CustomBlockAddLocalAction.php @@ -0,0 +1,30 @@ +attributes->has('theme')) { + $options['query']['theme'] = $request->attributes->get('theme'); + } + return $options; + } + +} diff --git a/core/modules/block/custom_block/tests/Drupal/custom_blocks/Tests/Menu/CustomBlockLocalTasksTest.php b/core/modules/block/custom_block/tests/Drupal/custom_blocks/Tests/Menu/CustomBlockLocalTasksTest.php new file mode 100644 index 0000000..96866fd --- /dev/null +++ b/core/modules/block/custom_block/tests/Drupal/custom_blocks/Tests/Menu/CustomBlockLocalTasksTest.php @@ -0,0 +1,64 @@ + 'Custom Block local tasks test', + 'description' => 'Test custom_block local tasks.', + 'group' => 'Block', + ); + } + + public function setUp() { + $this->moduleList = array( + 'block' => 'core/modules/block/block.info.yml', + 'custom_block' => 'core/modules/block/custom_block/custom_block.info.yml', + ); + parent::setUp(); + } + + /** + * Checks custom_block listing local tasks. + * + * @dataProvider getCustomBlockListingRoutes + */ + public function testCustomBlockListLocalTasks($route) { + // + $this->assertLocalTasks($route, array( + 0 => array( + 'block.admin_display', + 'custom_block.list', + ), + 1 => array( + 'custom_block.list_sub', + 'custom_block.type_list', + ) + )); + } + + /** + * Provides a list of routes to test. + */ + public function getCustomBlockListingRoutes() { + return array( + array('custom_block.list', 'custom_block.type_list'), + ); + } + +} diff --git a/core/modules/block/lib/Drupal/block/Plugin/Derivative/ThemeLocalTask.php b/core/modules/block/lib/Drupal/block/Plugin/Derivative/ThemeLocalTask.php new file mode 100644 index 0000000..77a2235 --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Plugin/Derivative/ThemeLocalTask.php @@ -0,0 +1,69 @@ +config = $config_factory->get('system.theme'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, $base_plugin_id) { + return new static($container->get('config.factory')); + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions(array $base_plugin_definition) { + $default_theme = $this->config->get('default'); + + foreach (list_themes() as $theme_name => $theme) { + if ($theme->status) { + $this->derivatives[$theme_name] = $base_plugin_definition; + $this->derivatives[$theme_name]['title'] = $theme->info['name']; + $this->derivatives[$theme_name]['route_parameters'] = array('theme' => $theme_name); + } + // Default task! + if ($default_theme == $theme_name) { + $this->derivatives[$theme_name]['route_name'] = 'block.admin_display'; + // Emulate default logic because without the base plugin id we can't set the + // change the tab_root_id. + $this->derivatives[$theme_name]['weight'] = -10; + + unset($this->derivatives[$theme_name]['route_parameters']); + } + } + return $this->derivatives; + } + +} diff --git a/core/modules/block/tests/Drupal/block/Tests/Menu/BlockLocalTasksTest.php b/core/modules/block/tests/Drupal/block/Tests/Menu/BlockLocalTasksTest.php new file mode 100644 index 0000000..59b26f8 --- /dev/null +++ b/core/modules/block/tests/Drupal/block/Tests/Menu/BlockLocalTasksTest.php @@ -0,0 +1,91 @@ + 'Block local tasks test', + 'description' => 'Test block local tasks.', + 'group' => 'Block', + ); + } + + public function setUp() { + $this->moduleList = array('block' => 'core/modules/block/block.info.yml'); + parent::setUp(); + + $config_factory = $this->getConfigFactoryStub(array('system.theme' => array( + 'default' => 'test_c', + ))); + \Drupal::getContainer()->set('config.factory', $config_factory); + } + + /** + * Tests the admin edit local task. + */ + public function testBlockAdminLocalTasks() { + $this->assertLocalTasks('block.admin_edit', array(array('block.admin_edit'))); + } + + /** + * Tests the block admin display local tasks. + * + * @dataProvider providerTestBlockAdminDisplay + */ + public function testBlockAdminDisplay($route, $expected) { + $this->assertLocalTasks($route, $expected); + } + + /** + * Provides a list of routes to test. + */ + public function providerTestBlockAdminDisplay() { + return array( + array('block.admin_display', array(array('block.admin_display'), array('block.admin_display_theme:test_b', 'block.admin_display_theme:test_c'))), + array('block.admin_display_theme', array(array('block.admin_display'), array('block.admin_display_theme:test_b', 'block.admin_display_theme:test_c'))), + ); + } + +} + +} + +namespace { + if (!function_exists('list_themes')) { + function list_themes() { + $themes = array(); + $themes['test_a'] = (object) array( + 'status' => 0, + ); + $themes['test_b'] = (object) array( + 'status' => 1, + 'info' => array( + 'name' => 'test_b', + ), + ); + $themes['test_c'] = (object) array( + 'status' => 1, + 'info' => array( + 'name' => 'test_c', + ), + ); + + return $themes; + } + } +} diff --git a/core/modules/book/book.local_tasks.yml b/core/modules/book/book.local_tasks.yml new file mode 100644 index 0000000..f0f8464 --- /dev/null +++ b/core/modules/book/book.local_tasks.yml @@ -0,0 +1,15 @@ +book.admin: + route_name: book.admin + title: 'List' + tab_root_id: book.admin +book.settings: + route_name: book.settings + title: 'Settings' + tab_root_id: book.admin + weight: 100 + +book.outline: + route_name: book.outline + tab_root_id: node.view + title: Outline + weight: 2 diff --git a/core/modules/book/book.module b/core/modules/book/book.module index aef8a52..f7be7d5 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -171,28 +171,11 @@ function book_menu() { 'description' => "Manage your site's book outlines.", 'route_name' => 'book.admin', ); - $items['admin/structure/book/list'] = array( - 'title' => 'List', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/structure/book/settings'] = array( - 'title' => 'Settings', - 'route_name' => 'book.settings', - 'access arguments' => array('administer site configuration'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 100, - ); $items['book'] = array( 'title' => 'Books', 'route_name' => 'book.render', 'type' => MENU_SUGGESTED_ITEM, ); - $items['node/%node/outline'] = array( - 'title' => 'Outline', - 'route_name' => 'book.outline', - 'type' => MENU_LOCAL_TASK, - 'weight' => 2, - ); $items['node/%node/outline/remove'] = array( 'title' => 'Remove from outline', 'route_name' => 'book.remove', diff --git a/core/modules/book/tests/Drupal/book/Tests/Menu/BookLocalTasksTest.php b/core/modules/book/tests/Drupal/book/Tests/Menu/BookLocalTasksTest.php new file mode 100644 index 0000000..2f8d749 --- /dev/null +++ b/core/modules/book/tests/Drupal/book/Tests/Menu/BookLocalTasksTest.php @@ -0,0 +1,79 @@ + 'Book local tasks test', + 'description' => 'Test existence of book local tasks.', + 'group' => 'Book', + ); + } + + public function setUp() { + $this->moduleList = array( + 'book' => 'core/modules/book/book.info.yml', + 'node' => 'core/modules/node/node.info.yml', + ); + parent::setUp(); + } + + /** + * Tests local task existence. + * + * @dataProvider getBookAdminRoutes + */ + public function testBookAdminLocalTasks($route) { + + $this->assertLocalTasks($route, array( + 0 => array('book.admin', 'book.settings'), + )); + } + + /** + * Provides a list of routes to test. + */ + public function getBookAdminRoutes() { + return array( + array('book.admin'), + array('book.settings'), + ); + } + + /** + * Tests local task existence. + * + * @dataProvider getBookNodeRoutes + */ + public function testBookNodeLocalTasks($route) { + $this->assertLocalTasks($route, array( + 0 => array('book.outline', 'node.view', 'node.page_edit', 'node.delete_confirm', 'node.revision_overview',), + )); + } + + /** + * Provides a list of routes to test. + */ + public function getBookNodeRoutes() { + return array( + array('node.view'), + array('book.outline'), + ); + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php index 50e7caf..9ff8307 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -358,8 +358,8 @@ public function submit(array $form, array &$form_state) { */ public function preview(array $form, array &$form_state) { $comment = $this->entity; - drupal_set_title(t('Preview comment'), PASS_THROUGH); $form_state['comment_preview'] = comment_preview($comment); + $form_state['comment_preview']['#title'] = $this->t('Preview comment'); $form_state['rebuild'] = TRUE; } diff --git a/core/modules/config/config.local_tasks.yml b/core/modules/config/config.local_tasks.yml new file mode 100644 index 0000000..03a4443 --- /dev/null +++ b/core/modules/config/config.local_tasks.yml @@ -0,0 +1,21 @@ +config.sync: + route_name: config.sync + tab_root_id: config.sync + title: 'Synchronize' + weight: 0 +config.export: + route_name: config.export + tab_root_id: config.sync + title: Export + weight: 1 +config.import: + route_name: config.import + title: Import + tab_root_id: config.sync + weight: 2 +config.sync_import: + route_name: config.sync + tab_root_id: config.sync + tab_parent_id: config.sync + title: 'Import' + weight: 0 diff --git a/core/modules/config/config.module b/core/modules/config/config.module index 5e93c02..76deec4 100644 --- a/core/modules/config/config.module +++ b/core/modules/config/config.module @@ -64,37 +64,8 @@ function config_menu() { $items['admin/config/development/configuration'] = array( 'title' => 'Configuration management', 'description' => 'Import, export, or synchronize your site configuration.', - 'route_name' => 'config_management', - ); - $items['admin/config/development/configuration/sync'] = array( - 'title' => 'Synchronize', - 'description' => 'Synchronize configuration changes.', 'route_name' => 'config.sync', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => 0, - ); - $items['admin/config/development/configuration/export'] = array( - 'title' => 'Export', - 'description' => 'Export your site configuration', - 'route_name' => 'config.export', - 'type' => MENU_LOCAL_TASK, - 'weight' => 1, - ); - $items['admin/config/development/configuration/import'] = array( - 'title' => 'Import', - 'description' => 'Import configuration for your site', - 'route_name' => 'config.import', - 'type' => MENU_LOCAL_TASK, - 'weight' => 2, - ); - $items['admin/config/development/configuration/sync/diff/%'] = array( - 'title' => 'Configuration file diff', - 'description' => 'Diff between active and staged configuration.', - 'route_name' => 'config.diff', - ); - $items['admin/config/development/configuration/sync/import'] = array( - 'title' => 'Import', - 'type' => MENU_DEFAULT_LOCAL_TASK, ); + return $items; } diff --git a/core/modules/config/config.routing.yml b/core/modules/config/config.routing.yml index 3eaeb34..c3a6850 100644 --- a/core/modules/config/config.routing.yml +++ b/core/modules/config/config.routing.yml @@ -1,14 +1,14 @@ -config.diff: - path: '/admin/config/development/configuration/sync/diff/{config_file}' +config.sync: + path: '/admin/config/development/configuration' defaults: - _content: '\Drupal\config\Controller\ConfigController::diff' + _form: '\Drupal\config\Form\ConfigSync' requirements: _permission: 'synchronize configuration' -config_management: - path: '/admin/config/development/configuration' +config.diff: + path: '/admin/config/development/configuration/sync/diff/{config_file}' defaults: - _form: '\Drupal\config\Form\ConfigSync' + _content: '\Drupal\config\Controller\ConfigController::diff' requirements: _permission: 'synchronize configuration' @@ -32,10 +32,3 @@ config.import: _form: '\Drupal\config\Form\ConfigImportForm' requirements: _permission: 'import configuration' - -config.sync: - path: '/admin/config/development/configuration/sync' - defaults: - _form: '\Drupal\config\Form\ConfigSync' - requirements: - _permission: 'synchronize configuration' diff --git a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php index 59cb82b..1c13a01 100644 --- a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php +++ b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php @@ -123,7 +123,7 @@ public function diff($config_file) { ), ), '#title' => "Back to 'Synchronize configuration' page.", - '#href' => 'admin/config/development/configuration/sync', + '#href' => 'admin/config/development/configuration', ); return $build; diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php index 3100919..2d5c4a8 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php @@ -96,7 +96,7 @@ public function submitForm(array &$form, array &$form_state) { } $archiver->extractList($files, config_get_config_directory(CONFIG_STAGING_DIRECTORY)); drupal_set_message($this->t('Your configuration files were successfully uploaded, ready for import.')); - $form_state['redirect'] = 'admin/config/development/configuration/sync'; + $form_state['redirect'] = 'admin/config/development/configuration'; } catch (\Exception $e) { form_set_error('import_tarball', $this->t('Could not extract the contents of the tar file. The error message is @message', array('@message' => $e->getMessage()))); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php index 58fc2d5..0148011 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php @@ -41,7 +41,7 @@ function testImport() { $storage = $this->container->get('config.storage'); $staging = $this->container->get('config.storage.staging'); - $this->drupalGet('admin/config/development/configuration/sync'); + $this->drupalGet('admin/config/development/configuration'); $this->assertText('There are no configuration changes.'); $this->assertNoFieldById('edit-submit', t('Import all')); @@ -65,7 +65,7 @@ function testImport() { $this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); // Verify that both appear as ready to import. - $this->drupalGet('admin/config/development/configuration/sync'); + $this->drupalGet('admin/config/development/configuration'); $this->assertText($name); $this->assertText($dynamic_name); $this->assertFieldById('edit-submit', t('Import all')); @@ -98,7 +98,7 @@ function testImportLock() { $this->prepareSiteNameUpdate($new_site_name); // Verify that there are configuration differences to import. - $this->drupalGet('admin/config/development/configuration/sync'); + $this->drupalGet('admin/config/development/configuration'); $this->assertNoText(t('There are no configuration changes.')); // Acquire a fake-lock on the import mechanism. diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index 661aac9..b31b8d8 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -5,6 +5,7 @@ * Allows entities to be translated into different languages. */ +use Drupal\content_translation\Plugin\Derivative\ContentTranslationLocalTasks; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityFormControllerInterface; use Drupal\Core\Entity\EntityInterface; @@ -277,6 +278,15 @@ function content_translation_menu_alter(array &$items) { } /** + * Implements hook_local_tasks_alter(). + */ +function content_translation_local_tasks_alter(&$local_tasks) { + // Alters in tab_root_id onto the content translation local task. + $derivative = ContentTranslationLocalTasks::create(\Drupal::getContainer(), 'content_translation.local_tasks'); + $derivative->alterLocalTasks($local_tasks); +} + +/** * Strips out menu loaders from the given path. * * @param string $path diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/ContentTranslationLocalTasks.php b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/ContentTranslationLocalTasks.php index 50c4c60..172b6be 100644 --- a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/ContentTranslationLocalTasks.php +++ b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/ContentTranslationLocalTasks.php @@ -15,27 +15,4 @@ */ class ContentTranslationLocalTasks extends LocalTaskDefault { - /** - * {@inheritdoc} - */ - public function getRouteParameters(Request $request) { - $parameters = parent::getRouteParameters($request); - $entity_type = $this->pluginDefinition['entity_type']; - if ($raw_variables = $request->attributes->get('_raw_variables')) { - // When the entity type is in the path, populate 'entity' for any dynamic - // local tasks. - if ($raw_variables->has($entity_type)) { - $entity = $raw_variables->get($entity_type); - $parameters['entity'] = $entity; - } - // When 'entity' is in the path, populate the parameters with the value - // for the actual entity type. - elseif ($raw_variables->has('entity')) { - $entity = $raw_variables->get('entity'); - $parameters[$entity_type] = $entity; - } - } - return $parameters; - } - } 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 1cc9e66..558bdfd 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 @@ -33,16 +33,26 @@ class ContentTranslationLocalTasks extends DerivativeBase implements ContainerDe protected $routeProvider; /** + * The base plugin ID + * + * @var string + */ + protected $basePluginId; + + /** * Constructs a new ContentTranslationLocalTasks. * + * @param string $base_plugin_id + * The base plugin ID. * @param \Drupal\Core\Entity\EntityManager $entity_manager * The entity manager. * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider * The route provider. */ - public function __construct(EntityManager $entity_manager, RouteProviderInterface $route_provider) { + public function __construct($base_plugin_id, EntityManager $entity_manager, RouteProviderInterface $route_provider) { $this->entityManager = $entity_manager; $this->routeProvider = $route_provider; + $this->basePluginId = $base_plugin_id; } /** @@ -50,6 +60,7 @@ public function __construct(EntityManager $entity_manager, RouteProviderInterfac */ public static function create(ContainerInterface $container, $base_plugin_id) { return new static( + $base_plugin_id, $container->get('entity.manager'), $container->get('router.route_provider') ); @@ -62,31 +73,72 @@ public function getDerivativeDefinitions(array $base_plugin_definition) { // Create tabs for all possible entity types. foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) { if ($entity_info['translatable'] && isset($entity_info['translation'])) { + // Find the route name for the translation overview. + $translation_route_name = "content_translation.translation_overview_$entity_type"; + $translation_tab = $translation_route_name; + + $this->derivatives[$translation_tab] = $base_plugin_definition + array( + 'entity_type' => $entity_type, + ); + $this->derivatives[$translation_tab]['title'] = 'Translate'; + $this->derivatives[$translation_tab]['route_name'] = $translation_route_name; + } + } + return parent::getDerivativeDefinitions($base_plugin_definition); + } + + /** + * Alters the local tasks to find the proper tab_root_id for each task. + */ + public function alterLocalTasks(array &$local_tasks) { + foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) { + if ($entity_info['translatable'] && isset($entity_info['translation'])) { $path = '/' . preg_replace('/%(.*)/', '{$1}', $entity_info['menu_base_path']); if ($routes = $this->routeProvider->getRoutesByPattern($path)->all()) { // Find the route name for the entity page. $entity_route_name = key($routes); - $entity_tab = $entity_route_name . '_tab'; + // Find the route name for the translation overview. $translation_route_name = "content_translation.translation_overview_$entity_type"; - $translation_tab = $translation_route_name . '_tab'; - - // Both tabs will have the same root and entity type. - $common_tab_settings = array( - 'tab_root_id' => $entity_tab, - 'entity_type' => $entity_type, - ); - $this->derivatives[$entity_tab] = $base_plugin_definition + $common_tab_settings; - $this->derivatives[$entity_tab]['title'] = t('Edit'); - $this->derivatives[$entity_tab]['route_name'] = $entity_route_name; - - $this->derivatives[$translation_tab] = $base_plugin_definition + $common_tab_settings; - $this->derivatives[$translation_tab]['title'] = t('Translate'); - $this->derivatives[$translation_tab]['route_name'] = $translation_route_name; + $translation_tab = $this->basePluginId . ':' . $translation_route_name; + + $local_tasks[$translation_tab]['tab_root_id'] = $this->getTaskFromRoute($entity_route_name, $local_tasks); } } } - return parent::getDerivativeDefinitions($base_plugin_definition); } + /** + * 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) { + $local_task = FALSE; + foreach ($local_tasks as $plugin_id => $local_task) { + if ($local_task['route_name'] == $route_name) { + $local_task = $plugin_id; + break; + } + } + + return $local_task; + } + + /** + * Translates a string to the current language or to a given language. + * + * See the t() documentation for details. + * + * @todo Move to derivative base. https://drupal.org/node/2112575 + */ + public function t($string, array $args = array(), array $options = array()) { + \Drupal::translation()->translate($string, $args, $options); + } } diff --git a/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php b/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php new file mode 100644 index 0000000..cde6985 --- /dev/null +++ b/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php @@ -0,0 +1,110 @@ + 'Content translation local tasks test', + 'description' => 'Test content translation local tasks.', + 'group' => 'Content Translation', + ); + } + + public function setUp() { + $this->moduleList = array( + 'content_translation' => 'core/modules/content_translation/content_translation.info.yml', + 'node' => 'core/modules/node/node.info.yml', + ); + parent::setUp(); + + // Entity manager stub for derivative building. + $entity_manager = $this->getMockBuilder('Drupal\Core\Entity\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + $entity_manager->expects($this->any()) + ->method('getDefinitions') + ->will($this->returnValue(array( + 'node' => array( + 'translatable' => true, + 'translation' => array( + 'content_translation' => array( + // things. + ), + ), + 'menu_base_path' => 'node/%node', + ), + ))); + \Drupal::getContainer()->set('entity.manager', $entity_manager); + + // Route provider for injecting node.view into derivative lookup. + $collection = $this->getMockBuilder('Symfony\Component\Routing\RouteCollection') + ->disableOriginalConstructor() + ->setMethods(array('all')) + ->getMock(); + $collection->expects($this->any()) + ->method('all') + ->will($this->returnValue(array('node.view' => array()))); + $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $route_provider->expects($this->any()) + ->method('getRoutesByPattern') + ->will($this->returnValue($collection)); + \Drupal::getContainer()->set('router.route_provider', $route_provider); + + // Stub for t(). + $string_translation = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface'); + $string_translation->expects($this->any()) + ->method('translate') + ->will($this->returnCallback(function($string) {return $string;})); + \Drupal::getContainer()->set('string_translation', $string_translation); + + // Load the content_translation.module file in order to run the alter hook. + require_once DRUPAL_ROOT . '/core/modules/content_translation/content_translation.module'; + } + + /** + * Tests the block admin display local tasks. + * + * @dataProvider providerTestBlockAdminDisplay + */ + public function testBlockAdminDisplay($route, $expected) { + $this->assertLocalTasks($route, $expected); + } + + /** + * Provides a list of routes to test. + */ + public function providerTestBlockAdminDisplay() { + return array( + array('node.view', array(array( + 'content_translation.local_tasks:content_translation.translation_overview_node', + 'node.view', + 'node.page_edit', + 'node.delete_confirm', + 'node.revision_overview', + ))), + array('content_translation.translation_overview_node', array(array( + 'content_translation.local_tasks:content_translation.translation_overview_node', + 'node.view', + 'node.page_edit', + 'node.delete_confirm', + 'node.revision_overview', + ))), + ); + } + +} diff --git a/core/modules/field_ui/field_ui.local_tasks.yml b/core/modules/field_ui/field_ui.local_tasks.yml new file mode 100644 index 0000000..e690491 --- /dev/null +++ b/core/modules/field_ui/field_ui.local_tasks.yml @@ -0,0 +1,4 @@ +field_ui.list: + title: Entities + route_name: field_ui.list + tab_root_id: field_ui.list diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index 0d81232..dc0d38f 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -60,10 +60,6 @@ function field_ui_menu() { 'route_name' => 'field_ui.list', 'type' => MENU_NORMAL_ITEM, ); - $items['admin/reports/fields/list'] = array( - 'title' => 'Entities', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); // Create tabs for all possible bundles. foreach (entity_get_info() as $entity_type => $entity_info) { diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Tests/FieldUIRouteTest.php b/core/modules/field_ui/lib/Drupal/field_ui/Tests/FieldUIRouteTest.php index 669b58e..d3764a4 100644 --- a/core/modules/field_ui/lib/Drupal/field_ui/Tests/FieldUIRouteTest.php +++ b/core/modules/field_ui/lib/Drupal/field_ui/Tests/FieldUIRouteTest.php @@ -51,7 +51,7 @@ public function testFieldUIRoutes() { //$this->assertText('No fields are present yet.'); $this->drupalGet('admin/structure/types/manage/article/fields'); - $this->assertTitle('Article | Drupal'); + $this->assertTitle('Manage fields | Drupal'); } } diff --git a/core/modules/forum/forum.local_tasks.yml b/core/modules/forum/forum.local_tasks.yml new file mode 100644 index 0000000..4cd9dcb --- /dev/null +++ b/core/modules/forum/forum.local_tasks.yml @@ -0,0 +1,9 @@ +forum.overview: + route_name: forum.overview + tab_root_id: forum.overview + title: List +forum.settings: + route_name: forum.settings + tab_root_id: forum.overview + title: Settings + weight: 100 diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index 392e4b4..1d30fcb 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -119,17 +119,6 @@ function forum_menu() { 'description' => 'Control forum hierarchy settings.', 'route_name' => 'forum.overview', ); - $items['admin/structure/forum/list'] = array( - 'title' => 'List', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/structure/forum/settings'] = array( - 'title' => 'Settings', - 'weight' => 100, - 'type' => MENU_LOCAL_TASK, - 'parent' => 'admin/structure/forum', - 'route_name' => 'forum.settings', - ); $items['admin/structure/forum/edit/container/%taxonomy_term'] = array( 'title' => 'Edit container', 'route_name' => 'forum.edit_container', diff --git a/core/modules/language/language.local_tasks.yml b/core/modules/language/language.local_tasks.yml new file mode 100644 index 0000000..690d3f8 --- /dev/null +++ b/core/modules/language/language.local_tasks.yml @@ -0,0 +1,19 @@ +system.date_format_language_overview: + title: 'Localize' + route_name: system.date_format_language_overview + tab_root_id: system.date_format_list + weight: -8 + +language.admin_overview: + title: 'List' + route_name: language.admin_overview + tab_root_id: language.admin_overview +language.negotiation: + title: 'Detection and selection' + route_name: language.negotiation + tab_root_id: language.admin_overview + +language.edit: + title: 'Edit' + route_name: language.edit + tab_root_id: language.edit diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 05e3776..3b30ffb 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -73,51 +73,6 @@ function language_menu() { 'route_name' => 'language.admin_overview', 'weight' => -10, ); - $items['admin/config/regional/language/list'] = array( - 'title' => 'List', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/config/regional/language/add'] = array( - 'route_name' => 'language.add', - 'type' => MENU_SIBLING_LOCAL_TASK, - ); - $items['admin/config/regional/language/edit/%language'] = array( - 'title' => 'Edit language', - 'route_name' => 'language.edit', - ); - $items['admin/config/regional/language/edit/%language/edit'] = array( - 'title' => 'Edit', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => -10, - ); - - // Language negotiation. - $items['admin/config/regional/language/detection'] = array( - 'title' => 'Detection and selection', - 'route_name' => 'language.negotiation', - 'weight' => 10, - 'type' => MENU_LOCAL_TASK, - ); - $items['admin/config/regional/language/detection/url'] = array( - 'title' => 'URL language detection configuration', - 'route_name' => 'language.negotiation_url', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - $items['admin/config/regional/language/detection/session'] = array( - 'title' => 'Session language detection configuration', - 'route_name' => 'language.negotiation_session', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - $items['admin/config/regional/language/detection/browser'] = array( - 'title' => 'Browser language detection configuration', - 'route_name' => 'language.negotiation_browser', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - $items['admin/config/regional/language/detection/selected'] = array( - 'title' => 'Selected language detection configuration', - 'route_name' => 'language.negotiation_selected', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); // Content language settings. $items['admin/config/regional/content-language'] = array( diff --git a/core/modules/language/language.routing.yml b/core/modules/language/language.routing.yml index ff7a4e7..3b76d56 100644 --- a/core/modules/language/language.routing.yml +++ b/core/modules/language/language.routing.yml @@ -30,6 +30,7 @@ language.edit: path: '/admin/config/regional/language/edit/{language_entity}' defaults: _entity_form: 'language_entity.edit' + _title: 'Edit language' requirements: _entity_access: 'language_entity.update' diff --git a/core/modules/locale/locale.local_tasks.yml b/core/modules/locale/locale.local_tasks.yml new file mode 100644 index 0000000..ade24ed --- /dev/null +++ b/core/modules/locale/locale.local_tasks.yml @@ -0,0 +1,22 @@ +locale.translate_page: + route_name: locale.translate_page + tab_root_id: locale.translate_page + title: Translate + +locale.translate_import: + route_name: locale.translate_import + tab_root_id: locale.translate_page + title: Import + weight: 20 + +locale.translate_export: + route_name: locale.translate_export + tab_root_id: locale.translate_page + title: Export + weight: 30 + +locale.settings: + route_name: locale.settings + tab_root_id: locale.translate_page + title: Settings + weight: 100 diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 1860239..30f134e 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -176,28 +176,7 @@ function locale_menu() { 'route_name' => 'locale.translate_page', 'weight' => -5, ); - $items['admin/config/regional/translate/translate'] = array( - 'title' => 'Translate', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/config/regional/translate/import'] = array( - 'title' => 'Import', - 'route_name' => 'locale.translate_import', - 'weight' => 20, - 'type' => MENU_LOCAL_TASK, - ); - $items['admin/config/regional/translate/export'] = array( - 'title' => 'Export', - 'route_name' => 'locale.translate_export', - 'weight' => 30, - 'type' => MENU_LOCAL_TASK, - ); - $items['admin/config/regional/translate/settings'] = array( - 'title' => 'Settings', - 'route_name' => 'locale.settings', - 'weight' => 100, - 'type' => MENU_LOCAL_TASK, - ); + $items['admin/reports/translations'] = array( 'title' => 'Available translation updates', 'route_name' => 'locale.translate_status', diff --git a/core/modules/locale/tests/Drupal/locale/Tests/Menu/LocaleLocalTasksTest.php b/core/modules/locale/tests/Drupal/locale/Tests/Menu/LocaleLocalTasksTest.php new file mode 100644 index 0000000..4c18831 --- /dev/null +++ b/core/modules/locale/tests/Drupal/locale/Tests/Menu/LocaleLocalTasksTest.php @@ -0,0 +1,59 @@ + 'Locale local tasks test', + 'description' => 'Test locale local tasks.', + 'group' => 'Locale', + ); + } + + public function setUp() { + $this->moduleList = array( + 'locale' => 'core/modules/locale/locale.info.yml', + ); + parent::setUp(); + } + + /** + * Checks locale listing local tasks. + * + * @dataProvider getLocalePageRoutes + */ + public function testLocalePageLocalTasks($route) { + $tasks = array( + 0 => array('locale.translate_page', 'locale.translate_import', 'locale.translate_export','locale.settings'), + ); + $this->assertLocalTasks($route, $tasks); + } + + /** + * Provides a list of routes to test. + */ + public function getLocalePageRoutes() { + return array( + array('locale.translate_page'), + array('locale.translate_import'), + array('locale.translate_export'), + array('locale.settings'), + ); + } + +} diff --git a/core/modules/node/node.local_tasks.yml b/core/modules/node/node.local_tasks.yml new file mode 100644 index 0000000..24d9ef2 --- /dev/null +++ b/core/modules/node/node.local_tasks.yml @@ -0,0 +1,18 @@ +node.view: + route_name: node.view + tab_root_id: node.view + title: 'View' +node.page_edit: + route_name: node.page_edit + tab_root_id: node.view + title: Edit +node.delete_confirm: + route_name: node.delete_confirm + tab_root_id: node.view + title: Delete + weight: 10 +node.revision_overview: + route_name: node.revision_overview + tab_root_id: node.view + title: 'Revisions' + weight: 20 diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 0ad789d..0aea122 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -971,15 +971,11 @@ function node_menu() { // overridden by a menu link. 'route_name' => 'node.view', ); - $items['node/%node/view'] = array( - 'title' => 'View', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); $items['node/%node/edit'] = array( 'title' => 'Edit', 'route_name' => 'node.page_edit', 'type' => MENU_LOCAL_TASK, - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'context' => MENU_CONTEXT_INLINE, ); $items['node/%node/delete'] = array( 'title' => 'Delete', @@ -988,12 +984,6 @@ function node_menu() { 'type' => MENU_LOCAL_TASK, 'context' => MENU_CONTEXT_INLINE, ); - $items['node/%node/revisions'] = array( - 'title' => 'Revisions', - 'route_name' => 'node.revision_overview', - 'weight' => 20, - 'type' => MENU_LOCAL_TASK, - ); $items['node/%node/revisions/%node_revision/view'] = array( 'title' => 'Revisions', 'route_name' => 'node.revision_show', diff --git a/core/modules/shortcut/shortcut.local_tasks.yml b/core/modules/shortcut/shortcut.local_tasks.yml new file mode 100644 index 0000000..edfeaeb --- /dev/null +++ b/core/modules/shortcut/shortcut.local_tasks.yml @@ -0,0 +1,4 @@ +shortcut.overview: + route_name: shortcut.overview + tab_root_id: user.view + title: 'Shortcuts' diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index 302d17b..dc9bd86 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -119,11 +119,6 @@ function shortcut_menu() { 'title' => 'Delete shortcut', 'route_name' => 'shortcut.link_delete', ); - $items['user/%user/shortcuts'] = array( - 'title' => 'Shortcuts', - 'route_name' => 'shortcut.overview', - 'type' => MENU_LOCAL_TASK, - ); return $items; } diff --git a/core/modules/shortcut/tests/Drupal/shortcut/Tests/Menu/ShortcutLocalTasksTest.php b/core/modules/shortcut/tests/Drupal/shortcut/Tests/Menu/ShortcutLocalTasksTest.php new file mode 100644 index 0000000..ff8bd7e --- /dev/null +++ b/core/modules/shortcut/tests/Drupal/shortcut/Tests/Menu/ShortcutLocalTasksTest.php @@ -0,0 +1,59 @@ + 'Shortcut local tasks test', + 'description' => 'Test shortcut local tasks.', + 'group' => 'Shortcut', + ); + } + + public function setUp() { + $this->moduleList = array( + 'shortcut' => 'core/modules/shortcut/shortcut.info.yml', + 'user' => 'core/modules/user/user.info.yml', + ); + parent::setUp(); + } + + /** + * Checks shortcut listing local tasks. + * + * @dataProvider getShortcutPageRoutes + */ + public function testShortcutPageLocalTasks($route) { + $tasks = array( + 0 => array('shortcut.overview', 'user.view', 'user.edit',), + ); + $this->assertLocalTasks($route, $tasks); + } + + /** + * Provides a list of routes to test. + */ + public function getShortcutPageRoutes() { + return array( + array('user.view'), + array('user.edit'), + array('shortcut.overview'), + ); + } + +} diff --git a/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php b/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php index ff52b5c..8c46595 100644 --- a/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php +++ b/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php @@ -65,25 +65,25 @@ public function getFormId() { /** * {@inheritdoc} * - * @param string $theme_name + * @param string $theme * The theme name. */ - public function buildForm(array $form, array &$form_state, $theme_name = '') { + public function buildForm(array $form, array &$form_state, $theme = '') { $form = parent::buildForm($form, $form_state); $themes = list_themes(); // Deny access if the theme is disabled or not found. - if (!empty($theme_name) && (empty($themes[$theme_name]) || !$themes[$theme_name]->status)) { + if (!empty($theme) && (empty($themes[$theme]) || !$themes[$theme]->status)) { throw new NotFoundHttpException(); } // Default settings are defined in theme_get_setting() in includes/theme.inc - if ($theme_name) { - $var = 'theme_' . $theme_name . '_settings'; - $config_key = $theme_name . '.settings'; + if ($theme) { + $var = 'theme_' . $theme . '_settings'; + $config_key = $theme . '.settings'; $themes = list_themes(); - $features = $themes[$theme_name]->info['features']; + $features = $themes[$theme]->info['features']; } else { $var = 'theme_settings'; @@ -129,8 +129,8 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { '#description' => t('Enable or disable the display of certain page elements.'), ); foreach ($toggles as $name => $title) { - if ((!$theme_name) || in_array($name, $features)) { - $form['theme_settings']['toggle_' . $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => theme_get_setting('features.' . $name, $theme_name)); + if ((!$theme) || in_array($name, $features)) { + $form['theme_settings']['toggle_' . $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => theme_get_setting('features.' . $name, $theme)); // Disable checkboxes for features not supported in the current configuration. if (isset($disabled['toggle_' . $name])) { $form['theme_settings']['toggle_' . $name]['#disabled'] = TRUE; @@ -145,7 +145,7 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { } // Logo settings, only available when file.module is enabled. - if ((!$theme_name) || in_array('logo', $features) && $this->moduleHandler->moduleExists('file')) { + if ((!$theme) || in_array('logo', $features) && $this->moduleHandler->moduleExists('file')) { $form['logo'] = array( '#type' => 'details', '#title' => t('Logo image settings'), @@ -160,7 +160,7 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { $form['logo']['default_logo'] = array( '#type' => 'checkbox', '#title' => t('Use the default logo supplied by the theme'), - '#default_value' => theme_get_setting('logo.use_default', $theme_name), + '#default_value' => theme_get_setting('logo.use_default', $theme), '#tree' => FALSE, ); $form['logo']['settings'] = array( @@ -175,7 +175,7 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { $form['logo']['settings']['logo_path'] = array( '#type' => 'textfield', '#title' => t('Path to custom logo'), - '#default_value' => theme_get_setting('logo.path', $theme_name), + '#default_value' => theme_get_setting('logo.path', $theme), ); $form['logo']['settings']['logo_upload'] = array( '#type' => 'file', @@ -185,7 +185,7 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { ); } - if ((!$theme_name) || in_array('favicon', $features) && $this->moduleHandler->moduleExists('file')) { + if ((!$theme) || in_array('favicon', $features) && $this->moduleHandler->moduleExists('file')) { $form['favicon'] = array( '#type' => 'details', '#title' => t('Shortcut icon settings'), @@ -201,7 +201,7 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { $form['favicon']['default_favicon'] = array( '#type' => 'checkbox', '#title' => t('Use the default shortcut icon supplied by the theme'), - '#default_value' => theme_get_setting('favicon.use_default', $theme_name), + '#default_value' => theme_get_setting('favicon.use_default', $theme), ); $form['favicon']['settings'] = array( '#type' => 'container', @@ -215,7 +215,7 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { $form['favicon']['settings']['favicon_path'] = array( '#type' => 'textfield', '#title' => t('Path to custom icon'), - '#default_value' => theme_get_setting('favicon.path', $theme_name), + '#default_value' => theme_get_setting('favicon.path', $theme), ); $form['favicon']['settings']['favicon_upload'] = array( '#type' => 'file', @@ -243,8 +243,8 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { if ($original_path && isset($friendly_path)) { $local_file = strtr($original_path, array('public:/' => PublicStream::basePath())); } - elseif ($theme_name) { - $local_file = drupal_get_path('theme', $theme_name) . '/' . $default; + elseif ($theme) { + $local_file = drupal_get_path('theme', $theme) . '/' . $default; } else { $local_file = path_to_theme() . '/' . $default; @@ -258,32 +258,32 @@ public function buildForm(array $form, array &$form_state, $theme_name = '') { } } - if ($theme_name) { + if ($theme) { // Call engine-specific settings. - $function = $themes[$theme_name]->prefix . '_engine_settings'; + $function = $themes[$theme]->prefix . '_engine_settings'; if (function_exists($function)) { $form['engine_specific'] = array( '#type' => 'details', '#title' => t('Theme-engine-specific settings'), - '#description' => t('These settings only exist for the themes based on the %engine theme engine.', array('%engine' => $themes[$theme_name]->prefix)), + '#description' => t('These settings only exist for the themes based on the %engine theme engine.', array('%engine' => $themes[$theme]->prefix)), ); $function($form, $form_state); } // Create a list which includes the current theme and all its base themes. - if (isset($themes[$theme_name]->base_themes)) { - $theme_keys = array_keys($themes[$theme_name]->base_themes); - $theme_keys[] = $theme_name; + if (isset($themes[$theme]->base_themes)) { + $theme_keys = array_keys($themes[$theme]->base_themes); + $theme_keys[] = $theme; } else { - $theme_keys = array($theme_name); + $theme_keys = array($theme); } // Save the name of the current theme (if any), so that we can temporarily // override the current theme and allow theme_get_setting() to work // without having to pass the theme name to it. $default_theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : NULL; - $GLOBALS['theme_key'] = $theme_name; + $GLOBALS['theme_key'] = $theme; // Process the theme and all its base themes. foreach ($theme_keys as $theme) { diff --git a/core/modules/system/lib/Drupal/system/Plugin/Derivative/ThemeLocalTask.php b/core/modules/system/lib/Drupal/system/Plugin/Derivative/ThemeLocalTask.php new file mode 100644 index 0000000..8a25fc3 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Plugin/Derivative/ThemeLocalTask.php @@ -0,0 +1,31 @@ + $theme) { + if ($theme->status) { + $this->derivatives[$theme_name] = $base_plugin_definition; + $this->derivatives[$theme_name]['title'] = $theme->info['name']; + $this->derivatives[$theme_name]['route_parameters'] = array('theme' => $theme_name); + } + } + return $this->derivatives; + } + +} diff --git a/core/modules/system/lib/Drupal/system/Routing/RouteSubscriber.php b/core/modules/system/lib/Drupal/system/Routing/RouteSubscriber.php deleted file mode 100644 index de5304e..0000000 --- a/core/modules/system/lib/Drupal/system/Routing/RouteSubscriber.php +++ /dev/null @@ -1,34 +0,0 @@ -status)) { - $route = new Route('admin/appearance/settings/' . $theme->name, array( - '_form' => '\Drupal\system\Form\ThemeSettingsForm', 'theme_name' => $theme->name), array( - '_permission' => 'administer themes', - )); - $collection->add('system.theme_settings_' . $theme->name, $route); - } - } - } - -} diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index f1a428f..6d7bc01 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -213,7 +213,9 @@ function system_theme_default() { // implementations, and doing the variable_set() before the theme_enable() // could result in a race condition where the theme is default but not // enabled. + \Drupal::service('router.builder')->rebuild(); menu_router_rebuild(); + \Drupal::cache('cache')->deleteTags(array('local_task' => 1)); // The status message depends on whether an admin theme is currently in use: // a value of 0 means the admin theme is set to be the default theme. diff --git a/core/modules/system/system.local_tasks.yml b/core/modules/system/system.local_tasks.yml index 575e515..3cf14ad 100644 --- a/core/modules/system/system.local_tasks.yml +++ b/core/modules/system/system.local_tasks.yml @@ -12,3 +12,52 @@ system.site_information_settings_tab: route_name: system.site_information_settings title: Settings tab_root_id: system.site_information_settings_tab + +system.themes_page: + route_name: system.themes_page + title: 'List' + tab_root_id: system.themes_page +system.theme_settings: + route_name: system.theme_settings + title: 'Settings' + tab_root_id: system.themes_page + weight: 100 + +system.theme_settings_global: + route_name: system.theme_settings + title: 'Global settings' + tab_root_id: system.themes_page + tab_parent_id: system.theme_settings + weight: -100 +system.theme_settings_theme: + route_name: system.theme_settings_theme + title: 'Theme name' + tab_root_id: system.themes_page + tab_parent_id: system.theme_settings + derivative: Drupal\system\Plugin\Derivative\ThemeLocalTask + +system.modules_list: + route_name: system.modules_list + tab_root_id: system.modules_list + title: 'List' +system.modules_uninstall: + route_name: system.modules_uninstall + tab_root_id: system.modules_list + title: 'Uninstall' + weight: 20 + +system.admin: + route_name: system.admin + tab_root_id: system.admin + title: 'Tasks' + weight: -20 +system.admin_index: + route_name: system.admin_index + tab_root_id: system.admin + title: 'Index' + weight: -18 + +system.date_format_list: + route_name: system.date_format_list + tab_root_id: system.date_format_list + title: 'List' diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 80b02f8..48ad4c6 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -620,17 +620,6 @@ function system_menu() { 'weight' => 9, 'menu_name' => 'admin', ); - $items['admin/tasks'] = array( - 'title' => 'Tasks', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => -20, - ); - $items['admin/index'] = array( - 'title' => 'Index', - 'route_name' => 'system.admin_index', - 'type' => MENU_LOCAL_TASK, - 'weight' => -18, - ); // Menu items that are basically just menu blocks. $items['admin/structure'] = array( @@ -648,34 +637,6 @@ function system_menu() { 'position' => 'left', 'weight' => -6, ); - $items['admin/appearance/list'] = array( - 'title' => 'List', - 'description' => 'Select and configure your theme', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'file' => 'system.admin.inc', - ); - $items['admin/appearance/settings'] = array( - 'title' => 'Settings', - 'description' => 'Configure default and theme specific settings.', - 'route_name' => 'system.theme_settings', - 'type' => MENU_LOCAL_TASK, - 'weight' => 100, - ); - // Theme configuration local tasks. - $items['admin/appearance/settings/global'] = array( - 'title' => 'Global settings', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - - foreach (list_themes(TRUE) as $theme) { - if (!empty($theme->status)) { - $items['admin/appearance/settings/' . $theme->name] = array( - 'title' => $theme->info['name'], - 'route_name' => 'system.theme_settings_' . $theme->name, - 'type' => MENU_LOCAL_TASK, - ); - } - } // Modules. $items['admin/modules'] = array( @@ -684,26 +645,6 @@ function system_menu() { 'route_name' => 'system.modules_list', 'weight' => -2, ); - $items['admin/modules/list'] = array( - 'title' => 'List', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/modules/list/confirm'] = array( - 'title' => 'List', - 'route_name' => 'system.modules_list_confirm', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - $items['admin/modules/uninstall'] = array( - 'title' => 'Uninstall', - 'route_name' => 'system.modules_uninstall', - 'type' => MENU_LOCAL_TASK, - 'weight' => 20, - ); - $items['admin/modules/uninstall/confirm'] = array( - 'title' => 'Uninstall', - 'route_name' => 'system.modules_uninstall_confirm', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); // Configuration. $items['admin/config'] = array( @@ -806,11 +747,6 @@ function system_menu() { 'description' => 'Allow users to edit a configured date format.', 'route_name' => 'system.date_format_edit', ); - $items['admin/config/regional/date-time/formats/manage/%/edit'] = array( - 'title' => 'Edit', - 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => -10, - ); // Search settings. $items['admin/config/search'] = array( @@ -890,7 +826,6 @@ function system_menu() { $items['admin/config/regional/date-time/locale'] = array( 'title' => 'Localize', 'description' => 'Configure date formats for each locale', - 'type' => MENU_LOCAL_TASK, 'weight' => -8, 'route_name' => 'system.date_format_language_overview', ); diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 4d1c90ac..69374b1 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -324,6 +324,13 @@ system.theme_settings: requirements: _permission: 'administer themes' +system.theme_settings_theme: + path: '/admin/appearance/settings/{theme}' + defaults: + _form: '\Drupal\system\Form\ThemeSettingsForm' + requirements: + _permission: 'administer themes' + '': path: '/' requirements: diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml index 6a68e29..3371dad 100644 --- a/core/modules/system/system.services.yml +++ b/core/modules/system/system.services.yml @@ -19,7 +19,3 @@ services: class: Drupal\system\PathProcessor\PathProcessorFiles tags: - { name: path_processor_inbound, priority: 200 } - system.route_subscriber: - class: Drupal\system\Routing\RouteSubscriber - tags: - - { name: event_subscriber } diff --git a/core/modules/system/tests/modules/entity_test/entity_test.local_tasks.yml b/core/modules/system/tests/modules/entity_test/entity_test.local_tasks.yml new file mode 100644 index 0000000..a411359 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/entity_test.local_tasks.yml @@ -0,0 +1,2 @@ +entity_test.local_tasks: + derivative: 'Drupal\entity_test\Plugin\Derivative\EntityTestLocalTasks' diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 75f2851..15a8269 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -259,11 +259,6 @@ function entity_test_menu() { 'title arguments' => array('@type' => $entity_type), 'route_name' => "entity_test.edit_$entity_type", ); - - $items[$entity_type . '/manage/%' . $entity_type . '/edit'] = array( - 'title' => 'Edit', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); } return $items; diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php index 053163b..ad22414 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Controller/EntityTestController.php @@ -8,6 +8,7 @@ namespace Drupal\entity_test\Controller; use Drupal\Core\Entity\EntityInterface; +use Symfony\Component\HttpFoundation\Request; /** * Controller routines for entity_test routes. @@ -24,7 +25,8 @@ public function testAdd($entity_type) { /** * @todo Remove entity_test_edit() */ - public function testEdit(EntityInterface $entity) { + public function testEdit(Request $request) { + $entity = $request->attributes->get($request->attributes->get('_entity_type')); return entity_test_edit($entity); } diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Derivative/EntityTestLocalTasks.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Derivative/EntityTestLocalTasks.php new file mode 100644 index 0000000..2f0950d --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Derivative/EntityTestLocalTasks.php @@ -0,0 +1,35 @@ +derivatives = array(); + $types = entity_test_entity_types(); + $types[] = 'entity_test_render'; + + foreach($types as $entity_type) { + $this->derivatives[$entity_type] = array(); + $this->derivatives[$entity_type]['tab_root_id'] = "entity_test.local_tasks:$entity_type"; + $this->derivatives[$entity_type]['route_name'] = "entity_test.edit_$entity_type"; + $this->derivatives[$entity_type]['title'] = 'Edit'; + } + + return parent::getDerivativeDefinitions($base_plugin_definition); + } + +} diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Routing/RouteSubscriber.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Routing/RouteSubscriber.php index ae09b55..c7102e7 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Routing/RouteSubscriber.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Routing/RouteSubscriber.php @@ -32,8 +32,8 @@ protected function routes(RouteCollection $collection) { $collection->add("entity_test.add_$entity_type", $route); $route = new Route( - "$entity_type/manage/{entity}", - array('_content' => '\Drupal\entity_test\Controller\EntityTestController::testEdit'), + "$entity_type/manage/{" . $entity_type . '}', + array('_content' => '\Drupal\entity_test\Controller\EntityTestController::testEdit', '_entity_type' => $entity_type), array('_permission' => 'administer entity_test content'), array('parameters' => array( 'entity' => array('type' => 'entity:' . $entity_type), diff --git a/core/modules/update/update.local_tasks.yml b/core/modules/update/update.local_tasks.yml new file mode 100644 index 0000000..1281a27 --- /dev/null +++ b/core/modules/update/update.local_tasks.yml @@ -0,0 +1,27 @@ +update.status: + route_name: update.status + tab_root_id: system.admin_reports + title: List +update.settings: + route_name: update.settings + tab_root_id: system.admin_reports + tab_parent_id: update.status + title: Settings + weight: 50 +update.report_install: + route_name: update.report_install + tab_root_id: system.admin_reports + title: Update + weight: 10 + +update.module_install: + route_name: update.module_install + tab_root_id: system.modules_list + title: Update + weight: 10 + +update.theme_install: + route_name: update.theme_install + tab_root_id: system.themes_page + title: Update + weight: 10 diff --git a/core/modules/update/update.module b/core/modules/update/update.module index 3c04664..e6dbf70 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -156,18 +156,6 @@ function update_menu() { 'route_name' => 'update.status', 'weight' => -50, ); - $items['admin/reports/updates/list'] = array( - 'title' => 'List', - 'access arguments' => array('administer site configuration'), - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - $items['admin/reports/updates/settings'] = array( - 'title' => 'Settings', - 'route_name' => 'update.settings', - 'access arguments' => array('administer site configuration'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 50, - ); // We want action links for updating projects at a few different locations: // both the module and theme administration pages, and on the available @@ -186,12 +174,6 @@ function update_menu() { 'weight' => 25, 'type' => MENU_LOCAL_ACTION, ); - $items[$path . '/update'] = array( - 'route_name' => "update.{$context}_update", - 'weight' => 10, - 'title' => 'Update', - 'type' => MENU_LOCAL_TASK, - ); } // Customize the titles of the action links depending on where they appear. // We use += array() to let the translation extractor find these menu titles. diff --git a/core/modules/user/tests/Drupal/user/Tests/Menu/UserLocalTasksTest.php b/core/modules/user/tests/Drupal/user/Tests/Menu/UserLocalTasksTest.php new file mode 100644 index 0000000..a978178 --- /dev/null +++ b/core/modules/user/tests/Drupal/user/Tests/Menu/UserLocalTasksTest.php @@ -0,0 +1,102 @@ + 'User local tasks test', + 'description' => 'Test user local tasks.', + 'group' => 'User', + ); + } + + public function setUp() { + $this->moduleList = array('user' => 'core/modules/user/user.info.yml'); + parent::setUp(); + } + + /** + * Tests local task existence. + * + * @dataProvider getUserAdminRoutes + */ + public function testUserAdminLocalTasks($route, $expected) { + $this->assertLocalTasks($route, $expected); + } + + /** + * Provides a list of routes to test. + */ + public function getUserAdminRoutes() { + return array( + array('user.role_list', array(array('user.role_list_tab'))), + array('user.account_settings', array(array('user.account_settings_tab'))), + ); + } + + /** + * Checks user listing local tasks. + * + * @dataProvider getUserLoginRoutes + */ + public function testUserLoginLocalTasks($route, $subtask = array()) { + $tasks = array( + 0 => array('user.page', 'user.register', 'user.pass',), + ); + if ($subtask) { + $tasks[] = $subtask; + } + $this->assertLocalTasks($route, $tasks); + } + + /** + * Provides a list of routes to test. + */ + public function getUserLoginRoutes() { + return array( + array('user.page', array('user.login',)), + array('user.login', array('user.login',)), + array('user.register'), + array('user.pass'), + ); + } + + /** + * Checks user listing local tasks. + * + * @dataProvider getUserPageRoutes + */ + public function testUserPageLocalTasks($route, $subtask = array()) { + $tasks = array( + 0 => array('user.view', 'user.edit',), + ); + if ($subtask) $tasks[] = $subtask; + $this->assertLocalTasks($route, $tasks); + } + + /** + * Provides a list of routes to test. + */ + public function getUserPageRoutes() { + return array( + array('user.view'), + array('user.edit'), + ); + } + +} diff --git a/core/modules/user/user.local_tasks.yml b/core/modules/user/user.local_tasks.yml index 0b40260..c69ac8e 100644 --- a/core/modules/user/user.local_tasks.yml +++ b/core/modules/user/user.local_tasks.yml @@ -2,7 +2,37 @@ user.role_list_tab: route_name: user.role_list title: 'Roles' tab_root_id: user.role_list_tab + user.account_settings_tab: route_name: user.account_settings title: 'Settings' tab_root_id: user.account_settings_tab + +user.page: + route_name: user.page + tab_root_id: user.page + title: 'Log in' + weight: -10 +user.register: + route_name: user.register + tab_root_id: user.page + title: 'Create new account' +user.pass: + route_name: user.pass + tab_root_id: user.page + title: 'Request new password' +# Other authentication methods may add pages below user/login/. +user.login: + route_name: user.login + tab_root_id: user.page + tab_parent_id: user.page + title: 'Username and password' + +user.view: + route_name: user.view + tab_root_id: user.view + title: View +user.edit: + route_name: user.edit + tab_root_id: user.view + title: Edit diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 41dbae9..faf0157 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -738,27 +738,6 @@ function user_menu() { 'menu_name' => 'account', ); - $items['user/login'] = array( - 'title' => 'Log in', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - // Other authentication methods may add pages below user/login/. - $items['user/login/default'] = array( - 'title' => 'Username and password', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); - - $items['user/register'] = array( - 'title' => 'Create new account', - 'type' => MENU_LOCAL_TASK, - 'route_name' => 'user.register', - ); - - $items['user/password'] = array( - 'title' => 'Request new password', - 'route_name' => 'user.pass', - 'type' => MENU_LOCAL_TASK, - ); // Since menu_get_ancestors() does not support multiple placeholders in a row, // this MENU_CALLBACK cannot be removed yet. $items['user/reset/%/%/%'] = array( @@ -836,10 +815,6 @@ function user_menu() { 'title arguments' => array(1), 'route_name' => 'user.view', ); - $items['user/%user/view'] = array( - 'title' => 'View', - 'type' => MENU_DEFAULT_LOCAL_TASK, - ); $items['user/%user/cancel'] = array( 'route_name' => 'user.cancel', ); @@ -847,11 +822,6 @@ function user_menu() { 'title' => 'Confirm account cancellation', 'route_name' => 'user.cancel_confirm', ); - $items['user/%user/edit'] = array( - 'title' => 'Edit', - 'route_name' => 'user.edit', - 'type' => MENU_LOCAL_TASK, - ); return $items; } diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index 169343b..25cfc3e 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -2,6 +2,7 @@ user.register: path: '/user/register' defaults: _entity_form: 'user.register' + _title: 'Create new account' requirements: _access_user_register: 'TRUE' @@ -108,6 +109,7 @@ user.pass: path: '/user/password' defaults: _form: '\Drupal\user\Form\UserPasswordForm' + _title: 'Request new password' requirements: _access: 'TRUE' @@ -131,6 +133,7 @@ user.login: path: '/user/login' defaults: _form: '\Drupal\user\Form\UserLoginForm' + _title: 'Log in' requirements: _access: 'TRUE' diff --git a/core/modules/views_ui/views_ui.local_tasks.yml b/core/modules/views_ui/views_ui.local_tasks.yml index 68fc7dc..7974734 100644 --- a/core/modules/views_ui/views_ui.local_tasks.yml +++ b/core/modules/views_ui/views_ui.local_tasks.yml @@ -20,3 +20,8 @@ views_ui.list_tab: route_name: views_ui.list title: List tab_root_id: views_ui.list_tab + +views_ui.reports_fields: + route_name: views_ui.reports_fields + title: 'Used in views' + tab_root_id: field_ui.list diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module index 0f76cbd..93742a4 100644 --- a/core/modules/views_ui/views_ui.module +++ b/core/modules/views_ui/views_ui.module @@ -25,20 +25,6 @@ function views_ui_menu() { 'route_name' => 'views_ui.list', ); - // @todo - remove all items of type MENU_VISIBLE_IN_BREADCRUMB - // when there is a route-aware breadcrumb builder. - $items['admin/structure/views/settings'] = array( - 'title' => 'Settings', - 'route_name' => 'views_ui.settings_basic', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - - $items['admin/structure/views/settings/advanced'] = array( - 'title' => 'Advanced', - 'route_name' => 'views_ui.settings_advanced', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - // The primary Edit View page. Secondary tabs for each Display are added in // views_ui_menu_local_tasks_alter(). $items['admin/structure/views/view/%'] = array( @@ -47,33 +33,7 @@ function views_ui_menu() { $items['admin/structure/views/view/%/edit'] = array( 'title' => 'Edit view', 'type' => MENU_DEFAULT_LOCAL_TASK, - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, - ); - $items['admin/structure/views/view/%/preview/%'] = array( - 'route_name' => 'views_ui.preview', - 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - - // Additional pages for acting on a View. - - $items['admin/structure/views/view/%/break-lock'] = array( - 'title' => 'Break lock', - 'route_name' => 'views_ui.break_lock', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - - // NoJS/AJAX callbacks that can use the default Views AJAX form system. - $items['admin/structure/views/nojs/%/%'] = array( - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - - // A page in the Reports section to show usage of fields in all views - $items['admin/reports/fields/views-fields'] = array( - 'title' => 'Used in views', - 'description' => 'Overview of fields used in all views.', - 'route_name' => 'views_ui.reports_fields', - 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, ); // A page in the Reports section to show usage of plugins in all views. diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskIntegrationTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskIntegrationTest.php new file mode 100644 index 0000000..61ee601 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskIntegrationTest.php @@ -0,0 +1,139 @@ +getConfigFactoryStub(array()); + $container->set('config.factory', $config_factory); + \Drupal::setContainer($container); + } + + protected function tearDown() { + // Passes in an empty container. + $container = new ContainerBuilder(); + \Drupal::setContainer($container); + } + + /** + * Sets up the local task manager for the test. + */ + protected function getLocalTaskManager($modules, $route_name, $route_params) { + $manager = $this + ->getMockBuilder('Drupal\Core\Menu\LocalTaskManager') + ->disableOriginalConstructor() + ->setMethods(NULL) + ->getMock(); + + $controllerResolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface'); + $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'controllerResolver'); + $property->setAccessible(TRUE); + $property->setValue($manager, $controllerResolver); + + // todo mock a request with a route. + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'request'); + $property->setAccessible(TRUE); + $property->setValue($manager, $request); + + $accessManager = $this->getMockBuilder('Drupal\Core\Access\AccessManager') + ->disableOriginalConstructor() + ->getMock(); $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'accessManager'); + $property->setAccessible(TRUE); + $property->setValue($manager, $accessManager); + + $module_handler = new ModuleHandler($modules); + $pluginDiscovery = new YamlDiscovery('local_tasks', $module_handler->getModuleDirectories()); + $pluginDiscovery = new ContainerDerivativeDiscoveryDecorator($pluginDiscovery); + $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery'); + $property->setAccessible(TRUE); + $property->setValue($manager, $pluginDiscovery); + + $method = new \ReflectionMethod('Drupal\Core\Menu\LocalTaskManager', 'alterInfo'); + $method->setAccessible(TRUE); + $method->invoke($manager, $module_handler, 'local_tasks'); + + $plugin_stub = $this->getMock('Drupal\Core\Menu\LocalTaskInterface'); + $factory = $this->getMock('Drupal\Component\Plugin\Factory\FactoryInterface'); + $factory->expects($this->any()) + ->method('createInstance') + ->will($this->returnValue($plugin_stub)); + $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'factory'); + $property->setAccessible(TRUE); + $property->setValue($manager, $factory); + + $language_manager = $this->getMockBuilder('Drupal\Core\Language\LanguageManager') + ->disableOriginalConstructor() + ->getMock(); + $language_manager->expects($this->any()) + ->method('getLanguage') + ->will($this->returnValue(new Language(array('id' => 'en')))); + + $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $manager->setCacheBackend($cache_backend, $language_manager, 'local_task', array('local_task' => 1)); + + return $manager; + } + + /** + * Tests integration for local tasks. + * + * @param $route_name + * Route name to base task building on. + * @param $expected_tasks + * A list of tasks groups by level expected at the given route + * @param array $route_params + * (optional) a list of route parameters used to resolve tasks. + */ + protected function assertLocalTasks($route_name, $expected_tasks, $route_params = array()) { + + $manager = $this->getLocalTaskManager($this->moduleList, $route_name, $route_params); + + $tmp_tasks = $manager->getLocalTasksForRoute($route_name); + + // At this point we're just testing existence so pull out keys and then + // compare. + // + // Deeper testing would require a functioning factory which because we are + // using the DefaultPluginManager base means we get into dependency soup + // because its factories create method and pulling services off the \Drupal + // container. + $tasks = array(); + foreach ($tmp_tasks as $level => $level_tasks) { + $tasks[$level] = array_keys($level_tasks); + } + $this->assertEquals($expected_tasks, $tasks); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php index f10bb38..049e3ee 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php @@ -166,7 +166,7 @@ public function testGetLocalTaskForRouteWithEmptyCache() { $this->cacheBackend->expects($this->at(3)) ->method('set') - ->with('local_task:en:menu_local_task_test_tasks_view', $expected_set, CacheBackendInterface::CACHE_PERMANENT, array('local_task')); + ->with('local_task:en:menu_local_task_test_tasks_view', $expected_set, CacheBackendInterface::CACHE_PERMANENT, array('local_task' => 1)); $local_tasks = $this->manager->getLocalTasksForRoute('menu_local_task_test_tasks_view'); $this->assertEquals($result, $local_tasks); @@ -254,7 +254,7 @@ protected function setupLocalTaskManager() { ->method('getLanguage') ->will($this->returnValue(new Language(array('id' => 'en')))); - $this->manager->setCacheBackend($this->cacheBackend, $language_manager, 'local_task'); + $this->manager->setCacheBackend($this->cacheBackend, $language_manager, 'local_task', array('local_task' => 1)); } /**