diff --git a/core/core.services.yml b/core/core.services.yml index 97c2dcb..99403ac 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -498,10 +498,10 @@ services: arguments: ['@menu.link_tree', '@entity.manager', '@string_translation'] plugin.manager.menu.local_action: class: Drupal\Core\Menu\LocalActionManager - arguments: ['@controller_resolver', '@request_stack', '@current_route_match', '@router.route_provider', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user'] + arguments: ['@controller_resolver', '@request_stack', '@current_route_match', '@router.route_provider', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user', '@renderer'] plugin.manager.menu.local_task: class: Drupal\Core\Menu\LocalTaskManager - arguments: ['@controller_resolver', '@request_stack', '@current_route_match', '@router.route_provider', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user'] + arguments: ['@controller_resolver', '@request_stack', '@current_route_match', '@router.route_provider', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user', '@renderer'] plugin.manager.menu.contextual_link: class: Drupal\Core\Menu\ContextualLinkManager arguments: ['@controller_resolver', '@module_handler', '@cache.discovery', '@language_manager', '@access_manager', '@current_user', '@request_stack'] diff --git a/core/includes/menu.inc b/core/includes/menu.inc index d578499..23aba4f 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -130,8 +130,8 @@ function menu_local_tasks($level = 0) { if (!\Drupal::request()->attributes->has('exception') && !empty($route_name)) { $manager = \Drupal::service('plugin.manager.menu.local_task'); $local_tasks = $manager->getTasksBuild($route_name); - foreach ($local_tasks as $level => $items) { - $data['tabs'][$level] = empty($data['tabs'][$level]) ? $items : array_merge($data['tabs'][$level], $items); + foreach ($local_tasks as $tab_level => $items) { + $data['tabs'][$tab_level] = empty($data['tabs'][$tab_level]) ? $items : array_merge($data['tabs'][$tab_level], $items); } } @@ -177,16 +177,6 @@ function menu_secondary_local_tasks() { } /** - * Returns the rendered local actions at the current level. - */ -function menu_get_local_actions() { - $links = menu_local_tasks(); - $route_name = Drupal::routeMatch()->getRouteName(); - $manager = \Drupal::service('plugin.manager.menu.local_action'); - return $manager->getActionsForRoute($route_name) + $links['actions']; -} - -/** * Returns the router path, or the path for a default local task's parent. */ function menu_tab_root_path() { diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 7400017..ca03450 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1362,14 +1362,6 @@ function template_preprocess_page(&$variables) { $variables['is_front'] = FALSE; $variables['db_is_active'] = FALSE; } - if (!defined('MAINTENANCE_MODE')) { - $variables['action_links'] = menu_get_local_actions(); - $variables['tabs'] = menu_local_tabs(); - } - else { - $variables['action_links'] = array(); - $variables['tabs'] = array(); - } if ($node = \Drupal::routeMatch()->getParameter('node')) { $variables['node'] = $node; diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index f53bb0c..4b84b49 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -269,6 +269,8 @@ public function rebuildThemeData() { 'page_top' => 'Page top', 'page_bottom' => 'Page bottom', 'breadcrumb' => 'Breadcrumb', + 'tabs' => 'Tabs', + 'actions' => 'Local actions', ), 'description' => '', 'features' => $this->defaultFeatures, diff --git a/core/lib/Drupal/Core/Menu/LocalActionManager.php b/core/lib/Drupal/Core/Menu/LocalActionManager.php index db6acef..c7182bd 100644 --- a/core/lib/Drupal/Core/Menu/LocalActionManager.php +++ b/core/lib/Drupal/Core/Menu/LocalActionManager.php @@ -15,6 +15,7 @@ use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator; use Drupal\Core\Plugin\Discovery\YamlDiscovery; use Drupal\Core\Plugin\Factory\ContainerFactory; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Url; @@ -101,6 +102,13 @@ class LocalActionManager extends DefaultPluginManager implements LocalActionMana protected $instances = array(); /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** * Constructs a LocalActionManager object. * * @param \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface $controller_resolver @@ -121,8 +129,10 @@ class LocalActionManager extends DefaultPluginManager implements LocalActionMana * The access manager. * @param \Drupal\Core\Session\AccountInterface $account * The current user. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. */ - public function __construct(ControllerResolverInterface $controller_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account) { + public function __construct(ControllerResolverInterface $controller_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account, RendererInterface $renderer) { // Skip calling the parent constructor, since that assumes annotation-based // discovery. $this->factory = new ContainerFactory($this, 'Drupal\Core\Menu\LocalActionInterface'); @@ -133,6 +143,7 @@ public function __construct(ControllerResolverInterface $controller_resolver, Re $this->accessManager = $access_manager; $this->moduleHandler = $module_handler; $this->account = $account; + $this->renderer = $renderer; $this->alterInfo('menu_local_actions'); $this->setCacheBackend($cache_backend, 'local_action_plugins:' . $language_manager->getCurrentLanguage()->getId(), array('local_action')); } @@ -183,6 +194,7 @@ public function getActionsForRoute($route_appears) { foreach ($this->instances[$route_appears] as $plugin_id => $plugin) { $route_name = $plugin->getRouteName(); $route_parameters = $plugin->getRouteParameters($this->routeMatch); + $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE); $links[$plugin_id] = array( '#theme' => 'menu_local_action', '#link' => array( @@ -190,10 +202,13 @@ public function getActionsForRoute($route_appears) { 'url' => Url::fromRoute($route_name, $route_parameters), 'localized_options' => $plugin->getOptions($this->routeMatch), ), - '#access' => $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account), + '#access' => $access->isAllowed(), '#weight' => $plugin->getWeight(), ); + // @todo Remove this after https://www.drupal.org/node/2487600 has landed. + $this->renderer->addCacheableDependency($links, $access); } + return $links; } diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php index 70b1102..a0f1969 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php @@ -20,6 +20,7 @@ use Drupal\Core\Plugin\Factory\ContainerFactory; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteProviderInterface; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Symfony\Component\HttpFoundation\RequestStack; @@ -103,6 +104,13 @@ class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerI protected $account; /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** * Constructs a \Drupal\Core\Menu\LocalTaskManager object. * * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver @@ -123,8 +131,10 @@ class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerI * The access manager. * @param \Drupal\Core\Session\AccountInterface $account * The current user. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. */ - public function __construct(ControllerResolverInterface $controller_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account) { + public function __construct(ControllerResolverInterface $controller_resolver, RequestStack $request_stack, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account, RendererInterface $renderer) { $this->factory = new ContainerFactory($this, '\Drupal\Core\Menu\LocalTaskInterface'); $this->controllerResolver = $controller_resolver; $this->requestStack = $request_stack; @@ -133,6 +143,7 @@ public function __construct(ControllerResolverInterface $controller_resolver, Re $this->accessManager = $access_manager; $this->account = $account; $this->moduleHandler = $module_handler; + $this->renderer = $renderer; $this->alterInfo('local_tasks'); $this->setCacheBackend($cache, 'local_task_plugins:' . $language_manager->getCurrentLanguage()->getId(), array('local_task')); } @@ -302,8 +313,8 @@ public function getTasksBuild($current_route_name) { $route_parameters = $child->getRouteParameters($this->routeMatch); // Find out whether the user has access to the task. - $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account); - if ($access) { + $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE); + if ($access->isAllowed()) { $active = $this->isRouteActive($current_route_name, $route_name, $route_parameters); // The plugin may have been set active in getLocalTasksForRoute() if @@ -321,11 +332,15 @@ public function getTasksBuild($current_route_name) { '#link' => $link, '#active' => $active, '#weight' => $child->getWeight(), - '#access' => $access, + '#access' => $access->isAllowed(), ); + // @todo Remove this after https://www.drupal.org/node/2487600 has + // landed. + $this->renderer->addCacheableDependency($build, $access); } } } + return $build; } diff --git a/core/modules/aggregator/src/Tests/AggregatorTestBase.php b/core/modules/aggregator/src/Tests/AggregatorTestBase.php index 3c5a452..e35dcd5 100644 --- a/core/modules/aggregator/src/Tests/AggregatorTestBase.php +++ b/core/modules/aggregator/src/Tests/AggregatorTestBase.php @@ -28,7 +28,7 @@ * * @var array */ - public static $modules = array('node', 'aggregator', 'aggregator_test', 'views'); + public static $modules = ['block', 'node', 'aggregator', 'aggregator_test', 'views']; /** * {@inheritdoc} @@ -43,6 +43,7 @@ protected function setUp() { $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer news feeds', 'access news feeds', 'create article content')); $this->drupalLogin($this->adminUser); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/block/src/Tests/BlockHiddenRegionTest.php b/core/modules/block/src/Tests/BlockHiddenRegionTest.php index b5abe7c..6f0bd66 100644 --- a/core/modules/block/src/Tests/BlockHiddenRegionTest.php +++ b/core/modules/block/src/Tests/BlockHiddenRegionTest.php @@ -42,6 +42,7 @@ protected function setUp() { $this->drupalLogin($this->adminUser); $this->drupalPlaceBlock('search_form_block'); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php index d6e0999..3f6aa16 100644 --- a/core/modules/block/src/Tests/BlockTest.php +++ b/core/modules/block/src/Tests/BlockTest.php @@ -222,6 +222,7 @@ public function testBlockThemeSelector() { function testThemeName() { // Enable the help block. $this->drupalPlaceBlock('help_block', array('region' => 'help')); + $this->drupalPlaceBlock('system_tabs_block'); // Explicitly set the default and admin themes. $theme = 'block_test_specialchars_theme'; \Drupal::service('theme_handler')->install(array($theme)); diff --git a/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php b/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php index 563d12d..e2f8c74 100644 --- a/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php +++ b/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php @@ -24,6 +24,15 @@ class NonDefaultBlockAdminTest extends WebTestBase { public static $modules = array('block'); /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_tabs_block'); + } + + /** * Test non-default theme admin. */ function testNonDefaultBlockAdmin() { diff --git a/core/modules/block_content/src/Tests/BlockContentTestBase.php b/core/modules/block_content/src/Tests/BlockContentTestBase.php index 82139c0..4500fd0 100644 --- a/core/modules/block_content/src/Tests/BlockContentTestBase.php +++ b/core/modules/block_content/src/Tests/BlockContentTestBase.php @@ -60,6 +60,7 @@ protected function setUp() { } $this->adminUser = $this->drupalCreateUser($this->permissions); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/comment/src/Tests/CommentTestBase.php b/core/modules/comment/src/Tests/CommentTestBase.php index 8ebc4e7..fa6595c 100644 --- a/core/modules/comment/src/Tests/CommentTestBase.php +++ b/core/modules/comment/src/Tests/CommentTestBase.php @@ -27,7 +27,7 @@ * * @var array */ - public static $modules = array('comment', 'node', 'history', 'field_ui', 'datetime'); + public static $modules = ['block', 'comment', 'node', 'history', 'field_ui', 'datetime']; /** * An administrative user with permission to configure comment settings. @@ -86,6 +86,7 @@ protected function setUp() { // Create a test node authored by the web user. $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->webUser->id())); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/config/src/Tests/ConfigEntityListTest.php b/core/modules/config/src/Tests/ConfigEntityListTest.php index 73571ce..14d69fb 100644 --- a/core/modules/config/src/Tests/ConfigEntityListTest.php +++ b/core/modules/config/src/Tests/ConfigEntityListTest.php @@ -23,7 +23,7 @@ class ConfigEntityListTest extends WebTestBase { * * @var array */ - public static $modules = array('config_test'); + public static $modules = ['block', 'config_test']; /** * {@inheritdoc} @@ -33,6 +33,7 @@ protected function setUp() { // Delete the override config_test entity since it is not required by this // test. \Drupal::entityManager()->getStorage('config_test')->load('override')->delete(); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationListUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationListUiTest.php index 4ceec20..634d934 100644 --- a/core/modules/config_translation/src/Tests/ConfigTranslationListUiTest.php +++ b/core/modules/config_translation/src/Tests/ConfigTranslationListUiTest.php @@ -80,6 +80,7 @@ protected function setUp() { $this->config('locale.settings') ->set('translation.import_enabled', TRUE) ->save(); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationOverviewTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationOverviewTest.php index a2f6a6c..98cc8a2 100644 --- a/core/modules/config_translation/src/Tests/ConfigTranslationOverviewTest.php +++ b/core/modules/config_translation/src/Tests/ConfigTranslationOverviewTest.php @@ -23,7 +23,7 @@ class ConfigTranslationOverviewTest extends WebTestBase { * * @var array */ - public static $modules = array('contact', 'config_translation', 'views', 'views_ui', 'contextual', 'config_test', 'config_translation_test'); + public static $modules = ['block', 'contact', 'config_translation', 'views', 'views_ui', 'contextual', 'config_test', 'config_translation_test']; /** * Languages to enable. @@ -58,6 +58,7 @@ protected function setUp() { ConfigurableLanguage::createFromLangcode($langcode)->save(); } $this->localeStorage = $this->container->get('locale.storage'); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php index c7218df..061b98d 100644 --- a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php +++ b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php @@ -28,7 +28,7 @@ class ConfigTranslationUiTest extends WebTestBase { * * @var array */ - public static $modules = array('node', 'contact', 'contact_test', 'config_translation', 'config_translation_test', 'views', 'views_ui', 'contextual', 'filter', 'filter_test'); + public static $modules = ['block', 'node', 'contact', 'contact_test', 'config_translation', 'config_translation_test', 'views', 'views_ui', 'contextual', 'filter', 'filter_test']; /** * Languages to enable. @@ -100,6 +100,7 @@ protected function setUp() { ConfigurableLanguage::createFromLangcode($langcode)->save(); } $this->localeStorage = $this->container->get('locale.storage'); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/contact/src/Tests/ContactSitewideTest.php b/core/modules/contact/src/Tests/ContactSitewideTest.php index c815d35..d37d1d7 100644 --- a/core/modules/contact/src/Tests/ContactSitewideTest.php +++ b/core/modules/contact/src/Tests/ContactSitewideTest.php @@ -39,6 +39,7 @@ class ContactSitewideTest extends WebTestBase { protected function setUp() { parent::setUp(); $this->drupalPlaceBlock('system_breadcrumb_block'); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/contact/src/Tests/ContactStorageTest.php b/core/modules/contact/src/Tests/ContactStorageTest.php index 5d62e10..f18d10e 100644 --- a/core/modules/contact/src/Tests/ContactStorageTest.php +++ b/core/modules/contact/src/Tests/ContactStorageTest.php @@ -28,13 +28,14 @@ class ContactStorageTest extends ContactSitewideTest { * * @var array */ - public static $modules = array( + public static $modules = [ + 'block', 'text', 'contact', 'field_ui', 'contact_storage_test', 'contact_test', - ); + ]; /** * Tests configuration options and the site-wide contact form. diff --git a/core/modules/field_ui/src/Tests/EntityDisplayModeTest.php b/core/modules/field_ui/src/Tests/EntityDisplayModeTest.php index 7b7fe12..62b2768 100644 --- a/core/modules/field_ui/src/Tests/EntityDisplayModeTest.php +++ b/core/modules/field_ui/src/Tests/EntityDisplayModeTest.php @@ -19,9 +19,18 @@ class EntityDisplayModeTest extends WebTestBase { /** * Modules to enable. * - * @var array + * @var string[] */ - public static $modules = array('entity_test', 'field_ui'); + public static $modules = ['block', 'entity_test', 'field_ui']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_local_actions_block'); + } /** * Tests the EntityViewMode user interface. diff --git a/core/modules/field_ui/src/Tests/FieldUIRouteTest.php b/core/modules/field_ui/src/Tests/FieldUIRouteTest.php index 332a87d..4a6380b 100644 --- a/core/modules/field_ui/src/Tests/FieldUIRouteTest.php +++ b/core/modules/field_ui/src/Tests/FieldUIRouteTest.php @@ -23,7 +23,7 @@ class FieldUIRouteTest extends WebTestBase { * * @var string[] */ - public static $modules = array('entity_test', 'field_ui'); + public static $modules = ['block', 'entity_test', 'field_ui']; /** * {@inheritdoc} @@ -32,6 +32,7 @@ protected function setUp() { parent::setUp(); $this->drupalLogin($this->rootUser); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/field_ui/src/Tests/ManageFieldsTest.php b/core/modules/field_ui/src/Tests/ManageFieldsTest.php index 3b90bff..28517fc 100644 --- a/core/modules/field_ui/src/Tests/ManageFieldsTest.php +++ b/core/modules/field_ui/src/Tests/ManageFieldsTest.php @@ -66,7 +66,10 @@ class ManageFieldsTest extends WebTestBase { */ protected function setUp() { parent::setUp(); + $this->drupalPlaceBlock('system_breadcrumb_block'); + $this->drupalPlaceBlock('system_local_actions_block'); + $this->drupalPlaceBlock('system_tabs_block'); // Create a test user. $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer taxonomy', 'administer taxonomy_term fields', 'administer taxonomy_term display', 'administer users', 'administer account settings', 'administer user display', 'bypass node access')); diff --git a/core/modules/filter/src/Tests/FilterAdminTest.php b/core/modules/filter/src/Tests/FilterAdminTest.php index 112c486..3c1bbf7 100644 --- a/core/modules/filter/src/Tests/FilterAdminTest.php +++ b/core/modules/filter/src/Tests/FilterAdminTest.php @@ -22,7 +22,7 @@ class FilterAdminTest extends WebTestBase { /** * {@inheritdoc} */ - public static $modules = array('filter', 'node'); + public static $modules = ['block', 'filter', 'node']; /** * An user with administration permissions. @@ -105,6 +105,7 @@ protected function setUp() { user_role_grant_permissions('authenticated', array($basic_html_format->getPermissionName())); user_role_grant_permissions('anonymous', array($restricted_html_format->getPermissionName())); $this->drupalLogin($this->adminUser); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/filter/src/Tests/FilterFormatAccessTest.php b/core/modules/filter/src/Tests/FilterFormatAccessTest.php index 32d5d04..c407e69 100644 --- a/core/modules/filter/src/Tests/FilterFormatAccessTest.php +++ b/core/modules/filter/src/Tests/FilterFormatAccessTest.php @@ -24,7 +24,7 @@ class FilterFormatAccessTest extends WebTestBase { * * @var array */ - public static $modules = array('filter', 'node'); + public static $modules = ['block', 'filter', 'node']; /** * A user with administrative permissions. @@ -114,6 +114,7 @@ protected function setUp() { $this->secondAllowedFormat->getPermissionName(), $this->disallowedFormat->getPermissionName(), )); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/forum/src/Tests/ForumTest.php b/core/modules/forum/src/Tests/ForumTest.php index 9440179..0036b58 100644 --- a/core/modules/forum/src/Tests/ForumTest.php +++ b/core/modules/forum/src/Tests/ForumTest.php @@ -113,6 +113,7 @@ protected function setUp() { 'access comments', )); $this->drupalPlaceBlock('help_block', array('region' => 'help')); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/language/src/Tests/LanguagePathMonolingualTest.php b/core/modules/language/src/Tests/LanguagePathMonolingualTest.php index d3aa814..a92b758 100644 --- a/core/modules/language/src/Tests/LanguagePathMonolingualTest.php +++ b/core/modules/language/src/Tests/LanguagePathMonolingualTest.php @@ -21,7 +21,7 @@ class LanguagePathMonolingualTest extends WebTestBase { * * @var array */ - public static $modules = array('language', 'path'); + public static $modules = ['block', 'language', 'path']; protected function setUp() { parent::setUp(); @@ -56,6 +56,7 @@ protected function setUp() { // Set language detection to URL. $edit = array('language_interface[enabled][language-url]' => TRUE); $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings')); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/language/src/Tests/LanguageTourTest.php b/core/modules/language/src/Tests/LanguageTourTest.php index 98418b6..91cbdab 100644 --- a/core/modules/language/src/Tests/LanguageTourTest.php +++ b/core/modules/language/src/Tests/LanguageTourTest.php @@ -28,7 +28,7 @@ class LanguageTourTest extends TourTestBase { * * @var array */ - public static $modules = array('language', 'tour'); + public static $modules = ['block', 'language', 'tour']; /** * {@inheritdoc} @@ -37,6 +37,7 @@ protected function setUp() { parent::setUp(); $this->adminUser = $this->drupalCreateUser(array('administer languages', 'access tour')); $this->drupalLogin($this->adminUser); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/node/src/Tests/PageEditTest.php b/core/modules/node/src/Tests/PageEditTest.php index 582ab6f..201f284 100644 --- a/core/modules/node/src/Tests/PageEditTest.php +++ b/core/modules/node/src/Tests/PageEditTest.php @@ -16,11 +16,19 @@ class PageEditTest extends NodeTestBase { protected $webUser; protected $adminUser; + /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block', 'node', 'datetime']; + protected function setUp() { parent::setUp(); $this->webUser = $this->drupalCreateUser(array('edit own page content', 'create page content')); $this->adminUser = $this->drupalCreateUser(array('bypass node access', 'administer nodes')); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php b/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php index 95bd739..5da1182 100644 --- a/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php +++ b/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php @@ -75,6 +75,7 @@ function testPageCacheTags() { 'route.menu_active_trails:footer', 'route.menu_active_trails:main', 'route.menu_active_trails:tools', + 'route.name', 'theme', 'timezone', 'user.permissions', @@ -97,6 +98,8 @@ function testPageCacheTags() { 'config:block.block.bartik_main_menu', 'config:block.block.bartik_account_menu', 'config:block.block.bartik_messages', + 'config:block.block.bartik_local_actions', + 'config:block.block.bartik_tabs', 'node_view', 'node:' . $node_1->id(), 'user:' . $author_1->id(), @@ -129,6 +132,8 @@ function testPageCacheTags() { 'config:block.block.bartik_main_menu', 'config:block.block.bartik_account_menu', 'config:block.block.bartik_messages', + 'config:block.block.bartik_local_actions', + 'config:block.block.bartik_tabs', 'node_view', 'node:' . $node_2->id(), 'user:' . $author_2->id(), diff --git a/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php b/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php index a99ea45..c897616 100644 --- a/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php +++ b/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php @@ -58,6 +58,7 @@ protected function setUp() { // Enable the search block. $this->drupalPlaceBlock('search_form_block'); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/search/src/Tests/SearchPageTextTest.php b/core/modules/search/src/Tests/SearchPageTextTest.php index 4614baf..7a9adb0 100644 --- a/core/modules/search/src/Tests/SearchPageTextTest.php +++ b/core/modules/search/src/Tests/SearchPageTextTest.php @@ -23,11 +23,22 @@ class SearchPageTextTest extends SearchTestBase { */ protected $searchingUser; + /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block']; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); // Create user. $this->searchingUser = $this->drupalCreateUser(array('search content', 'access user profiles', 'use advanced search')); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php index db98745..c1493f9 100644 --- a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php +++ b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php @@ -18,6 +18,22 @@ class ShortcutSetsTest extends ShortcutTestBase { /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_local_actions_block'); + } + + /** * Tests creating a shortcut set. */ function testShortcutSetAdd() { diff --git a/core/modules/simpletest/src/Tests/BrowserTest.php b/core/modules/simpletest/src/Tests/BrowserTest.php index d43f63b..4a04b4d 100644 --- a/core/modules/simpletest/src/Tests/BrowserTest.php +++ b/core/modules/simpletest/src/Tests/BrowserTest.php @@ -24,6 +24,22 @@ class BrowserTest extends WebTestBase { protected static $cookieSet = FALSE; /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_tabs_block'); + } + + /** * Test \Drupal\simpletest\WebTestBase::getAbsoluteUrl(). */ function testGetAbsoluteUrl() { diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 7cde50d..3b7ed3f 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -353,6 +353,17 @@ block.settings.system_menu_block:*: type: integer label: 'Maximum number of levels' +block.settings.system_tabs_block: + type: block_settings + label: 'Tabs block' + mapping: + primary: + type: boolean + label: 'Whether primary tabs are shown' + secondary: + type: boolean + label: 'Whether secondary tabs are shown' + condition.plugin.request_path: type: condition.plugin mapping: diff --git a/core/modules/system/src/Plugin/Block/SystemLocalActionsBlock.php b/core/modules/system/src/Plugin/Block/SystemLocalActionsBlock.php new file mode 100644 index 0000000..fb17c14 --- /dev/null +++ b/core/modules/system/src/Plugin/Block/SystemLocalActionsBlock.php @@ -0,0 +1,129 @@ +localActionManager = $local_action_manager; + $this->routeMatch = $route_match; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('plugin.manager.menu.local_action'), + $container->get('current_route_match') + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return ['label_display' => FALSE]; + } + + /** + * {@inheritdoc} + */ + public function build() { + $build = []; + $links = menu_local_tasks(); + $route_name = $this->routeMatch->getRouteName(); + $action_links = $this->localActionManager->getActionsForRoute($route_name) + $links['actions']; + if (empty($action_links)) { + return []; + } + + $build['action_links'] = $action_links; + + return $build; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // The "Local actions" block is never cacheable because hooks creating local + // actions don't provide cacheability metadata. + // @todo Remove after https://www.drupal.org/node/2511516 has landed. + $form['cache']['#disabled'] = TRUE; + $form['cache']['#description'] = $this->t('This block is never cacheable.'); + $form['cache']['max_age']['#value'] = 0; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // @todo Remove after https://www.drupal.org/node/2511516 has landed. + return 0; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['route.name']; + } + +} diff --git a/core/modules/system/src/Plugin/Block/SystemTabsBlock.php b/core/modules/system/src/Plugin/Block/SystemTabsBlock.php new file mode 100644 index 0000000..9168455 --- /dev/null +++ b/core/modules/system/src/Plugin/Block/SystemTabsBlock.php @@ -0,0 +1,146 @@ + FALSE, + 'primary' => TRUE, + 'secondary' => TRUE, + ]; + } + + /** + * {@inheritdoc} + */ + public function build() { + $config = $this->configuration; + + $tabs = [ + '#theme' => 'menu_local_tasks', + ]; + + // Add only selected levels for the printed output. + if ($config['primary']) { + $tabs += [ + '#primary' => menu_primary_local_tasks(), + ]; + } + if ($config['secondary']) { + $tabs += [ + '#secondary' => menu_secondary_local_tasks(), + ]; + } + + if (empty($tabs['#primary']) && empty($tabs['#secondary'])) { + return []; + } + + $build['tabs'] = $tabs; + + return $build; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // The "Page actions" block is never cacheable because of hooks creating + // local tasks doesn't provide cacheability metadata. + // @todo Remove after https://www.drupal.org/node/2511516 has landed. + $form['cache']['#disabled'] = TRUE; + $form['cache']['#description'] = $this->t('This block is never cacheable.'); + $form['cache']['max_age']['#value'] = 0; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // @todo Remove after https://www.drupal.org/node/2511516 has landed. + return 0; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['route.name']; + } + + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + $config = $this->configuration; + $defaults = $this->defaultConfiguration(); + + $form['levels'] = array( + '#type' => 'details', + '#title' => $this->t('Shown tabs'), + '#description' => $this->t('Select tabs being shown in the block'), + // Open if not set to defaults. + '#open' => $defaults['primary'] !== $config['primary'] || $defaults['secondary'] !== $config['secondary'], + ); + $form['levels']['primary'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Show primary tabs'), + '#default_value' => $config['primary'], + ]; + $form['levels']['secondary'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Show secondary tabs'), + '#default_value' => $config['secondary'], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function blockSubmit($form, FormStateInterface $form_state) { + $levels = $form_state->getValue('levels'); + $this->configuration['primary'] = $levels['primary']; + $this->configuration['secondary'] = $levels['secondary']; + } + +} diff --git a/core/modules/system/src/Tests/Menu/LocalActionTest.php b/core/modules/system/src/Tests/Menu/LocalActionTest.php index c049198..bd1c593 100644 --- a/core/modules/system/src/Tests/Menu/LocalActionTest.php +++ b/core/modules/system/src/Tests/Menu/LocalActionTest.php @@ -18,9 +18,20 @@ class LocalActionTest extends WebTestBase { /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block', 'menu_test']; + + /** * {@inheritdoc} */ - public static $modules = array('menu_test'); + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_local_actions_block'); + } /** * Tests appearance of local actions. diff --git a/core/modules/system/src/Tests/Menu/LocalTasksTest.php b/core/modules/system/src/Tests/Menu/LocalTasksTest.php index b7824ef..168b037 100644 --- a/core/modules/system/src/Tests/Menu/LocalTasksTest.php +++ b/core/modules/system/src/Tests/Menu/LocalTasksTest.php @@ -17,7 +17,28 @@ */ class LocalTasksTest extends WebTestBase { - public static $modules = array('menu_test', 'entity_test'); + /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block', 'menu_test', 'entity_test']; + + /** + * The local tasks block under testing. + * + * @var \Drupal\block\Entity\Block + */ + protected $sut; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->sut = $this->drupalPlaceBlock('system_tabs_block', ['id' => 'tabs_block']); + } /** * Asserts local tasks in the page output. @@ -48,6 +69,20 @@ protected function assertLocalTasks(array $routes, $level = 0) { } /** + * Asserts that the local tasks on the specified level are not being printed. + * + * @param int $level + * (optional) The local tasks level to assert; 0 for primary, 1 for + * secondary. Defaults to 0. + */ + protected function assertNoLocalTasks($level = 0) { + $elements = $this->xpath('//*[contains(@class, :class)]//a', array( + ':class' => $level == 0 ? 'tabs primary' : 'tabs secondary', + )); + $this->assertFalse(count($elements), 'Local tasks not found.'); + } + + /** * Tests the plugin based local tasks. */ public function testPluginLocalTask() { @@ -135,4 +170,52 @@ public function testPluginLocalTask() { $this->assertEqual('upcasting sub2', (string) $result[0]->a, 'The "upcasting sub2" tab is active.'); } + /** + * Tests that local task blocks are configurable to show a specific level. + */ + public function testLocalTaskBlock() { + // Remove the default block and create a new one. + $this->sut->delete(); + + $this->sut = $this->drupalPlaceBlock('system_tabs_block', [ + 'id' => 'tabs_block', + 'primary' => TRUE, + 'secondary' => FALSE, + ]); + + $this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings')); + + // Verify that local tasks in the first level appear. + $this->assertLocalTasks([ + ['menu_test.local_task_test_tasks_view', []], + ['menu_test.local_task_test_tasks_edit', []], + ['menu_test.local_task_test_tasks_settings', []], + ]); + + // Verify that local tasks in the second level doesn't appear. + $this->assertNoLocalTasks(1); + + $this->sut->delete(); + $this->sut = $this->drupalPlaceBlock('system_tabs_block', [ + 'id' => 'tabs_block', + 'primary' => FALSE, + 'secondary' => TRUE, + ]); + + $this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings')); + + // Verify that local tasks in the first level doesn't appear. + $this->assertNoLocalTasks(0); + + // Verify that local tasks in the second level appear. + $sub_tasks = [ + ['menu_test.local_task_test_tasks_settings_sub1', []], + ['menu_test.local_task_test_tasks_settings_sub2', []], + ['menu_test.local_task_test_tasks_settings_sub3', []], + ['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive1']], + ['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive2']], + ]; + $this->assertLocalTasks($sub_tasks, 1); + } + } diff --git a/core/modules/system/src/Tests/Menu/MenuRouterTest.php b/core/modules/system/src/Tests/Menu/MenuRouterTest.php index d75bd6c..6e0eab0 100644 --- a/core/modules/system/src/Tests/Menu/MenuRouterTest.php +++ b/core/modules/system/src/Tests/Menu/MenuRouterTest.php @@ -43,6 +43,7 @@ protected function setUp() { parent::setUp(); $this->drupalPlaceBlock('system_menu_block:tools'); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/system/src/Tests/Menu/MenuTranslateTest.php b/core/modules/system/src/Tests/Menu/MenuTranslateTest.php index e316c95..e02a304 100644 --- a/core/modules/system/src/Tests/Menu/MenuTranslateTest.php +++ b/core/modules/system/src/Tests/Menu/MenuTranslateTest.php @@ -23,7 +23,16 @@ class MenuTranslateTest extends WebTestBase { * * @var array */ - public static $modules = array('menu_test'); + public static $modules = ['block', 'menu_test']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_tabs_block'); + } /** * Tests _menu_translate(). diff --git a/core/modules/system/src/Tests/System/DateTimeTest.php b/core/modules/system/src/Tests/System/DateTimeTest.php index 9f0b0ac..9817fc6 100644 --- a/core/modules/system/src/Tests/System/DateTimeTest.php +++ b/core/modules/system/src/Tests/System/DateTimeTest.php @@ -22,13 +22,14 @@ class DateTimeTest extends WebTestBase { * * @var array */ - public static $modules = array('node', 'language'); + public static $modules = ['block', 'node', 'language']; protected function setUp() { parent::setUp(); // Create admin user and log in admin user. $this->drupalLogin ($this->drupalCreateUser(array('administer site configuration'))); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/system/src/Tests/System/ThemeTest.php b/core/modules/system/src/Tests/System/ThemeTest.php index d5bb6f6..26fd4ef 100644 --- a/core/modules/system/src/Tests/System/ThemeTest.php +++ b/core/modules/system/src/Tests/System/ThemeTest.php @@ -30,7 +30,7 @@ class ThemeTest extends WebTestBase { * * @var array */ - public static $modules = array('node', 'block', 'file'); + public static $modules = ['node', 'block', 'file']; protected function setUp() { parent::setUp(); @@ -40,6 +40,7 @@ protected function setUp() { $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'view the administration theme', 'administer themes', 'bypass node access', 'administer blocks')); $this->drupalLogin($this->adminUser); $this->node = $this->drupalCreateNode(); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 96f0083..e88ab2d 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -782,6 +782,14 @@ function system_preprocess_block(&$variables) { } break; + case 'system_local_actions_block': + $variables['action_links'] = $variables['content']['action_links']; + break; + + case 'system_tabs_block': + $variables['tabs'] = $variables['content']['tabs']; + break; + case 'system_powered_by_block': $variables['attributes']['role'] = 'complementary'; break; diff --git a/core/modules/system/templates/page.html.twig b/core/modules/system/templates/page.html.twig index c5a3711..6200539 100644 --- a/core/modules/system/templates/page.html.twig +++ b/core/modules/system/templates/page.html.twig @@ -32,10 +32,6 @@ * - title_suffix: Additional output populated by modules, intended to be * displayed after the main title tag that appears in the template. * - messages: Status and error messages. Should be displayed prominently. - * - tabs: Tabs linking to any sub-pages beneath the current page (e.g., the - * view and edit tabs when displaying a node). - * - action_links: Actions local to the page, such as "Add menu" on the menu - * administration interface. * - node: Fully loaded node, if there is an automatically-loaded node * associated with the page and the node ID is the second argument in the * page's path (e.g. node/12345 and node/12345/revisions, but not @@ -52,6 +48,8 @@ * - page.sidebar_second: Items for the second sidebar. * - page.footer: Items for the footer region. * - page.breadcrumb: Items for the breadcrumb region. + * - page.tabs: Items for the tabs region. + * - page.actions: Items for the actions region. * * @see template_preprocess_page() * @see html.html.twig @@ -111,11 +109,9 @@ {% endif %} {{ title_suffix }} - {{ tabs }} + {{ page.tabs }} - {% if action_links %} - - {% endif %} + {{ page.actions }} {{ page.content }} {# /.layout-content #} diff --git a/core/modules/taxonomy/src/Tests/TermTest.php b/core/modules/taxonomy/src/Tests/TermTest.php index bfeeb86..5fe8e96 100644 --- a/core/modules/taxonomy/src/Tests/TermTest.php +++ b/core/modules/taxonomy/src/Tests/TermTest.php @@ -35,8 +35,22 @@ class TermTest extends TaxonomyTestBase { */ protected $field; + /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block']; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); + + $this->drupalPlaceBlock('system_local_actions_block'); + $this->drupalPlaceBlock('system_tabs_block'); + $this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access'])); $this->vocabulary = $this->createVocabulary(); @@ -308,10 +322,7 @@ function testTermInterface() { // Submitting a term takes us to the add page; we need the List page. $this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview'); - // Test edit link as accessed from Taxonomy administration pages. - // Because Simpletest creates its own database when running tests, we know - // the first edit link found on the listing page is to our term. - $this->clickLink(t('Edit'), 1); + $this->clickLink(t('Edit')); $this->assertRaw($edit['name[0][value]'], 'The randomly generated term name is present.'); $this->assertText($edit['description[0][value]'], 'The randomly generated term description is present.'); diff --git a/core/modules/taxonomy/src/Tests/VocabularyUiTest.php b/core/modules/taxonomy/src/Tests/VocabularyUiTest.php index 3a34c2e..69eba32 100644 --- a/core/modules/taxonomy/src/Tests/VocabularyUiTest.php +++ b/core/modules/taxonomy/src/Tests/VocabularyUiTest.php @@ -29,6 +29,7 @@ protected function setUp() { parent::setUp(); $this->drupalLogin($this->drupalCreateUser(['administer taxonomy'])); $this->vocabulary = $this->createVocabulary(); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/tour/src/Tests/TourTest.php b/core/modules/tour/src/Tests/TourTest.php index 8ed261c..2a4d101 100644 --- a/core/modules/tour/src/Tests/TourTest.php +++ b/core/modules/tour/src/Tests/TourTest.php @@ -21,7 +21,7 @@ class TourTest extends TourTestBasic { * * @var array */ - public static $modules = array('tour', 'locale', 'language', 'tour_test'); + public static $modules = ['block', 'tour', 'locale', 'language', 'tour_test']; /** * The permissions required for a logged in user to test tour tips. @@ -42,6 +42,18 @@ class TourTest extends TourTestBasic { ); /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('system_local_actions_block', [ + 'theme' => 'seven', + 'region' => 'actions' + ]); + } + + /** * Test tour functionality. */ public function testTourFunctionality() { diff --git a/core/modules/tracker/src/Tests/TrackerTest.php b/core/modules/tracker/src/Tests/TrackerTest.php index c20c054..82ede27 100644 --- a/core/modules/tracker/src/Tests/TrackerTest.php +++ b/core/modules/tracker/src/Tests/TrackerTest.php @@ -29,7 +29,7 @@ class TrackerTest extends WebTestBase { * * @var array */ - public static $modules = array('comment', 'tracker', 'history', 'node_test'); + public static $modules = ['block', 'comment', 'tracker', 'history', 'node_test']; /** * The main user for testing. @@ -54,6 +54,8 @@ protected function setUp() { $this->user = $this->drupalCreateUser($permissions); $this->otherUser = $this->drupalCreateUser($permissions); $this->addDefaultCommentField('node', 'page'); + $this->drupalPlaceBlock('system_tabs_block', ['id' => 'page_tabs_block']); + $this->drupalPlaceBlock('system_local_actions_block', ['id' => 'page_actions_block']); } /** @@ -77,9 +79,17 @@ function testTrackerAll() { $this->assertLink(t('My recent content'), 0, 'User tab shows up on the global tracker page.'); // Assert cache contexts, specifically the pager and node access contexts. - $this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args.pagers:0', 'user.node_grants:view', 'user.permissions']); + $this->assertCacheContexts(['languages:language_interface', 'route.name', 'theme', 'url.query_args.pagers:0', 'user.node_grants:view', 'user.permissions']); // Assert cache tags for the visible node and node list cache tag. - $this->assertCacheTags(Cache::mergeTags($published->getCacheTags(), $published->getOwner()->getCacheTags(), ['node_list', 'rendered'])); + $additional_cache_tags = [ + 'node_list', + 'rendered', + 'block_view', + 'config:block.block.page_actions_block', + 'config:block.block.page_tabs_block', + 'config:block_list', + ]; + $this->assertCacheTags(Cache::mergeTags($published->getCacheTags(), $published->getOwner()->getCacheTags(), $additional_cache_tags)); // Delete a node and ensure it no longer appears on the tracker. $published->delete(); @@ -142,7 +152,7 @@ function testTrackerUser() { // Assert cache contexts; the node grant context is not directly visible due // to it being implied by the user context. - $this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args.pagers:0', 'user']); + $this->assertCacheContexts(['languages:language_interface', 'route.name', 'theme', 'url.query_args.pagers:0', 'user']); // Assert cache tags for the visible nodes (including owners) and node list // cache tag. $tags = Cache::mergeTags( @@ -150,7 +160,14 @@ function testTrackerUser() { $my_published->getOwner()->getCacheTags(), $other_published_my_comment->getCacheTags(), $other_published_my_comment->getOwner()->getCacheTags(), - ['node_list', 'rendered'] + [ + 'node_list', + 'rendered', + 'block_view', + 'config:block.block.page_actions_block', + 'config:block.block.page_tabs_block', + 'config:block_list', + ] ); $this->assertCacheTags($tags); diff --git a/core/modules/update/src/Tests/UpdateCoreTest.php b/core/modules/update/src/Tests/UpdateCoreTest.php index 3aa598a..289baaf 100644 --- a/core/modules/update/src/Tests/UpdateCoreTest.php +++ b/core/modules/update/src/Tests/UpdateCoreTest.php @@ -22,12 +22,13 @@ class UpdateCoreTest extends UpdateTestBase { * * @var array */ - public static $modules = array('update_test', 'update', 'language'); + public static $modules = ['update_test', 'update', 'language', 'block']; protected function setUp() { parent::setUp(); $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer modules', 'administer themes')); $this->drupalLogin($admin_user); + $this->drupalPlaceBlock('system_local_actions_block'); } /** diff --git a/core/modules/user/src/Tests/UserRoleAdminTest.php b/core/modules/user/src/Tests/UserRoleAdminTest.php index c19130c..defccfc 100644 --- a/core/modules/user/src/Tests/UserRoleAdminTest.php +++ b/core/modules/user/src/Tests/UserRoleAdminTest.php @@ -25,9 +25,20 @@ class UserRoleAdminTest extends WebTestBase { */ protected $adminUser; + /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block']; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); $this->adminUser = $this->drupalCreateUser(array('administer permissions', 'administer users')); + $this->drupalPlaceBlock('system_tabs_block'); } /** diff --git a/core/modules/views/src/Tests/Plugin/DisplayPageWebTest.php b/core/modules/views/src/Tests/Plugin/DisplayPageWebTest.php index 31bfd3c..dddd4ce 100644 --- a/core/modules/views/src/Tests/Plugin/DisplayPageWebTest.php +++ b/core/modules/views/src/Tests/Plugin/DisplayPageWebTest.php @@ -33,10 +33,14 @@ class DisplayPageWebTest extends PluginTestBase { */ public static $modules = ['menu_ui', 'block']; + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); $this->enableViewsTestModule(); + $this->drupalPlaceBlock('system_tabs_block'); } /** @@ -53,7 +57,7 @@ public function testArguments() { $this->drupalGet('test_route_with_argument/1'); $this->assertResponse(200); - $this->assertCacheContexts(['languages:language_interface', 'theme', 'url']); + $this->assertCacheContexts(['languages:language_interface', 'route.name', 'theme', 'url']); $result = $this->xpath('//span[@class="field-content"]'); $this->assertEqual(count($result), 1, 'Ensure that just the filtered entry was returned.'); $this->assertEqual((string) $result[0], 1, 'The passed ID was returned.'); diff --git a/core/modules/views/src/Tests/Wizard/WizardTestBase.php b/core/modules/views/src/Tests/Wizard/WizardTestBase.php index 0a2e998..7b3b275 100644 --- a/core/modules/views/src/Tests/Wizard/WizardTestBase.php +++ b/core/modules/views/src/Tests/Wizard/WizardTestBase.php @@ -27,6 +27,7 @@ protected function setUp() { // Create and log in a user with administer views permission. $views_admin = $this->drupalCreateUser(array('administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view all revisions')); $this->drupalLogin($views_admin); + $this->drupalPlaceBlock('system_local_actions_block'); } } diff --git a/core/modules/views_ui/src/Tests/SettingsTest.php b/core/modules/views_ui/src/Tests/SettingsTest.php index 262fc7a..62acd9d 100644 --- a/core/modules/views_ui/src/Tests/SettingsTest.php +++ b/core/modules/views_ui/src/Tests/SettingsTest.php @@ -22,6 +22,14 @@ class SettingsTest extends UITestBase { protected $adminUser; /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->drupalPlaceBlock('system_tabs_block'); + } + + /** * Tests the settings for the edit ui. */ function testEditUI() { diff --git a/core/profiles/minimal/config/install/block.block.stark_local_actions.yml b/core/profiles/minimal/config/install/block.block.stark_local_actions.yml new file mode 100644 index 0000000..3471435 --- /dev/null +++ b/core/profiles/minimal/config/install/block.block.stark_local_actions.yml @@ -0,0 +1,18 @@ +id: bartik_local_actions +theme: stark +weight: 10 +status: true +langcode: en +region: actions +plugin: system_local_actions_block +settings: + id: system_local_actions_block + label: Local actions + provider: system + label_display: '0' +dependencies: + module: + - system + theme: + - stark +visibility: { } diff --git a/core/profiles/minimal/config/install/block.block.stark_tabs.yml b/core/profiles/minimal/config/install/block.block.stark_tabs.yml new file mode 100644 index 0000000..f692525 --- /dev/null +++ b/core/profiles/minimal/config/install/block.block.stark_tabs.yml @@ -0,0 +1,18 @@ +id: bartik_tabs +theme: stark +weight: 10 +status: true +langcode: en +region: tabs +plugin: system_tabs_block +settings: + id: system_tabs_block + label: Tabs + provider: system + label_display: '0' +dependencies: + module: + - system + theme: + - stark +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_local_actions.yml b/core/profiles/standard/config/install/block.block.bartik_local_actions.yml new file mode 100644 index 0000000..6dee235 --- /dev/null +++ b/core/profiles/standard/config/install/block.block.bartik_local_actions.yml @@ -0,0 +1,18 @@ +id: bartik_local_actions +theme: bartik +weight: 10 +status: true +langcode: en +region: actions +plugin: system_local_actions_block +settings: + id: system_local_actions_block + label: Local actions + provider: system + label_display: '0' +dependencies: + module: + - system + theme: + - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.bartik_tabs.yml b/core/profiles/standard/config/install/block.block.bartik_tabs.yml new file mode 100644 index 0000000..559a60f --- /dev/null +++ b/core/profiles/standard/config/install/block.block.bartik_tabs.yml @@ -0,0 +1,18 @@ +id: bartik_tabs +theme: bartik +weight: 10 +status: true +langcode: en +region: tabs +plugin: system_tabs_block +settings: + id: system_tabs_block + label: Tabs + provider: system + label_display: '0' +dependencies: + module: + - system + theme: + - bartik +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.seven_local_actions.yml b/core/profiles/standard/config/install/block.block.seven_local_actions.yml new file mode 100644 index 0000000..7983a77 --- /dev/null +++ b/core/profiles/standard/config/install/block.block.seven_local_actions.yml @@ -0,0 +1,18 @@ +id: local_actions +theme: seven +weight: 0 +status: true +langcode: en +region: actions +plugin: system_local_actions_block +settings: + id: system_local_actions_block + label: Local actions + provider: system + label_display: '0' +dependencies: + module: + - system + theme: + - seven +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.seven_page_primary_tabs.yml b/core/profiles/standard/config/install/block.block.seven_page_primary_tabs.yml new file mode 100644 index 0000000..9743478 --- /dev/null +++ b/core/profiles/standard/config/install/block.block.seven_page_primary_tabs.yml @@ -0,0 +1,20 @@ +id: seven_primary_tabs +theme: seven +weight: 0 +status: true +langcode: en +region: primary_tabs +plugin: system_tabs_block +settings: + id: system_tabs_block + label: Primary tabs + provider: system + label_display: '0' + primary: true + secondary: false +dependencies: + module: + - system + theme: + - seven +visibility: { } diff --git a/core/profiles/standard/config/install/block.block.seven_page_seconadry_tabs.yml b/core/profiles/standard/config/install/block.block.seven_page_seconadry_tabs.yml new file mode 100644 index 0000000..9bf8e3a --- /dev/null +++ b/core/profiles/standard/config/install/block.block.seven_page_seconadry_tabs.yml @@ -0,0 +1,20 @@ +id: seven_secondary_tabs +theme: seven +weight: 0 +status: true +langcode: en +region: secondary_tabs +plugin: system_tabs_block +settings: + id: system_tabs_block + label: Secondary tabs + provider: system + label_display: '0' + primary: false + secondary: true +dependencies: + module: + - system + theme: + - seven +visibility: { } diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php index 85731c7..8ed7ada 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php @@ -10,10 +10,12 @@ use Drupal\Component\Plugin\Discovery\DiscoveryInterface; use Drupal\Component\Plugin\Factory\FactoryInterface; use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Access\AccessResultForbidden; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\Language; use Drupal\Core\Menu\LocalActionManager; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Session\AccountInterface; @@ -93,6 +95,13 @@ class LocalActionManagerTest extends UnitTestCase { protected $discovery; /** + * The mocked renderer. + * + * @var \Drupal\Core\Render\RendererInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $renderer; + + /** * The tested local action manager * * @var \Drupal\Tests\Core\Menu\TestLocalActionManager @@ -109,16 +118,18 @@ protected function setUp() { $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $access_result = new AccessResultForbidden(); $this->accessManager = $this->getMock('Drupal\Core\Access\AccessManagerInterface'); $this->accessManager->expects($this->any()) ->method('checkNamedRoute') - ->will($this->returnValue(FALSE)); + ->willReturn($access_result); $this->account = $this->getMock('Drupal\Core\Session\AccountInterface'); $this->discovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface'); $this->factory = $this->getMock('Drupal\Component\Plugin\Factory\FactoryInterface'); $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $this->renderer = $this->getMock('Drupal\Core\Render\RendererInterface'); - $this->localActionManager = new TestLocalActionManager($this->controllerResolver, $this->request, $route_match, $this->routeProvider, $this->moduleHandler, $this->cacheBackend, $this->accessManager, $this->account, $this->discovery, $this->factory); + $this->localActionManager = new TestLocalActionManager($this->controllerResolver, $this->request, $route_match, $this->routeProvider, $this->moduleHandler, $this->cacheBackend, $this->accessManager, $this->account, $this->discovery, $this->factory, $this->renderer); } /** @@ -341,7 +352,7 @@ public function getActionsForRouteProvider() { class TestLocalActionManager extends LocalActionManager { - public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, AccessManagerInterface $access_manager, AccountInterface $account, DiscoveryInterface $discovery, FactoryInterface $factory) { + public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteMatchInterface $route_match, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, AccessManagerInterface $access_manager, AccountInterface $account, DiscoveryInterface $discovery, FactoryInterface $factory, RendererInterface $renderer) { $this->discovery = $discovery; $this->factory = $factory; $this->routeProvider = $route_provider; @@ -352,6 +363,7 @@ public function __construct(ControllerResolverInterface $controller_resolver, Re $this->requestStack->push($request); $this->routeMatch = $route_match; $this->moduleHandler = $module_handler; + $this->renderer = $renderer; $this->alterInfo('menu_local_actions'); $this->setCacheBackend($cache_backend, 'local_action_plugins', array('local_action')); } diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php index 92b2b54..a788d85 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php @@ -250,7 +250,8 @@ protected function setupLocalTaskManager() { ->will($this->returnValue(new Language(array('id' => 'en')))); $account = $this->getMock('Drupal\Core\Session\AccountInterface'); - $this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $account); + $renderer = $this->getMock('Drupal\Core\Render\RendererInterface'); + $this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $account, $renderer); $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery'); $property->setAccessible(TRUE); diff --git a/core/themes/bartik/bartik.info.yml b/core/themes/bartik/bartik.info.yml index fb8f2fb..444992e 100644 --- a/core/themes/bartik/bartik.info.yml +++ b/core/themes/bartik/bartik.info.yml @@ -24,6 +24,8 @@ regions: highlighted: Highlighted featured_top: 'Featured top' breadcrumb: Breadcrumb + tabs: Tabs + actions: Actions content: Content sidebar_first: 'Sidebar first' sidebar_second: 'Sidebar second' diff --git a/core/themes/bartik/templates/page.html.twig b/core/themes/bartik/templates/page.html.twig index d1ba506..1184863 100644 --- a/core/themes/bartik/templates/page.html.twig +++ b/core/themes/bartik/templates/page.html.twig @@ -38,10 +38,6 @@ * - title: The page title, for use in the actual content. * - title_suffix: Additional output populated by modules, intended to be * displayed after the main title tag that appears in the template. - * - tabs: Tabs linking to any sub-pages beneath the current page (e.g., the - * view and edit tabs when displaying a node). - * - action_links: Actions local to the page, such as "Add menu" on the menu - * administration interface. * - node: Fully loaded node, if there is an automatically-loaded node * associated with the page and the node ID is the second argument in the * page's path (e.g. node/12345 and node/12345/revisions, but not @@ -66,6 +62,8 @@ * - page.footer_fourth: Items for the fourth footer column. * - page.footer_fifth: Items for the fifth footer column. * - page.breadcrumb: Items for the breadcrumb region. + * - page.tabs: Items for the tabs region. + * - page.actions: Items for the actions region. * * @see template_preprocess_page() * @see bartik_preprocess_page() @@ -136,15 +134,9 @@ {% endif %} {{ title_suffix }} - {% if tabs %} - - {% endif %} + {{ page.tabs }} {{ page.help }} - {% if action_links %} -