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(