diff --git a/core/core.services.yml b/core/core.services.yml index 65882af..d61c198 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -173,7 +173,7 @@ services: arguments: ['@container.namespaces'] plugin.manager.menu.local_action: class: Drupal\Core\Menu\LocalActionManager - arguments: ['@container.namespaces', '@controller_resolver', '@request', '@module_handler', '@cache.cache', '@language_manager'] + arguments: ['@controller_resolver', '@request', '@router.route_provider', '@module_handler', '@cache.cache', '@language_manager', '@access_manager'] plugin.manager.menu.local_task: class: Drupal\Core\Menu\LocalTaskManager arguments: ['@container.namespaces', '@controller_resolver', '@request', '@router.route_provider', '@module_handler', '@cache.cache', '@language_manager'] diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 0b59cfe..40f3c74 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -1736,12 +1736,18 @@ function theme_menu_local_action($variables) { $link += array( 'href' => '', 'localized_options' => array(), + 'route_parameters' => array(), ); $link['localized_options']['attributes']['class'][] = 'button'; $link['localized_options']['attributes']['class'][] = 'button-action'; $output = '
  • '; - $output .= l($link['title'], $link['href'], $link['localized_options']); + if (!empty($link['route_name'])) { + $output .= Drupal::l($link['title'], $link['route_name'], $link['route_parameters'], $link['localized_options']); + } + else { + $output .= l($link['title'], $link['href'], $link['localized_options']); + } $output .= "
  • "; return $output; @@ -2307,21 +2313,9 @@ function menu_secondary_local_tasks() { */ function menu_get_local_actions() { $links = menu_local_tasks(); - $router_item = menu_get_item(); + $route_name = Drupal::request()->attributes->get('_route'); $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'], - ); - } + $links['actions'] += $manager->getActionsForRoute($route_name); return $links['actions']; } diff --git a/core/lib/Drupal/Core/Annotation/Menu/LocalAction.php b/core/lib/Drupal/Core/Annotation/Menu/LocalAction.php deleted file mode 100644 index 3c4cdb3..0000000 --- a/core/lib/Drupal/Core/Annotation/Menu/LocalAction.php +++ /dev/null @@ -1,49 +0,0 @@ -generator = $generator; + $this->routeProvider = $route_provider; // This is available for subclasses that need to translate a dynamic title. - $this->t = $string_translation; + $this->stringTranslation = $string_translation; } /** @@ -62,11 +63,11 @@ public function __construct(TranslatorInterface $string_translation, UrlGenerat */ 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 + $plugin_definition, + $container->get('string_translation'), + $container->get('router.route_provider') ); } @@ -82,23 +83,56 @@ public function getRouteName() { */ public function getTitle() { // Subclasses may pull in the request or specific attributes as parameters. - return $this->pluginDefinition['title']; + return $this->t($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)); + public function getRouteParameters(Request $request) { + $parameters = isset($this->pluginDefinition['route_parameters']) ? $this->pluginDefinition['route_parameters'] : array(); + $route = $this->routeProvider->getRouteByName($this->getRouteName()); + $variables = $route->compile()->getVariables(); + + // Noramlly the \Drupal\Core\ParamConverter\ParamConverterManager has + // processed the Request attributes, and in that case the _raw_variables + // attribute holds the original path strings keyed to the corresponding + // slugs in the path patterns. For example, if the route's path pattern is + // /filter/tips/{filter_format} and the path is /filter/tips/plain_text then + // $raw_variables->get('filter_format') == 'plain_text'. + + $raw_variables = $request->attributes->get('_raw_variables'); + + foreach ($variables as $name) { + if (isset($parameters[$name])) { + continue; + } + + if ($raw_variables && $raw_variables->has($name)) { + $parameters[$name] = $raw_variables->get($name); + } + elseif ($request->attributes->has($name)) { + $parameters[$name] = $request->attributes->get($name); + } } - return trim($path, '/'); + // The UrlGenerator will throw an exception if expected parameters are + // missing. This method should be overridden if that is possible. + return $parameters; } + /** + * {@inheritdoc} + */ + public function getOptions(Request $request) { + return (array) $this->pluginDefinition['options']; + } + + /** + * Translates a string to the current language or to a given language. + * + * See the t() documentation for details. + */ + protected function t($string, array $args = array(), array $options = array()) { + return $this->stringTranslation->translate($string, $args, $options); + } } diff --git a/core/lib/Drupal/Core/Menu/LocalActionInterface.php b/core/lib/Drupal/Core/Menu/LocalActionInterface.php index 65287b6..4151160 100644 --- a/core/lib/Drupal/Core/Menu/LocalActionInterface.php +++ b/core/lib/Drupal/Core/Menu/LocalActionInterface.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Menu; +use Symfony\Component\HttpFoundation\Request; + /** * Defines an interface for menu local actions. */ @@ -21,6 +23,37 @@ public function getRouteName(); /** + * Returns the route parameters needed to render a link for the local task. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The HttpRequest object representing the current request. + * + * @return array + * An array of parameter names and values. + * + * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() + */ + public function getRouteParameters(Request $request); + + /** + * Returns the weight of the local task. +@@ -51,14 +55,17 @@ public function getPath(); + public function getWeight(); + + /** + * Returns options for rendering a link to the local task. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The HttpRequest object representing the current request. + * + * @return array + * An associative array of options. + * + * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() + */ + public function getOptions(Request $request); + + /** * Returns the localized title to be shown for this action. * * Subclasses may add optional arguments like NodeInterface $node = NULL that @@ -33,17 +66,6 @@ public function getRouteName(); */ public function getTitle(); - /** - * Return an internal Drupal path to use when linking to the action. - * - * Subclasses may add arguments for request attributes which will then be - * automatically supplied by the controller resolver. - * - * @return string - * The path to use when linking to the action. - * - * @see \Drupal\Core\Menu\LocalActionManager::getPath() - */ - public function getPath(); + } diff --git a/core/lib/Drupal/Core/Menu/LocalActionManager.php b/core/lib/Drupal/Core/Menu/LocalActionManager.php index c99dd83..12ec9a1 100644 --- a/core/lib/Drupal/Core/Menu/LocalActionManager.php +++ b/core/lib/Drupal/Core/Menu/LocalActionManager.php @@ -7,11 +7,17 @@ namespace Drupal\Core\Menu; +use Drupal\Core\Access\AccessManager; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManager; use Drupal\Core\Menu\LocalActionInterface; use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\Component\Plugin\Discovery\ProcessDecorator; +use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator; +use Drupal\Core\Plugin\Discovery\YamlDiscovery; +use Drupal\Core\Plugin\Factory\ContainerFactory; +use Drupal\Core\Routing\RouteProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; @@ -25,6 +31,23 @@ */ class LocalActionManager extends DefaultPluginManager { + protected $defaults = array( + // The ID. + 'id' => '', + // The static title for the local task. + 'title' => '', + // (Required) the route name used to generate a link. + 'route_name' => '', + // Default reoute parameters for generating links. + 'route_parameters' => array(), + // Associative array of link options. + 'options' => array(), + // The route names where this local action appears. + 'appears_on' => array(), + // Default class for local task implementations. + 'class' => 'Drupal\Core\Menu\LocalActionBase', + ); + /** * A controller resolver object. * @@ -40,6 +63,20 @@ class LocalActionManager extends DefaultPluginManager { protected $request; /** + * The route provider to load routes by name. + * + * @var \Drupal\Core\Routing\RouteProviderInterface + */ + protected $routeProvider; + + /** + * The access manager. + * + * @var \Drupal\Core\Access\AccessManager + */ + protected $accessManager; + +/** * The plugin instances. * * @var array @@ -64,13 +101,17 @@ class LocalActionManager extends DefaultPluginManager { * @param \Drupal\Core\Language\LanguageManager $language_manager * The language manager. */ - public function __construct(\Traversable $namespaces, ControllerResolverInterface $controller_resolver, Request $request, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManager $language_manager) { - parent::__construct('Plugin/Menu/LocalAction', $namespaces, array(), 'Drupal\Core\Annotation\Menu\LocalAction'); - + public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManager $language_manager, AccessManager $access_manager) { + // Skip calling the parent constructor, since that assumes annontation-based discovery + $this->discovery = new YamlDiscovery('local_actions', $module_handler->getModuleDirectories()); + $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery); + $this->factory = new ContainerFactory($this); $this->controllerResolver = $controller_resolver; $this->request = $request; + $this->routeProvider = $route_provider; + $this->accessManager = $access_manager; $this->alterInfo($module_handler, 'menu_local_actions'); - $this->setCacheBackend($cache_backend, $language_manager, 'local_action_plugins'); + $this->setCacheBackend($cache_backend, $language_manager, 'local_action_plugins', array('local_action' => TRUE)); } /** @@ -92,44 +133,46 @@ public function getTitle(LocalActionInterface $local_action) { } /** - * 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. + * @param string $route_appears + * The route name for which to find local actions. * - * @return \Drupal\Core\Menu\LocalActionInterface[] - * An array of LocalActionInterface objects that appear on the route path. + * @return array + * An array of link render arrays. */ - public function getActionsForRoute($route_name) { - if (!isset($this->instances[$route_name])) { - $this->instances[$route_name] = array(); + public function getActionsForRoute($route_appears) { + if (!isset($this->instances[$route_appears])) { + $route_names = array(); + $this->instances[$route_appears] = 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'])) { + if (in_array($route_appears, $action_info['appears_on'])) { $plugin = $this->createInstance($plugin_id); - $this->instances[$route_name][$plugin_id] = $plugin; + $route_names[] = $plugin->getRouteName(); + $this->instances[$route_appears][$plugin_id] = $plugin; } } + // Pre-fetch all the action route objects. This reduces the number of SQL + // queries that would otherwise be triggered by the access manager. + $routes = $route_names ? $this->routeProvider->getRoutesByNames($route_names) : array(); + } + $links = array(); + foreach ($this->instances[$route_appears] as $plugin) { + $route_name = $plugin->getRouteName(); + $route_parameters = $plugin->getRouteParameters($this->request); + $links[$route_name] = array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => $this->getTitle($plugin), + 'route_name' => $route_name, + 'route_parameters' => $route_parameters, + 'localized_options' => $plugin->getOptions($this->request), + ), + '#access' => $this->accessManager->checkNamedRoute($route_name, $route_parameters), + ); } - return $this->instances[$route_name]; + return $links; } } diff --git a/core/lib/Drupal/Core/Menu/Plugin/Derivative/StaticLocalActionDeriver.php b/core/lib/Drupal/Core/Menu/Plugin/Derivative/StaticLocalActionDeriver.php deleted file mode 100644 index e9ef0ee..0000000 --- a/core/lib/Drupal/Core/Menu/Plugin/Derivative/StaticLocalActionDeriver.php +++ /dev/null @@ -1,124 +0,0 @@ -get('module_handler'), - $container->get('string_translation') - ); - } - - /** - * Constructs a StaticLocalActionDeriver object. - * - * @param string $base_plugin_id - * The base plugin ID. - * @param\Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * The module handler. - * @param \Drupal\Core\StringTranslation\TranslationInterface translation_manager - * The translation manager. - */ - public function __construct($base_plugin_id, ModuleHandlerInterface $module_handler, TranslationInterface $translation_manager) { - $this->basePluginId = $base_plugin_id; - $this->moduleHandler = $module_handler; - $this->translationManager = $translation_manager; - } - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) { - if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) { - return $this->derivatives[$derivative_id]; - } - $this->getDerivativeDefinitions($base_plugin_definition); - return $this->derivatives[$derivative_id]; - } - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions(array $base_plugin_definition) { - $yaml_discovery = new YamlDiscovery('local_actions', $this->moduleHandler->getModuleDirectories()); - $required_keys = array('title' => 1, 'route_name' => 1, 'appears_on' => 1); - - foreach ($yaml_discovery->findAll() as $module => $local_actions) { - if (!empty($local_actions)) { - foreach ($local_actions as $name => $info) { - if ($missing_keys = array_diff_key($required_keys, array_intersect_key($info, $required_keys))) { - throw new PluginException(String::format('Static local action @name is missing @keys', array('@name' => $name, '@keys' => implode(', ', array_keys($missing_keys))))); - } - - $info += array('provider' => $module); - // Make sure 'appears_on' is an array. - $info['appears_on'] = (array) $info['appears_on']; - $info['title'] = $this->t($info['title']); - $this->derivatives[$name] = $info + $base_plugin_definition; - } - } - } - - return $this->derivatives; - } - - /** - * Translates a string to the current language or to a given language. - * - * See the t() documentation for details. - */ - protected function t($string, array $args = array(), array $options = array()) { - return $this->translationManager->translate($string, $args, $options); - } - -} diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module index 9e61cfd..fcf3c18 100644 --- a/core/modules/views_ui/views_ui.module +++ b/core/modules/views_ui/views_ui.module @@ -25,11 +25,6 @@ function views_ui_menu() { 'route_name' => 'views_ui.list', ); - $items['admin/structure/views/add'] = array( - 'route_name' => 'views_ui.add', - 'type' => MENU_SIBLING_LOCAL_TASK, - ); - // @todo - remove all items of type MENU_VISIBLE_IN_BREADCRUMB // when there is a route-aware breadcrumb builder. $items['admin/structure/views/settings'] = array(