diff --git a/core/includes/menu.inc b/core/includes/menu.inc index b2e115d..826ca09 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,64 +101,26 @@ 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 $tab_level => $items) { - $data['tabs'][$tab_level] = empty($data['tabs'][$tab_level]) ? $items : array_merge($data['tabs'][$tab_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($level); } /** * Returns the rendered local tasks at the top level. */ 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(0); // Do not display single tabs. return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : ''; } @@ -167,20 +129,14 @@ function menu_primary_local_tasks() { * Returns the rendered local tasks at the second level. */ 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(1); // Do not display single tabs. return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : ''; } /** - * 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. */ function menu_local_tabs() { diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php index 3f2ab8b..bc422fd 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php @@ -82,6 +82,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. * * @var \Drupal\Core\Routing\RouteProviderInterface @@ -296,6 +303,8 @@ 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) { @@ -328,6 +337,43 @@ public function getTasksBuild($current_route_name) { } /** + * {@inheritdoc} + */ + public function getLocalTasks($level = 0) { + + if (!isset($this->taskData)) { + $route_name = $this->routeMatch->getRouteName(); + // Look for route-based tabs. + $this->taskData = array( + 'tabs' => array(), + 'route_name' => $route_name, + ); + + if (!empty($route_name) && !$this->requestStack->getCurrentRequest()->attributes->has('exception')) { + $data = array(); + $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['tabs'] = $data; + } + // Allow modules to alter local tasks. + $this->moduleHandler->alter('menu_local_tasks', $this->taskData, $route_name); + } + + if (isset($this->taskData['tabs'][$level])) { + return array( + 'tabs' => $this->taskData['tabs'][$level], + 'route_name' => $this->taskData['route_name'], + ); + } + return array( + 'tabs' => array(), + 'route_name' => $this->taskData['route_name'], + ); + } + + /** * Determines whether the route of a certain local task is currently active. * * @param string $current_route_name diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php index e75307d..e7ec7b1 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php @@ -53,4 +53,20 @@ public function getLocalTasksForRoute($route_name); */ public function getTasksBuild($current_route_name); + /** + * 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. + * + * @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($level = 0); + } diff --git a/core/lib/Drupal/Core/Menu/menu.api.php b/core/lib/Drupal/Core/Menu/menu.api.php index 8550fc5..87c2c71 100644 --- a/core/lib/Drupal/Core/Menu/menu.api.php +++ b/core/lib/Drupal/Core/Menu/menu.api.php @@ -377,48 +377,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'), @@ -433,25 +420,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. * * @param array $local_actions diff --git a/core/modules/system/src/Plugin/Block/SystemLocalActionsBlock.php b/core/modules/system/src/Plugin/Block/SystemLocalActionsBlock.php index 23a65c0..30e690b 100644 --- a/core/modules/system/src/Plugin/Block/SystemLocalActionsBlock.php +++ b/core/modules/system/src/Plugin/Block/SystemLocalActionsBlock.php @@ -83,9 +83,8 @@ public function defaultConfiguration() { */ public function build() { $build = []; - $links = menu_local_tasks(); $route_name = $this->routeMatch->getRouteName(); - $local_actions = $this->localActionManager->getActionsForRoute($route_name) + $links['actions']; + $local_actions = $this->localActionManager->getActionsForRoute($route_name); if (empty($local_actions)) { return []; } diff --git a/core/modules/system/src/Plugin/Block/SystemLocalTasksBlock.php b/core/modules/system/src/Plugin/Block/SystemLocalTasksBlock.php index bbbbb9d..897900a 100644 --- a/core/modules/system/src/Plugin/Block/SystemLocalTasksBlock.php +++ b/core/modules/system/src/Plugin/Block/SystemLocalTasksBlock.php @@ -9,7 +9,9 @@ use Drupal\Core\Block\BlockBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Menu\LocalTaskManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Render\Element; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -23,13 +25,38 @@ class SystemLocalTasksBlock extends BlockBase implements ContainerFactoryPluginInterface { /** + * The local task manager. + * + * @var \Drupal\Core\Menu\LocalTaskManagerInterface + */ + protected $localTaskManager; + + /** + * Creates a SystemLocalTasksBlock instance. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Menu\LocalTaskManagerInterface $local_task_manager + * The local task manager. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, LocalTaskManagerInterface $local_task_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->localTaskManager = $local_task_manager; + } + + /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, - $plugin_definition + $plugin_definition, + $container->get('plugin.manager.menu.local_task') ); } @@ -56,13 +83,17 @@ public function build() { // Add only selected levels for the printed output. if ($config['primary']) { + $links = $this->localTaskManager->getLocalTasks(0); + // Do not display single tabs. $tabs += [ - '#primary' => menu_primary_local_tasks(), + '#primary' => count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : [], ]; } if ($config['secondary']) { + $links = $this->localTaskManager->getLocalTasks(1); + // Do not display single tabs. $tabs += [ - '#secondary' => menu_secondary_local_tasks(), + '#secondary' => count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : [], ]; } diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module index 429b357..60c926f 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.module +++ b/core/modules/system/tests/modules/menu_test/menu_test.module @@ -27,9 +27,9 @@ function menu_test_menu_links_discovered_alter(&$links) { } /** - * Implements hook_menu_local_tasks(). + * Implements hook_menu_local_tasks_alter(). */ -function menu_test_menu_local_tasks(&$data, $route_name) { +function menu_test_menu_local_tasks_alter(&$data, $route_name) { if (in_array($route_name, array('menu_test.tasks_default'))) { $data['tabs'][0]['foo'] = array( '#theme' => 'menu_local_task', @@ -51,32 +51,6 @@ function menu_test_menu_local_tasks(&$data, $route_name) { } /** - * Implements hook_menu_local_tasks_alter(). - * - * If the menu_test.settings configuration 'tasks.alter' has been set, adds - * several local tasks to menu-test/tasks. - */ -function menu_test_menu_local_tasks_alter(&$data, $route_name) { - if (!\Drupal::config('menu_test.settings')->get('tasks.alter')) { - return; - } - if (in_array($route_name, array('menu_test.tasks_default', 'menu_test.tasks_empty', 'menu_test.tasks_tasks'))) { - // Rename the default local task from 'View' to 'Show'. - // $data['tabs'] is expected to be keyed by link hrefs. - // The default local task always links to its parent path, which means that - // if the tab root path appears as key in $data['tabs'], then that key is - // the default local task. - $key = $route_name . '_tab'; - if (isset($data['tabs'][0][$key])) { - $data['tabs'][0][$key]['#link']['title'] = 'Show it'; - } - // Rename the 'foo' task to "Advanced settings" and put it last. - $data['tabs'][0]['foo']['#link']['title'] = 'Advanced settings'; - $data['tabs'][0]['foo']['#weight'] = 110; - } -} - -/** * Page callback: Tests the theme negotiation functionality. * * @param bool $inherited