diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 56566e1f8eb2b86d19ef6ea608d2ea737c715cb5..9dbbc2037daabefe43c651ba629cfd95a3b2ddf4 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -93,7 +93,7 @@ function menu_list_system_menus() { } /** - * Collects the local tasks (tabs), action links, and the root path. + * Collects the local tasks (tabs) for the current route. * * @param int $level * The level of tasks you ask for. Primary tasks are 0, secondary are 1. @@ -101,95 +101,45 @@ function menu_list_system_menus() { * @return array * An array containing * - tabs: Local tasks for the requested level. - * - actions: Action links for the requested level. - * - root_path: The router path for the current page. If the current page is - * a default local task, then this corresponds to the parent tab. + * - route_name: The route name for the current page used to collect the local + * tasks. * - * @see hook_menu_local_tasks() * @see hook_menu_local_tasks_alter() + * + * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0. */ function menu_local_tasks($level = 0) { - $data = &drupal_static(__FUNCTION__); - $root_path = &drupal_static(__FUNCTION__ . ':root_path', ''); - $empty = array( - 'tabs' => array(), - 'actions' => array(), - 'root_path' => &$root_path, - ); - - if (!isset($data)) { - // Look for route-based tabs. - $data['tabs'] = array(); - $data['actions'] = array(); - - $route_name = \Drupal::routeMatch()->getRouteName(); - 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); - } - } - - // Allow modules to dynamically add further tasks. - $module_handler = \Drupal::moduleHandler(); - foreach ($module_handler->getImplementations('menu_local_tasks') as $module) { - $function = $module . '_menu_local_tasks'; - $function($data, $route_name); - } - // Allow modules to alter local tasks. - $module_handler->alter('menu_local_tasks', $data, $route_name); - } - - if (isset($data['tabs'][$level])) { - return array( - 'tabs' => $data['tabs'][$level], - 'actions' => $data['actions'], - 'root_path' => $root_path, - ); - } - elseif (!empty($data['actions'])) { - return array('actions' => $data['actions']) + $empty; - } - return $empty; + /** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */ + $manager = \Drupal::service('plugin.manager.menu.local_task'); + return $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), $level); } /** * Returns the rendered local tasks at the top level. + * + * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0. */ function menu_primary_local_tasks() { - $links = menu_local_tasks(0); + /** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */ + $manager = \Drupal::service('plugin.manager.menu.local_task'); + $links = $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), 0); // Do not display single tabs. return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : ''; } /** * Returns the rendered local tasks at the second level. + * + * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0. */ function menu_secondary_local_tasks() { - $links = menu_local_tasks(1); + /** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */ + $manager = \Drupal::service('plugin.manager.menu.local_task'); + $links = $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), 1); // Do not display single tabs. return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : ''; } -/** - * 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() { - $links = menu_local_tasks(); - return $links['root_path']; -} - /** * Returns a renderable element for the primary and secondary tabs. */ diff --git a/core/includes/theme.inc b/core/includes/theme.inc index b3deb3260e3878c358569794fc7b40f437bf747c..329ff58c626d5fbe663e419131064941de1135ce 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1322,14 +1322,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/Menu/LocalActionManager.php b/core/lib/Drupal/Core/Menu/LocalActionManager.php index c387b15903170204db602e096f6c9a321d6a9ef7..eb1e2d037b2e2b74f7e3d603863c37cd3794b8be 100644 --- a/core/lib/Drupal/Core/Menu/LocalActionManager.php +++ b/core/lib/Drupal/Core/Menu/LocalActionManager.php @@ -191,10 +191,11 @@ 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' => $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE), '#weight' => $plugin->getWeight(), ); } + return $links; } diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php index 5b12ac8c4595d6673bdabe8085a80287b3bd52a5..705562825026e209b74d725dc7aa4c64b07d181e 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php @@ -81,6 +81,13 @@ class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerI */ protected $instances = array(); + /** + * The local task render arrays for the current route. + * + * @var array + */ + protected $taskData; + /** * The route provider to load routes by name. * @@ -296,40 +303,75 @@ public function getTasksBuild($current_route_name) { // of SQL queries that would otherwise be triggered by the access manager. $routes = $route_names ? $this->routeProvider->getRoutesByNames($route_names) : array(); + // @todo add cacheability data in https://www.drupal.org/node/2511516 so + // that we are not re-building inaccessible links on every page request. foreach ($tree as $level => $instances) { /** @var $instances \Drupal\Core\Menu\LocalTaskInterface[] */ foreach ($instances as $plugin_id => $child) { $route_name = $child->getRouteName(); $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) { - $active = $this->isRouteActive($current_route_name, $route_name, $route_parameters); - - // The plugin may have been set active in getLocalTasksForRoute() if - // one of its child tabs is the active tab. - $active = $active || $child->getActive(); - // @todo It might make sense to use link render elements instead. - - $link = array( - 'title' => $this->getTitle($child), - 'url' => Url::fromRoute($route_name, $route_parameters), - 'localized_options' => $child->getOptions($this->routeMatch), - ); - $build[$level][$plugin_id] = array( - '#theme' => 'menu_local_task', - '#link' => $link, - '#active' => $active, - '#weight' => $child->getWeight(), - '#access' => $access, - ); - } + $active = $this->isRouteActive($current_route_name, $route_name, $route_parameters); + + // The plugin may have been set active in getLocalTasksForRoute() if + // one of its child tabs is the active tab. + $active = $active || $child->getActive(); + // @todo It might make sense to use link render elements instead. + + $link = [ + 'title' => $this->getTitle($child), + 'url' => Url::fromRoute($route_name, $route_parameters), + 'localized_options' => $child->getOptions($this->routeMatch), + ]; + $build[$level][$plugin_id] = [ + '#theme' => 'menu_local_task', + '#link' => $link, + '#active' => $active, + '#weight' => $child->getWeight(), + '#access' => $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE), + ]; } } + return $build; } + /** + * {@inheritdoc} + */ + public function getLocalTasks($route_name, $level = 0) { + if (!isset($this->taskData[$route_name])) { + // Look for route-based tabs. + $this->taskData[$route_name] = [ + 'tabs' => [], + ]; + + if (!$this->requestStack->getCurrentRequest()->attributes->has('exception')) { + // Safe to build tasks only when no exceptions raised. + $data = []; + $local_tasks = $this->getTasksBuild($route_name); + foreach ($local_tasks as $tab_level => $items) { + $data[$tab_level] = empty($data[$tab_level]) ? $items : array_merge($data[$tab_level], $items); + } + $this->taskData[$route_name]['tabs'] = $data; + // Allow modules to alter local tasks. + $this->moduleHandler->alter('menu_local_tasks', $this->taskData[$route_name], $route_name); + } + } + + if (isset($this->taskData[$route_name]['tabs'][$level])) { + return [ + 'tabs' => $this->taskData[$route_name]['tabs'][$level], + 'route_name' => $route_name, + ]; + } + + return [ + 'tabs' => [], + 'route_name' => $route_name, + ]; + } + /** * Determines whether the route of a certain local task is currently active. * diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php index e75307d48916f6077914d4f7235c85be358cae93..1f8d3d2ffee81ea9b26f332e793f393e955f12ec 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php @@ -53,4 +53,22 @@ public function getLocalTasksForRoute($route_name); */ public function getTasksBuild($current_route_name); + /** + * Collects the local tasks (tabs) for the current route. + * + * @param string $route_name + * The route for which to make renderable local tasks. + * @param int $level + * The level of tasks you ask for. Primary tasks are 0, secondary are 1. + * + * @return array + * An array containing + * - tabs: Local tasks render array for the requested level. + * - route_name: The route name for the current page used to collect the + * local tasks. + * + * @see hook_menu_local_tasks_alter() + */ + public function getLocalTasks($route_name, $level = 0); + } diff --git a/core/lib/Drupal/Core/Menu/Plugin/Block/LocalActionsBlock.php b/core/lib/Drupal/Core/Menu/Plugin/Block/LocalActionsBlock.php new file mode 100644 index 0000000000000000000000000000000000000000..c8348e00c8b6c01d244d42d0aa295fb7c61bafdb --- /dev/null +++ b/core/lib/Drupal/Core/Menu/Plugin/Block/LocalActionsBlock.php @@ -0,0 +1,125 @@ +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() { + $route_name = $this->routeMatch->getRouteName(); + $local_actions = $this->localActionManager->getActionsForRoute($route_name); + if (empty($local_actions)) { + return []; + } + + return $local_actions; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + // The "Primary admin 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/lib/Drupal/Core/Menu/Plugin/Block/LocalTasksBlock.php b/core/lib/Drupal/Core/Menu/Plugin/Block/LocalTasksBlock.php new file mode 100644 index 0000000000000000000000000000000000000000..6edfc6bed72c9a56d6dd519a9d20c1594159649d --- /dev/null +++ b/core/lib/Drupal/Core/Menu/Plugin/Block/LocalTasksBlock.php @@ -0,0 +1,187 @@ +localTaskManager = $local_task_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_task'), + $container->get('current_route_match') + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'label_display' => 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']) { + $links = $this->localTaskManager->getLocalTasks($this->routeMatch->getRouteName(), 0); + // Do not display single tabs. + $tabs += [ + '#primary' => count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : [], + ]; + } + if ($config['secondary']) { + $links = $this->localTaskManager->getLocalTasks($this->routeMatch->getRouteName(), 1); + // Do not display single tabs. + $tabs += [ + '#secondary' => count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : [], + ]; + } + + if (empty($tabs['#primary']) && empty($tabs['#secondary'])) { + return []; + } + + return $tabs; + } + + /** + * {@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'] = [ + '#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/lib/Drupal/Core/Menu/menu.api.php b/core/lib/Drupal/Core/Menu/menu.api.php index e68b7951fc3c7b75b5314b36597c0a3a111cb0ea..971a193e6c9ba9c0bc8fed60e9e0574a66cabc9e 100644 --- a/core/lib/Drupal/Core/Menu/menu.api.php +++ b/core/lib/Drupal/Core/Menu/menu.api.php @@ -393,48 +393,35 @@ function hook_menu_links_discovered_alter(&$links) { } /** - * Alter tabs and actions displayed on the page before they are rendered. + * Alter local tasks displayed on the page before they are rendered. * * This hook is invoked by menu_local_tasks(). The system-determined tabs and - * actions are passed in by reference. Additional tabs or actions may be added. + * actions are passed in by reference. Additional tabs may be added. * - * Each tab or action is an associative array containing: + * The local tasks are under the 'tabs' element and keyed by plugin ID. + * + * Each local task is an associative array containing: * - #theme: The theme function to use to render. * - #link: An associative array containing: * - title: The localized title of the link. - * - href: The system path to link to. + * - url: a Url object. * - localized_options: An array of options to pass to _l(). * - #weight: The link's weight compared to other links. * - #active: Whether the link should be marked as 'active'. * * @param array $data - * An associative array containing: - * - actions: A list of of actions keyed by their href, each one being an - * associative array as described above. - * - tabs: A list of (up to 2) tab levels that contain a list of of tabs keyed - * by their href, each one being an associative array as described above. + * An associative array containing list of (up to 2) tab levels that contain a + * list of of tabs keyed by their href, each one being an associative array + * as described above. * @param string $route_name * The route name of the page. * * @ingroup menu */ -function hook_menu_local_tasks(&$data, $route_name) { - // Add an action linking to node/add to all pages. - $data['actions']['node/add'] = array( - '#theme' => 'menu_local_action', - '#link' => array( - 'title' => t('Add content'), - 'url' => Url::fromRoute('node.add_page'), - 'localized_options' => array( - 'attributes' => array( - 'title' => t('Add content'), - ), - ), - ), - ); +function hook_menu_local_tasks_alter(&$data, $route_name) { // Add a tab linking to node/add to all pages. - $data['tabs'][0]['node/add'] = array( + $data['tabs'][0]['node.add_page'] = array( '#theme' => 'menu_local_task', '#link' => array( 'title' => t('Example tab'), @@ -448,25 +435,6 @@ function hook_menu_local_tasks(&$data, $route_name) { ); } -/** - * Alter tabs and actions displayed on the page before they are rendered. - * - * This hook is invoked by menu_local_tasks(). The system-determined tabs and - * actions are passed in by reference. Existing tabs or actions may be altered. - * - * @param array $data - * An associative array containing tabs and actions. See - * hook_menu_local_tasks() for details. - * @param string $route_name - * The route name of the page. - * - * @see hook_menu_local_tasks() - * - * @ingroup menu - */ -function hook_menu_local_tasks_alter(&$data, $route_name) { -} - /** * Alter local actions plugins. * diff --git a/core/modules/aggregator/src/Tests/AggregatorTestBase.php b/core/modules/aggregator/src/Tests/AggregatorTestBase.php index 312a0b1ef8c89c10b97e791cdb01927bf2bbabbd..08c0c23315277726193aebea73d474f5241db7a2 100644 --- a/core/modules/aggregator/src/Tests/AggregatorTestBase.php +++ b/core/modules/aggregator/src/Tests/AggregatorTestBase.php @@ -29,7 +29,7 @@ abstract class AggregatorTestBase extends WebTestBase { * * @var array */ - public static $modules = array('node', 'aggregator', 'aggregator_test', 'views'); + public static $modules = ['block', 'node', 'aggregator', 'aggregator_test', 'views']; /** * {@inheritdoc} @@ -44,6 +44,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('local_tasks_block'); } /** diff --git a/core/modules/block/src/Tests/BlockHiddenRegionTest.php b/core/modules/block/src/Tests/BlockHiddenRegionTest.php index 347cdaf41bb23c9fc6298e50e67b7a2e01b97c9a..a6af2ce5e686cc43130aea38b29358d99ae01c2f 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('local_tasks_block'); } /** diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php index cff4a24d5200fb78b9182d3221042ed476c2d575..bd360f680afc7ded89e8a6f6ea1e2eab7c69fd04 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('local_tasks_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 bc55ab2c2653da7f07c6bf86773d333921821fa1..a2bfddcf0239bfcf6891343091ef670fa8653f9f 100644 --- a/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php +++ b/core/modules/block/src/Tests/NonDefaultBlockAdminTest.php @@ -23,6 +23,15 @@ class NonDefaultBlockAdminTest extends WebTestBase { */ public static $modules = array('block'); + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('local_tasks_block'); + } + /** * Test non-default theme admin. */ diff --git a/core/modules/block_content/src/Tests/BlockContentTestBase.php b/core/modules/block_content/src/Tests/BlockContentTestBase.php index 82139c0a5c8f352e1e4c1bc45acbcdf61ab24dc6..3e0aa749bbe1e0a0552b45e1112ec52ef0ec3242 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('local_actions_block'); } /** diff --git a/core/modules/comment/src/Tests/CommentTestBase.php b/core/modules/comment/src/Tests/CommentTestBase.php index a52cbb39765b2a70eeaab931ef5d0bb80e92d786..12ea894ecdcb871aeb3dfecd1af21e9e52938b66 100644 --- a/core/modules/comment/src/Tests/CommentTestBase.php +++ b/core/modules/comment/src/Tests/CommentTestBase.php @@ -27,7 +27,7 @@ abstract class CommentTestBase extends WebTestBase { * * @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('local_tasks_block'); } /** diff --git a/core/modules/config/src/Tests/ConfigEntityListTest.php b/core/modules/config/src/Tests/ConfigEntityListTest.php index ee58be5d2134b48cbb7e6964bff47d777cd27a7f..9b74a5f3f60e6f0cc50ea6ecc31f76355015a181 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('local_actions_block'); } /** diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationListUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationListUiTest.php index 4ceec20f2a5effbcd704dd201a72656d87460ed8..fceb41d110136942d267b8cb6342eedec0910426 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('local_tasks_block'); } /** diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationOverviewTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationOverviewTest.php index 0916dcd79594fe683f4da621b46f7ac098569b14..221b7d6680ea910a6106c7eed67a3a7c6dd1084e 100644 --- a/core/modules/config_translation/src/Tests/ConfigTranslationOverviewTest.php +++ b/core/modules/config_translation/src/Tests/ConfigTranslationOverviewTest.php @@ -24,6 +24,7 @@ class ConfigTranslationOverviewTest extends WebTestBase { * @var array */ public static $modules = [ + 'block', 'config_test', 'config_translation', 'config_translation_test', @@ -67,6 +68,7 @@ protected function setUp() { ConfigurableLanguage::createFromLangcode($langcode)->save(); } $this->localeStorage = $this->container->get('locale.storage'); + $this->drupalPlaceBlock('local_tasks_block'); } /** diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php index f220d1a31484ec01c5e295d6774f24fa344996d1..98a18168c0c64ce415d1a08d30c047e0f8bc65e9 100644 --- a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php +++ b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php @@ -31,6 +31,7 @@ class ConfigTranslationUiTest extends WebTestBase { * @var array */ public static $modules = [ + 'block', 'config_translation', 'config_translation_test', 'contact', @@ -117,6 +118,7 @@ protected function setUp() { ConfigurableLanguage::createFromLangcode($langcode)->save(); } $this->localeStorage = $this->container->get('locale.storage'); + $this->drupalPlaceBlock('local_tasks_block'); } /** diff --git a/core/modules/contact/src/Tests/ContactSitewideTest.php b/core/modules/contact/src/Tests/ContactSitewideTest.php index 1ab942b781ba61ceb0706e5d663a62f439975ea5..83f296fad560686d361acd0f9cf6091524c2639b 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('local_actions_block'); } /** diff --git a/core/modules/contact/src/Tests/ContactStorageTest.php b/core/modules/contact/src/Tests/ContactStorageTest.php index 5d62e10ad95fd518b39ff167e49575e24754215b..f18d10ec8cef868283ae51f4081e7fcc10c3785f 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 7b7fe1257781829a10f794cbdf3236903ce989c2..1a6007ca3760bec1034de67a24c8aed23b4590fa 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('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 6b680430e5089f9988727c561d7c44698666bef9..181d3c35b3220406a4c718935ee9ba17ab3e39bb 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('local_tasks_block'); } /** diff --git a/core/modules/field_ui/src/Tests/ManageFieldsTest.php b/core/modules/field_ui/src/Tests/ManageFieldsTest.php index 3b2ac3ebab772be621dd5591ae90b759ff863669..6872bcffc85bd882365ddf368e4b0c65fa88a4b3 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('local_actions_block'); + $this->drupalPlaceBlock('local_tasks_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 54a72377b21362da2080a44a0cddba4bbe834785..0c61a9ed2314a1f26bcd5fcfde747e618d77d1d7 100644 --- a/core/modules/filter/src/Tests/FilterAdminTest.php +++ b/core/modules/filter/src/Tests/FilterAdminTest.php @@ -25,7 +25,7 @@ class FilterAdminTest extends WebTestBase { /** * {@inheritdoc} */ - public static $modules = ['filter', 'node', 'filter_test_plugin', 'dblog']; + public static $modules = ['block', 'filter', 'node', 'filter_test_plugin', 'dblog']; /** * An user with administration permissions. @@ -109,6 +109,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('local_actions_block'); } /** diff --git a/core/modules/filter/src/Tests/FilterFormatAccessTest.php b/core/modules/filter/src/Tests/FilterFormatAccessTest.php index 029c60b06bb3f15b3e3b5f3dc0c269efafd6defe..b9eeaa7ada62a832b565d952dc57d2e326ddbcb5 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('local_tasks_block'); } /** diff --git a/core/modules/forum/src/Tests/ForumTest.php b/core/modules/forum/src/Tests/ForumTest.php index 3861f66081c81a92db1e12bbf2bf83238f1763ca..767e3e3ddff62dc0a71bfe91577960ad74f58609 100644 --- a/core/modules/forum/src/Tests/ForumTest.php +++ b/core/modules/forum/src/Tests/ForumTest.php @@ -115,6 +115,7 @@ protected function setUp() { 'access comments', )); $this->drupalPlaceBlock('help_block', array('region' => 'help')); + $this->drupalPlaceBlock('local_actions_block'); } /** diff --git a/core/modules/language/src/Tests/LanguagePathMonolingualTest.php b/core/modules/language/src/Tests/LanguagePathMonolingualTest.php index c20a57e902acae423cc46b4e344b752e04475a44..aaccec0f42773c64a9bf7830ccfb16c034d5cc1b 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('local_actions_block'); } /** diff --git a/core/modules/language/src/Tests/LanguageTourTest.php b/core/modules/language/src/Tests/LanguageTourTest.php index 98418b6dd79b219d780cff3e2a40399e1d030515..6d8ec6a0ecdbef7fd34113307c99b3ab6b2f128e 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('local_actions_block'); } /** diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php index fe3a4bc1ce8a17ca7eeb2ae14a7548ba73217334..bcb9de96056179ed4cd8886226ff0ae4ba5754c5 100644 --- a/core/modules/node/src/Tests/NodeTranslationUITest.php +++ b/core/modules/node/src/Tests/NodeTranslationUITest.php @@ -27,6 +27,7 @@ class NodeTranslationUITest extends ContentTranslationUITestBase { protected $defaultCacheContexts = [ 'languages:language_interface', 'theme', + 'route.name', 'route.menu_active_trails:account', 'route.menu_active_trails:footer', 'route.menu_active_trails:main', diff --git a/core/modules/node/src/Tests/PageEditTest.php b/core/modules/node/src/Tests/PageEditTest.php index 9082dcaae7d46e7c5076eebbbd034ed54633ecbb..31d6ea9925ce505f6214fe208358afcf1946107f 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('local_tasks_block'); } /** diff --git a/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php b/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php index c471cea9684327360d9cf9040f4b7211c21bb784..f19ac701454d774c83e73fe1f79701d8a63c4201 100644 --- a/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php +++ b/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php @@ -103,6 +103,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_local_tasks', 'node_view', 'node:' . $node_1->id(), 'user:' . $author_1->id(), @@ -137,6 +139,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_local_tasks', '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 a99ea452909ef976a74fd46bb4ad2d024a8cb29d..2c17ed6db79c89b8a564195acff8f65d64965787 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('local_tasks_block'); } /** diff --git a/core/modules/search/src/Tests/SearchPageTextTest.php b/core/modules/search/src/Tests/SearchPageTextTest.php index 408848c40ccbf86f854c7836916e7f3e86761118..8a95fe6d2f27da0dd5d566b865ce8485e6e16219 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('local_tasks_block'); } /** diff --git a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php index 26a31718b270b0b7336f8ec0ea0ce6c023e680ff..bc3c5f921c63669fe41f8937475ca2ddd0a591db 100644 --- a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php +++ b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php @@ -16,6 +16,22 @@ */ class ShortcutSetsTest extends ShortcutTestBase { + /** + * Modules to enable. + * + * @var string[] + */ + public static $modules = ['block']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('local_actions_block'); + } + /** * Tests creating a shortcut set. */ diff --git a/core/modules/simpletest/src/Tests/BrowserTest.php b/core/modules/simpletest/src/Tests/BrowserTest.php index 02422e7a2dd340d0505af28cddf313eb7075d588..3e58c0d2a92e3d19c1711f0942e8c5c8b91233d4 100644 --- a/core/modules/simpletest/src/Tests/BrowserTest.php +++ b/core/modules/simpletest/src/Tests/BrowserTest.php @@ -23,6 +23,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('local_tasks_block'); + } + /** * Test \Drupal\simpletest\WebTestBase::getAbsoluteUrl(). */ diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 12b0e6c17e598330e03a59a30debac8ee842292a..b4a8873ca8fce87351d1e15e3411459c6b948c72 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -342,6 +342,17 @@ block.settings.system_menu_block:*: type: integer label: 'Maximum number of levels' +block.settings.local_tasks_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/Tests/Menu/LocalActionTest.php b/core/modules/system/src/Tests/Menu/LocalActionTest.php index cd6dc81a48a9ca290966f09d8bd431144b23f23a..22f188dba6b4115c3c518121daeda783474d02f7 100644 --- a/core/modules/system/src/Tests/Menu/LocalActionTest.php +++ b/core/modules/system/src/Tests/Menu/LocalActionTest.php @@ -18,10 +18,21 @@ */ 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('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 1a0ce81a86a9d1175af2635ced173317091dbc34..4030c48cfc732295ceb008ef6b2d1e085f841092 100644 --- a/core/modules/system/src/Tests/Menu/LocalTasksTest.php +++ b/core/modules/system/src/Tests/Menu/LocalTasksTest.php @@ -18,7 +18,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('local_tasks_block', ['id' => 'tabs_block']); + } /** * Asserts local tasks in the page output. @@ -65,6 +86,20 @@ protected function assertLocalTaskAppers($title) { return $this->assertPattern('@]*>' . preg_quote($title, '@') . '@'); } + /** + * 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. */ @@ -172,4 +207,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('local_tasks_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('local_tasks_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 7fd809d12a4435b379a7a43b850e09a9e37bfb26..21052a48a0a13e8574f1083816da3e3c4aa92ee6 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('local_tasks_block'); } /** diff --git a/core/modules/system/src/Tests/Menu/MenuTranslateTest.php b/core/modules/system/src/Tests/Menu/MenuTranslateTest.php index e316c95002f2745d9a8bf3228bc01eaccf337716..3641ab1fb0ddb7941d0ad655d698c7da0a899572 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('local_tasks_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 4fdc2790fa228387e66bea04b200283af5334a8a..39dac525d2f8f001de588f269404c85da1328549 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('local_actions_block'); } /** diff --git a/core/modules/system/src/Tests/System/ThemeTest.php b/core/modules/system/src/Tests/System/ThemeTest.php index 7c852758839f17b5b67441e5c8712ecd3dcc59c0..f4e87e1f03b6e0cc9f9fd308cf8e67d2b385fb53 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('local_tasks_block'); } /** diff --git a/core/modules/system/src/Tests/Update/LocalActionsAndTasksConvertedIntoBlocksUpdateTest.php b/core/modules/system/src/Tests/Update/LocalActionsAndTasksConvertedIntoBlocksUpdateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0665f40ac9868b98a0eb97140e0defacdec5ddcb --- /dev/null +++ b/core/modules/system/src/Tests/Update/LocalActionsAndTasksConvertedIntoBlocksUpdateTest.php @@ -0,0 +1,91 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.local-actions-tasks-into-blocks-507488.php', + ]; + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */ + $theme_handler = \Drupal::service('theme_handler'); + $theme_handler->refreshInfo(); + } + + /** + * Tests that local actions/tasks are being converted into blocks. + */ + public function testUpdateHookN() { + $this->runUpdates(); + + /** @var \Drupal\block\BlockInterface $block_storage */ + $block_storage = \Drupal::entityManager()->getStorage('block'); + /* @var \Drupal\block\BlockInterface[] $help_blocks */ + $help_blocks = $block_storage->loadByProperties(['theme' => 'bartik', 'region' => 'help']); + + $this->assertRaw('Because your site has custom theme(s) installed, we had to set local actions and tasks blocks into the content region. Please manually review the block configurations and remove the removed variables from your templates.'); + + // Disable maintenance mode. + // @todo Can be removed once maintenance mode is automatically turned off + // after updates in https://www.drupal.org/node/2435135. + \Drupal::state()->set('system.maintenance_mode', FALSE); + + // We finished updating so we can login the user now. + $this->drupalLogin($this->rootUser); + + $page = Node::create([ + 'type' => 'page', + 'title' => 'Page node', + ]); + $page->save(); + + // Ensures that blocks inside help region has been moved to content region. + foreach ($help_blocks as $block) { + $new_block = $block_storage->load($block->id()); + $this->assertEqual($new_block->getRegion(), 'content'); + } + + // Local tasks are visible on the node page. + $this->drupalGet('node/' . $page->id()); + $this->assertText(t('Edit')); + + // Local actions are visible on the content listing page. + $this->drupalGet('admin/content'); + $action_link = $this->cssSelect('.action-links'); + $this->assertTrue($action_link); + + $this->drupalGet('admin/structure/block/list/seven'); + + /** @var \Drupal\Core\Config\StorageInterface $config_storage */ + $config_storage = \Drupal::service('config.storage'); + $this->assertTrue($config_storage->exists('block.block.test_theme_local_tasks'), 'Local task block has been created for the custom theme.'); + $this->assertTrue($config_storage->exists('block.block.test_theme_local_actions'), 'Local action block has been created for the custom theme.'); + } + +} diff --git a/core/modules/system/system.install b/core/modules/system/system.install index ce06b7a5be513f4d3268c2a4b6081f0b09494b69..e6992f962a07030dd7494b33e9f0036553d7af6a 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1154,6 +1154,11 @@ function system_schema() { } /** + * @addtogroup updates-8.0.0-beta + * @{ + */ + +/* * Change two fields on the default menu link storage to be serialized data. */ function system_update_8001(&$sandbox = NULL) { @@ -1268,3 +1273,179 @@ function system_update_8004() { $manager->updateEntityType($manager->getEntityType($entity_type_id)); } } + +/** + * Place local actions and tasks blocks in every theme. + */ +function system_update_8005() { + // When block module is not installed, there is nothing that could be done + // except showing a warning. + if (!\Drupal::moduleHandler()->moduleExists('block')) { + return t('Block module is not enabled so local actions and tasks which have been converted to blocks, are not visible anymore.'); + } + $config_factory = \Drupal::configFactory(); + /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */ + $theme_handler = \Drupal::service('theme_handler'); + $custom_themes_installed = FALSE; + $message = NULL; + $langcode = \Drupal::service('language_manager')->getCurrentLanguage()->getId(); + + $local_actions_default_settings = [ + 'plugin' => 'local_actions_block', + 'region' => 'content', + 'settings.label' => 'Primary admin actions', + 'settings.label_display' => 0, + 'settings.cache.max_age' => 0, + 'visibility' => [], + 'weight' => 0, + 'langcode' => $langcode, + ]; + $tabs_default_settings = [ + 'plugin' => 'local_tasks_block', + 'region' => 'content', + 'settings.label' => 'Tabs', + 'settings.label_display' => 0, + 'settings.cache.max_age' => 0, + 'visibility' => [], + 'weight' => 0, + 'langcode' => $langcode, + ]; + foreach ($theme_handler->listInfo() as $theme) { + $theme_name = $theme->getName(); + switch ($theme_name) { + case 'bartik': + $name = 'block.block.bartik_local_actions'; + $values = [ + 'id' => 'bartik_local_actions', + 'weight' => -1, + ] + $local_actions_default_settings; + _system_update_create_block($name, $theme_name, $values); + + $name = 'block.block.bartik_local_tasks'; + $values = [ + 'id' => 'bartik_local_tasks', + 'weight' => -7, + ] + $tabs_default_settings; + _system_update_create_block($name, $theme_name, $values); + + // Help region has been removed so all the blocks inside has to be moved + // to content region. + $weight = -6; + $blocks = []; + foreach ($config_factory->listAll('block.block.') as $block_config) { + $block = $config_factory->getEditable($block_config); + if ($block->get('theme') == 'bartik' && $block->get('region') == 'help') { + $blocks[] = $block; + } + } + // Sort blocks by block weight. + uasort($blocks, function ($a, $b) { + return $a->get('weight') - $b->get('weight'); + }); + // Move blocks to content region and set them in right order by their + // weight. + foreach ($blocks as $block) { + $block->set('region', 'content'); + $block->set('weight', $weight++); + $block->save(); + } + break; + + case 'seven': + $name = 'block.block.seven_local_actions'; + $values = [ + 'id' => 'seven_local_actions', + 'weight' => -10, + ] + $local_actions_default_settings; + _system_update_create_block($name, $theme_name, $values); + + $name = 'block.block.seven_primary_local_tasks'; + $values = [ + 'region' => 'header', + 'id' => 'seven_primary_local_tasks', + 'settings.label' => 'Primary tabs', + 'settings.primary' => TRUE, + 'settings.secondary' => FALSE, + ] + $tabs_default_settings; + _system_update_create_block($name, $theme_name, $values); + + $values = [ + 'region' => 'pre_content', + 'id' => 'seven_secondary_local_tasks', + 'settings.label' => 'Secondary tabs', + 'settings.primary' => FALSE, + 'settings.secondary' => TRUE, + ] + $tabs_default_settings; + _system_update_create_block($name, $theme_name, $values); + break; + + case 'stark': + $name = 'block.block.stark_local_actions'; + $values = [ + 'id' => 'stark_local_actions', + ] + $local_actions_default_settings; + _system_update_create_block($name, $theme_name, $values); + + $name = 'block.block.stark_local_tasks'; + $values = [ + 'id' => 'stark_local_tasks', + ] + $tabs_default_settings; + _system_update_create_block($name, $theme_name, $values); + break; + + case 'classy': + // Don't place any blocks or trigger custom themes installed warning. + break; + + default: + $custom_themes_installed = TRUE; + $name = sprintf('block.block.%s_local_actions', $theme_name); + $values = [ + 'id' => sprintf('%s_local_actions', $theme_name), + 'weight' => -10, + ] + $local_actions_default_settings; + _system_update_create_block($name, $theme_name, $values); + + $name = sprintf('block.block.%s_local_tasks', $theme_name); + $values = [ + 'id' => sprintf('%s_local_tasks', $theme_name), + 'weight' => -20, + ] + $tabs_default_settings; + _system_update_create_block($name, $theme_name, $values); + break; + } + } + + if ($custom_themes_installed) { + $message = t('Because your site has custom theme(s) installed, we had to set local actions and tasks blocks into the content region. Please manually review the block configurations and remove the removed variables from your templates.'); + } + + return $message; +} + +/** + * Helper function to create block configuration objects for the update. + * + * @param string $name + * The name of the config object. + * @param string $theme_name + * The name of the theme the block is associated with. + * @param array $values + * The block config values. + */ +function _system_update_create_block($name, $theme_name, array $values) { + if (!\Drupal::service('config.storage')->exists($name)) { + $block = \Drupal::configFactory()->getEditable($name); + $values['uuid'] = \Drupal::service('uuid')->generate(); + $values['theme'] = $theme_name; + $values['dependencies.theme'] = [$theme_name]; + foreach ($values as $key => $value) { + $block->set($key, $value); + } + $block->save(); + } +} + +/** + * @} End of "addtogroup updates-8.0.0-beta". + */ diff --git a/core/modules/system/templates/block--local-actions-block.html.twig b/core/modules/system/templates/block--local-actions-block.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..65d57be1fd8301e02026c13797b3db0e5a05b2cf --- /dev/null +++ b/core/modules/system/templates/block--local-actions-block.html.twig @@ -0,0 +1,12 @@ +{% extends "@block/block.html.twig" %} +{# +/** + * @file + * Theme override for local actions (primary admin actions.) + */ +#} +{% block content %} + {% if content %} + + {% endif %} +{% endblock %} diff --git a/core/modules/system/templates/page.html.twig b/core/modules/system/templates/page.html.twig index c5a3711ed70d57a02acd9d470fdd7ec441fc1e57..b80310e5553e8056895ce52093eca6dec30c5810 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 @@ -110,13 +106,6 @@