diff --git a/core/core.services.yml b/core/core.services.yml index 99241e450f4f..7f14b1a53ec0 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -169,6 +169,9 @@ services: plugin.manager.action: class: Drupal\Core\Action\ActionManager arguments: ['@container.namespaces'] + plugin.manager.menu.local_action: + class: Drupal\Core\Menu\LocalActionManager + arguments: ['@container.namespaces', '@controller_resolver', '@request', '@module_handler'] request: class: Symfony\Component\HttpFoundation\Request # @TODO the synthetic setting must be uncommented whenever drupal_session_initialize() diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 94d0e57464b0..ff1eec2412c8 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -2273,20 +2273,19 @@ function menu_secondary_local_tasks() { function menu_get_local_actions() { $links = menu_local_tasks(); $router_item = menu_get_item(); - // @todo Consider storing the results of hook_local_actions() in a static. - foreach (Drupal::moduleHandler()->invokeAll('local_actions') as $route_info) { - if (in_array($router_item['route_name'], $route_info['appears_on'])) { - $route_path = _menu_router_translate_route($route_info['route_name']); - $action_router_item = menu_get_item($route_path); - $links['actions'][$route_path] = array( - '#theme' => 'menu_local_action', - '#link' => array( - 'title' => $route_info['title'], - 'href' => $route_path, - ), - '#access' => $action_router_item['access'], - ); - } + $manager = Drupal::service('plugin.manager.menu.local_action'); + $local_actions = $manager->getActionsForRoute($router_item['route_name']); + foreach ($local_actions as $plugin) { + $route_path = $manager->getPath($plugin); + $action_router_item = menu_get_item($route_path); + $links['actions'][$route_path] = array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => $manager->getTitle($plugin), + 'href' => $route_path, + ), + '#access' => $action_router_item['access'], + ); } return $links['actions']; } diff --git a/core/lib/Drupal/Core/Annotation/Menu/LocalAction.php b/core/lib/Drupal/Core/Annotation/Menu/LocalAction.php new file mode 100644 index 000000000000..3c4cdb333357 --- /dev/null +++ b/core/lib/Drupal/Core/Annotation/Menu/LocalAction.php @@ -0,0 +1,49 @@ +generator = $generator; + // This is available for subclasses that need to translate a dynamic title. + $this->t = $string_translation; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static( + $container->get('string_translation'), + $container->get('url_generator'), + $configuration, + $plugin_id, + $plugin_definition + ); + } + + /** + * {@inheritdoc} + */ + public function getRouteName() { + return $this->pluginDefinition['route_name']; + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + // Subclasses may pull in the request or specific attributes as parameters. + return $this->pluginDefinition['title']; + } + + /** + * {@inheritdoc} + */ + public function getPath() { + // Subclasses may set a request into the generator or use any desired method + // to generate the path. + // @todo - use the new method from https://drupal.org/node/2031353 + $path = $this->generator->generate($this->getRouteName()); + // In order to get the Drupal path the base URL has to be stripped off. + $base_url = $this->generator->getContext()->getBaseUrl(); + if (!empty($base_url) && strpos($path, $base_url) === 0) { + $path = substr($path, strlen($base_url)); + } + return trim($path, '/'); + } + +} diff --git a/core/lib/Drupal/Core/Menu/LocalActionInterface.php b/core/lib/Drupal/Core/Menu/LocalActionInterface.php new file mode 100644 index 000000000000..65287b6557f8 --- /dev/null +++ b/core/lib/Drupal/Core/Menu/LocalActionInterface.php @@ -0,0 +1,49 @@ +controllerResolver = $controller_resolver; + $this->request = $request; + $this->alterInfo($module_handler, 'menu_local_actions'); + } + + /** + * Gets the title for a local action. + * + * @param \Drupal\Core\Menu\LocalActionInterface $local_action + * An object to get the title from. + * + * @return string + * The title (already localized). + * + * @throws \BadMethodCallException + * If the plugin does not implement the getTitle() method. + */ + public function getTitle(LocalActionInterface $local_action) { + $controller = array($local_action, 'getTitle'); + $arguments = $this->controllerResolver->getArguments($this->request, $controller); + return call_user_func_array($controller, $arguments); + } + + /** + * Gets the Drupal path for a local action. + * + * @param \Drupal\Core\Menu\LocalActionInterface $local_action + * An object to get the path from. + * + * @return string + * The path. + * + * @throws \BadMethodCallException + * If the plugin does not implement the getPath() method. + */ + public function getPath(LocalActionInterface $local_action) { + $controller = array($local_action, 'getPath'); + $arguments = $this->controllerResolver->getArguments($this->request, $controller); + return call_user_func_array($controller, $arguments); + } + + /** + * Finds all local actions that appear on a named route. + * + * @param string $route_name + * The route for which to find local actions. + * + * @return \Drupal\Core\Menu\LocalActionInterface[] + * An array of LocalActionInterface objects that appear on the route path. + */ + public function getActionsForRoute($route_name) { + if (!isset($this->instances[$route_name])) { + $this->instances[$route_name] = array(); + // @todo - optimize this lookup by compiling or caching. + foreach ($this->getDefinitions() as $plugin_id => $action_info) { + if (in_array($route_name, $action_info['appears_on'])) { + $plugin = $this->createInstance($plugin_id); + $this->instances[$route_name][$plugin_id] = $plugin; + } + } + } + return $this->instances[$route_name]; + } + +} diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module index 5367d648480f..41e7c626a7ce 100644 --- a/core/modules/config/tests/config_test/config_test.module +++ b/core/modules/config/tests/config_test/config_test.module @@ -57,21 +57,6 @@ function config_test_menu() { return $items; } -/** - * Implements hook_local_actions() - */ -function config_test_local_actions() { - return array( - array( - 'route_name' => 'config_test_entity_add', - 'title' => t('Add test configuration'), - 'appears_on' => array( - 'config_test_list_page', - ), - ), - ); -} - /** * Loads a ConfigTest object. * diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Menu/LocalAction/AddConfigTestEntityLocalAction.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Menu/LocalAction/AddConfigTestEntityLocalAction.php new file mode 100644 index 000000000000..9a55b79d6c71 --- /dev/null +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Menu/LocalAction/AddConfigTestEntityLocalAction.php @@ -0,0 +1,24 @@ + 'filter_format_add', - 'title' => t('Add text format'), - 'appears_on' => array( - 'filter_admin_overview', - ), - ), - ); -} - /** * Loads a text format object from the database. * diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Menu/LocalAction/AddFilterFormatLocalAction.php b/core/modules/filter/lib/Drupal/filter/Plugin/Menu/LocalAction/AddFilterFormatLocalAction.php new file mode 100644 index 000000000000..f42bb88f4208 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Plugin/Menu/LocalAction/AddFilterFormatLocalAction.php @@ -0,0 +1,24 @@ + 'shortcut_set_add', - 'title' => t('Add shortcut set'), - 'appears_on' => array( - 'shortcut_set_admin', - ), - ), - ); -} diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 5fef63597383..3fe115e7d13b 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -820,31 +820,6 @@ function hook_menu() { return $items; } -/** - * Define route-based local actions. - * - * Instead of using MENU_LOCAL_ACTION in hook_menu(), implement - * hook_local_actions(). - * - * @return array - * An associative array containing the following keys: - * - route_name: The machine name of the local action route. - * - title: The title of the local action. - * - appears_on: An array of route names for this action to be display on. - */ -function hook_local_actions() { - return array( - array( - 'route_name' => 'mymodule.route.action', - 'title' => t('Perform local action'), - 'appears_on' => array( - 'mymodule.other_route', - 'mymodule.other_other_route', - ), - ), - ); -} - /** * Alter the data being saved to the {menu_router} table after hook_menu is invoked. * @@ -938,6 +913,18 @@ function hook_menu_local_tasks(&$data, $router_item, $root_path) { function hook_menu_local_tasks_alter(&$data, $router_item, $root_path) { } +/** + * Alter local actions plugins. + * + * @param array $local_actions + * The array of local action plugin definitions, keyed by plugin ID. + * + * @see \Drupal\Core\Menu\LocalActionInterface + * @see \Drupal\Core\Menu\LocalActionManager + */ +function hook_menu_local_actions_alter(&$local_actions) { +} + /** * Alter links in the active trail before it is rendered as the breadcrumb. * diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalAction/MenuTestLocalAction.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalAction/MenuTestLocalAction.php new file mode 100644 index 000000000000..d09a8fbf7164 --- /dev/null +++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalAction/MenuTestLocalAction.php @@ -0,0 +1,24 @@ + $arg)); } -/** - * Implements hook_local_actions(). - */ -function menu_test_local_actions() { - return array( - array( - 'route_name' => 'menu_test_local_action3', - 'title' => t('My routing action'), - 'appears_on' => array( - 'menu_test_local_action1', - ), - ), - ); -} - /** * Implements hook_menu_local_tasks(). * diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalAction/AddViewLocalAction.php b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalAction/AddViewLocalAction.php new file mode 100644 index 000000000000..406107f4d77b --- /dev/null +++ b/core/modules/views_ui/lib/Drupal/views_ui/Plugin/Menu/LocalAction/AddViewLocalAction.php @@ -0,0 +1,24 @@ + 'views_ui.add', - 'title' => t('Add new view'), - 'appears_on' => array( - 'views_ui.list', - ), - ), - ); -} - /** * Implements hook_theme(). */