diff --git a/core/core.services.yml b/core/core.services.yml
index 4fab422..295e57d 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -177,7 +177,7 @@ services:
arguments: ['@container.namespaces', '@controller_resolver', '@request', '@module_handler', '@cache.cache', '@language_manager']
plugin.manager.menu.local_task:
class: Drupal\Core\Menu\LocalTaskManager
- arguments: ['@controller_resolver', '@request', '@router.route_provider', '@module_handler', '@cache.cache', '@language_manager']
+ arguments: ['@controller_resolver', '@request', '@router.route_provider', '@module_handler', '@cache.cache', '@language_manager', '@access_manager']
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 f289cbb..65eea7c 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -1717,8 +1717,15 @@ function theme_menu_local_task($variables) {
$link['localized_options']['html'] = TRUE;
$link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active));
}
+ if (!empty($link['href'])) {
+ // @todo - remove this once all pages are converted to routes.
+ $a_tag = l($link_text, $link['href'], $link['localized_options']);
+ }
+ else {
+ $a_tag = Drupal::l($link_text, $link['route_name'], $link['route_parameters'], $link['localized_options']);
+ }
- return '
' . l($link_text, $link['href'], $link['localized_options']) . '';
+ return '' . $a_tag . '';
}
/**
@@ -1958,6 +1965,20 @@ function menu_local_tasks($level = 0) {
$actions = $empty['actions'];
$tabs = array();
+ // Look for route-based tabs.
+ $data['tabs'] = array();
+ $data['actions'] = array();
+
+ $route_name = Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME);
+ if (!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);
+ }
+ }
+
+ // @todo Remove the code below once the old menu router system got removed.
$router_item = menu_get_item();
// If this router item points to its parent, start from the parents to
@@ -2143,16 +2164,7 @@ function menu_local_tasks($level = 0) {
ksort($tabs);
// Remove the depth, we are interested only in their relative placement.
$tabs = array_values($tabs);
- $data['tabs'] = $tabs;
- // Look for route-based tabs.
- $route_name = \Drupal::request()->attributes->get('_route');
- if (!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);
- }
- }
+ $data['tabs'] += $tabs;
// Allow modules to dynamically add further tasks.
$module_handler = \Drupal::moduleHandler();
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskDefault.php b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php
index 5021e90..8983604 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskDefault.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php
@@ -9,9 +9,10 @@
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\HttpFoundation\Request;
/**
* Default object used for LocalTaskPlugins.
@@ -26,11 +27,11 @@ class LocalTaskDefault extends PluginBase implements LocalTaskInterface, Contain
protected $stringTranslation;
/**
- * URL generator object.
+ * The route provider to load routes by name.
*
- * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface
+ * @var \Drupal\Core\Routing\RouteProviderInterface
*/
- protected $generator;
+ protected $routeProvider;
/**
* TRUE if this plugin is forced active for options attributes.
@@ -50,12 +51,12 @@ class LocalTaskDefault extends PluginBase implements LocalTaskInterface, Contain
* The plugin implementation definition.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation object.
- * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $generator
- * The url generator object.
+ * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+ * The route provider.
*/
- public function __construct(array $configuration, $plugin_id, array $plugin_definition, TranslationInterface $string_translation, UrlGeneratorInterface $generator) {
+ public function __construct(array $configuration, $plugin_id, array $plugin_definition, TranslationInterface $string_translation, RouteProviderInterface $route_provider) {
$this->stringTranslation = $string_translation;
- $this->generator = $generator;
+ $this->routeProvider = $route_provider;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
@@ -68,7 +69,7 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_id,
$plugin_definition,
$container->get('string_translation'),
- $container->get('url_generator')
+ $container->get('router.route_provider')
);
}
@@ -91,26 +92,43 @@ public function getRouteName() {
/**
* {@inheritdoc}
*/
- public function getTitle() {
- // Subclasses may pull in the request or specific attributes as parameters.
- return $this->t($this->pluginDefinition['title']);
+ 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();
+
+ // Normally 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);
+ }
+ }
+ // The UrlGenerator will throw an exception if expected parameters are
+ // missing. This method should be overridden if that is possible.
+ return $parameters;
}
/**
* {@inheritdoc}
- *
- * @todo update based on https://drupal.org/node/2045267
*/
- public function getPath() {
- // Subclasses may set a request into the generator or use any desired method
- // to generate the path.
- $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, '/');
+ public function getTitle() {
+ // Subclasses may pull in the request or specific attributes as parameters.
+ return $this->t($this->pluginDefinition['title']);
}
/**
@@ -136,7 +154,7 @@ public function getWeight() {
/**
* {@inheritdoc}
*/
- public function getOptions() {
+ public function getOptions(Request $request) {
$options = $this->pluginDefinition['options'];
if ($this->active) {
if (empty($options['attributes']['class']) || !in_array('active', $options['attributes']['class'])) {
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskInterface.php b/core/lib/Drupal/Core/Menu/LocalTaskInterface.php
index b7f581d..790e801 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskInterface.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskInterface.php
@@ -7,6 +7,8 @@
namespace Drupal\Core\Menu;
+use Symfony\Component\HttpFoundation\Request;
+
/**
* Defines an interface for menu local tasks.
*/
@@ -32,15 +34,17 @@ public function getRouteName();
public function getTitle();
/**
- * Returns an internal Drupal path to use when creating the link for the tab.
+ * Returns the route parameters needed to render a link for the local task.
*
- * Subclasses may add optional arguments like NodeInterface $node = NULL that
- * will be supplied by the ControllerResolver.
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The HttpRequest object representing the current request.
*
- * @return string
- * The path of this local task.
+ * @return array
+ * An array of parameter names and values.
+ *
+ * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate()
*/
- public function getPath();
+ public function getRouteParameters(Request $request);
/**
* Returns the weight of the local task.
@@ -51,14 +55,17 @@ public function getPath();
public function getWeight();
/**
- * Returns an array of options suitable to pass to l().
+ * 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
- * Associative array of options.
+ * An array of options.
*
- * @see l()
+ * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate()
*/
- public function getOptions();
+ public function getOptions(Request $request);
/**
* Sets the active status.
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
index 3ae67fd..f1139fe 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
@@ -8,7 +8,9 @@
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Access\AccessManager;
use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Plugin\DefaultPluginManager;
@@ -17,7 +19,6 @@
use Drupal\Core\Plugin\Factory\ContainerFactory;
use Drupal\Core\Routing\RouteProviderInterface;
use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
/**
* Manages discovery and instantiation of menu local task plugins.
@@ -34,6 +35,8 @@ class LocalTaskManager extends DefaultPluginManager {
protected $defaults = array(
// (required) The name of the route this task links to.
'route_name' => '',
+ // Parameters for route variables when generating a link.
+ 'route_parameters' => array(),
// The static title for the local task.
'title' => '',
// The plugin ID of the root tab.
@@ -53,7 +56,7 @@ class LocalTaskManager extends DefaultPluginManager {
/**
* A controller resolver object.
*
- * @var \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface
+ * @var \Drupal\Core\Controller\ControllerResolverInterface
*/
protected $controllerResolver;
@@ -79,9 +82,16 @@ class LocalTaskManager extends DefaultPluginManager {
protected $routeProvider;
/**
+ * The access manager.
+ *
+ * @var \Drupal\Core\Access\AccessManager
+ */
+ protected $accessManager;
+
+ /**
* Constructs a \Drupal\Core\Menu\LocalTaskManager object.
*
- * @param \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface $controller_resolver
+ * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
* An object to use in introspecting route methods.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object to use for building titles and paths for plugin instances.
@@ -93,14 +103,17 @@ class LocalTaskManager extends DefaultPluginManager {
* The cache backend.
* @param \Drupal\Core\Language\LanguageManager $language_manager
* The language manager.
+ * @param \Drupal\Core\Access\AccessManager $access_manager
+ * The access manager.
*/
- public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManager $language_manager) {
+ public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManager $language_manager, AccessManager $access_manager) {
$this->discovery = new YamlDiscovery('local_tasks', $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, 'local_tasks');
$this->setCacheBackend($cache, $language_manager, 'local_task', array('local_task' => TRUE));
}
@@ -132,21 +145,6 @@ public function getTitle(LocalTaskInterface $local_task) {
}
/**
- * Gets the Drupal path for a local task.
- *
- * @param \Drupal\Core\Menu\LocalTaskInterface $local_task
- * The local task plugin instance to get the path for.
- *
- * @return string
- * The path.
- */
- public function getPath(LocalTaskInterface $local_task) {
- $controller = array($local_task, 'getPath');
- $arguments = $this->controllerResolver->getArguments($this->request, $controller);
- return call_user_func_array($controller, $arguments);
- }
-
- /**
* Find all local tasks that appear on a named route.
*
* @param string $route_name
@@ -240,14 +238,14 @@ public function getLocalTasksForRoute($route_name) {
/**
* Gets the render array for all local tasks.
*
- * @param string $route_name
+ * @param string $current_route_name
* The route for which to make renderable local tasks.
*
* @return array
* A render array as expected by theme_menu_local_tasks.
*/
- public function getTasksBuild($route_name) {
- $tree = $this->getLocalTasksForRoute($route_name);
+ public function getTasksBuild($current_route_name) {
+ $tree = $this->getLocalTasksForRoute($current_route_name);
$build = array();
// Collect all route names.
@@ -257,31 +255,34 @@ public function getTasksBuild($route_name) {
$route_names[] = $child->getRouteName();
}
}
- // Fetches all routes involved in the tree.
+ // Pre-fetch all routes involved in the tree. 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();
foreach ($tree as $level => $instances) {
- foreach ($instances as $child) {
- $path = $this->getPath($child);
+ foreach ($instances as $plugin_id => $child) {
+ // In order to get the Drupal path the base URL has to be stripped off.
+ $route_name = $child->getRouteName();
+ $route_parameters = $child->getRouteParameters($this->request);
+
// Find out whether the user has access to the task.
- $route = $routes[$child->getRouteName()];
- $map = array();
- // @todo - replace this call when we have a real service for it.
- $access = menu_item_route_access($route, $path, $map);
+ $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters);
if ($access) {
// Need to flag the list element as active for a tab for the current
// route or if the plugin is set active (i.e. the parent tab).
- $active = ($route_name == $child->getRouteName() || $child->getActive());
+ $active = (($current_route_name == $route_name && (array_intersect_assoc($route_parameters, $this->request->attributes->all()) == $route_parameters)) || $child->getActive());
// @todo It might make sense to use menu link entities instead of
// arrays.
- $menu_link = array(
+
+ $link = array(
'title' => $this->getTitle($child),
- 'href' => $path,
- 'localized_options' => $child->getOptions(),
+ 'route_name' => $route_name,
+ 'route_parameters' => $route_parameters,
+ 'localized_options' => $child->getOptions($this->request),
);
- $build[$level][$path] = array(
+ $build[$level][$plugin_id] = array(
'#theme' => 'menu_local_task',
- '#link' => $menu_link,
+ '#link' => $link,
'#active' => $active,
'#weight' => $child->getWeight(),
'#access' => $access,
diff --git a/core/modules/comment/comment.local_tasks.yml b/core/modules/comment/comment.local_tasks.yml
new file mode 100644
index 0000000..ee8d3bf
--- /dev/null
+++ b/core/modules/comment/comment.local_tasks.yml
@@ -0,0 +1,15 @@
+comment_permalink_tab:
+ route_name: comment_permalink
+ title: 'View comment'
+ tab_root_id: comment_permalink_tab
+comment_edit_page_tab:
+ route_name: comment_edit_page
+ title: 'Edit'
+ tab_root_id: comment_permalink_tab
+ weight: 0
+comment_confirm_delete_tab:
+ route_name: comment_confirm_delete
+ title: 'Delete'
+ tab_root_id: comment_permalink_tab
+ weight: 10
+
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/LocalTasksTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/LocalTasksTest.php
index 43a2c8d..0f54856 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/LocalTasksTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/LocalTasksTest.php
@@ -155,36 +155,41 @@ public function testPluginLocalTask() {
));
// Ensure the view tab is active.
- $result = $this->xpath('//ul[contains(@class, "tabs")]//a[contains(@class, "active")]');
+ $result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]/a');
$this->assertEqual(1, count($result), 'There is just a single active tab.');
$this->assertEqual('View', (string) $result[0], 'The view tab is active.');
// Verify that local tasks in the second level appear.
-
- $this->drupalGet('menu-local-task-test/tasks/settings');
- $this->assertLocalTasks(array(
+ $sub_tasks = array(
'menu-local-task-test/tasks/settings/sub1',
'menu-local-task-test/tasks/settings/sub2',
'menu-local-task-test/tasks/settings/sub3',
- ), 1);
+ 'menu-local-task-test/tasks/settings/derive1',
+ 'menu-local-task-test/tasks/settings/derive2',
+ );
+ $this->drupalGet('menu-local-task-test/tasks/settings');
+ $this->assertLocalTasks($sub_tasks, 1);
- $result = $this->xpath('//ul[contains(@class, "tabs")]//a[contains(@class, "active")]');
+ $result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]/a');
$this->assertEqual(1, count($result), 'There is just a single active tab.');
$this->assertEqual('Settings', (string) $result[0], 'The settings tab is active.');
-
$this->drupalGet('menu-local-task-test/tasks/settings/sub1');
- $this->assertLocalTasks(array(
- 'menu-local-task-test/tasks/settings/sub1',
- 'menu-local-task-test/tasks/settings/sub2',
- 'menu-local-task-test/tasks/settings/sub3',
- ), 1);
+ $this->assertLocalTasks($sub_tasks, 1);
$result = $this->xpath('//ul[contains(@class, "tabs")]//a[contains(@class, "active")]');
$this->assertEqual(2, count($result), 'There are tabs active on both levels.');
$this->assertEqual('Settings', (string) $result[0], 'The settings tab is active.');
$this->assertEqual('Dynamic title for TestTasksSettingsSub1', (string) $result[1], 'The sub1 tab is active.');
+ $this->drupalGet('menu-local-task-test/tasks/settings/derive1');
+ $this->assertLocalTasks($sub_tasks, 1);
+
+ $result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]');
+ $this->assertEqual(2, count($result), 'There are tabs active on both levels.');
+ $this->assertEqual('Settings', (string) $result[0]->a, 'The settings tab is active.');
+ $this->assertEqual('Derive 1', (string) $result[1]->a, 'The derive1 tab is active.');
+
// Ensures that the local tasks contains the proper 'provider key'
$definitions = $this->container->get('plugin.manager.menu.local_task')->getDefinitions();
$this->assertEqual($definitions['menu_local_task_test_tasks_view']['provider'], 'menu_test');
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Derivative/LocalTaskTest.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Derivative/LocalTaskTest.php
new file mode 100644
index 0000000..807c78d
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Derivative/LocalTaskTest.php
@@ -0,0 +1,26 @@
+ 'Derive 1', 'derive2' => 'Derive 2') as $key => $title) {
+ $this->derivatives[$key] = $base_plugin_definition;
+ $this->derivatives[$key]['title'] = $title;
+ $this->derivatives[$key]['route_parameters'] = array('placeholder' => $key);
+ $this->derivatives[$key]['weight'] = $weight++; // ensure weights for testing.
+ }
+ return $this->derivatives;
+ }
+}
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/TestControllers.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/TestControllers.php
index c36a0ae..608e0c7 100644
--- a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/TestControllers.php
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/TestControllers.php
@@ -38,6 +38,13 @@ public function test2() {
/**
* Prints out test data.
+ */
+ public function testDerived() {
+ return 'testDerived';
+ }
+
+ /**
+ * Prints out test data.
*
* @param string|null $placeholder
* A placeholder for the return string.
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/menu_test.local_tasks.yml b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/menu_test.local_tasks.yml
new file mode 100644
index 0000000..43b9de4
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/menu_test.local_tasks.yml
@@ -0,0 +1,8 @@
+menu_local_task_test_placeholder_sub1:
+ route_name: menu_local_task_test_placeholder_sub1
+ title: 'placeholder sub1'
+ tab_root_id: menu_local_task_test_placeholder_sub1
+menu_local_task_test_placeholder_sub2:
+ route_name: menu_local_task_test_placeholder_sub2
+ title: 'placeholder sub2'
+ tab_root_id: menu_local_task_test_placeholder_sub1
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml b/core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml
index 08eeaf1..a7cec6e 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml
@@ -28,3 +28,10 @@ menu_local_task_test_tasks_settings_sub3:
tab_root_id: menu_local_task_test_tasks_view
tab_parent_id: menu_local_task_test_tasks_settings
weight: 20
+menu_local_task_test_tasks_settings_derived:
+ route_name: menu_test.local_task_test_tasks_settings_derived
+ title: derived
+ tab_root_id: menu_local_task_test_tasks_view
+ tab_parent_id: menu_local_task_test_tasks_settings
+ derivative: Drupal\menu_test\Plugin\Derivative\LocalTaskTest
+ weight: 50
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.routing.yml b/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
index 661a667..7823aed 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
@@ -117,6 +117,27 @@ menu_test.local_task_test_tasks_settings_sub3:
requirements:
_access: 'TRUE'
+menu_test.local_task_test_tasks_settings_derived:
+ path: '/menu-local-task-test/tasks/settings/{placeholder}'
+ defaults:
+ _content: '\Drupal\menu_test\TestControllers::testDerived'
+ requirements:
+ _access: 'TRUE'
+
+menu_test.local_task_test_placeholder_sub1:
+ path: '/menu-local-task-test-dynamic/{placeholder}/sub1'
+ defaults:
+ _content: '\Drupal\menu_test\TestControllers::test1'
+ requirements:
+ _access: 'TRUE'
+
+menu_test.local_task_test_placeholder_sub2:
+ path: '/menu-local-task-test-dynamic/{placeholder}/sub2'
+ defaults:
+ _content: '\Drupal\menu_test\TestControllers::test1'
+ requirements:
+ _access: 'TRUE'
+
menu_test.optional_placeholder:
path: '/menu-test/optional/{placeholder}'
defaults:
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php
new file mode 100644
index 0000000..6a7c8af
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php
@@ -0,0 +1,278 @@
+ 'local_task_default',
+ );
+
+ /**
+ * The mocked translator.
+ *
+ * @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $stringTranslation;
+
+ /**
+ * The mocked route provider.
+ *
+ * @var \Drupal\Core\Routing\RouteProviderInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $routeProvider;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Local tasks default plugin.',
+ 'description' => 'Tests the local task default class.',
+ 'group' => 'Menu',
+ );
+ }
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->stringTranslation = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface');
+ $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
+ }
+
+ /**
+ * Setups the local task default.
+ */
+ protected function setupLocalTaskDefault() {
+ $this->localTaskBase = new LocalTaskDefault($this->config, $this->pluginId, $this->pluginDefinition, $this->stringTranslation, $this->routeProvider);
+ }
+
+ /**
+ * Tests the getRouteParameters for a static route.
+ *
+ * @see \Drupal\Core\Menu\LocalTaskDefault::getRouteParameters()
+ */
+ public function testGetRouteParametersForStaticRoute() {
+ $this->pluginDefinition = array(
+ 'route_name' => 'test_route'
+ );
+
+ $this->routeProvider->expects($this->once())
+ ->method('getRouteByName')
+ ->with('test_route')
+ ->will($this->returnValue(new Route('/test-route')));
+
+ $this->setupLocalTaskDefault();
+
+ $request = Request::create('/');
+ $this->assertEquals(array(), $this->localTaskBase->getRouteParameters($request));
+ }
+
+ /**
+ * Tests the getRouteParameters for a route with the parameters in the plugin definition.
+ */
+ public function testGetRouteParametersInPluginDefinitions() {
+ $this->pluginDefinition = array(
+ 'route_name' => 'test_route',
+ 'route_parameters' => array('parameter' => 'example')
+ );
+
+ $this->routeProvider->expects($this->once())
+ ->method('getRouteByName')
+ ->with('test_route')
+ ->will($this->returnValue(new Route('/test-route/{parameter}')));
+
+ $this->setupLocalTaskDefault();
+
+ $request = new Request();
+
+ $this->assertEquals(array('parameter' => 'example'), $this->localTaskBase->getRouteParameters($request));
+ }
+
+ /**
+ * Tests the getRouteParameters method for a route with dynamic non upcasted parameters.
+ *
+ * @see \Drupal\Core\Menu\LocalTaskDefault::getRouteParameters()
+ */
+ public function testGetRouteParametersForDynamicRoute() {
+ $this->pluginDefinition = array(
+ 'route_name' => 'test_route'
+ );
+
+ $this->routeProvider->expects($this->once())
+ ->method('getRouteByName')
+ ->with('test_route')
+ ->will($this->returnValue(new Route('/test-route/{parameter}')));
+
+ $this->setupLocalTaskDefault();
+
+ $request = new Request(array(), array(), array('parameter' => 'example'));
+
+ $this->assertEquals(array('parameter' => 'example'), $this->localTaskBase->getRouteParameters($request));
+ }
+
+ /**
+ * Tests the getRouteParameters method for a route with upcasted parameters.
+ *
+ * @see \Drupal\Core\Menu\LocalTaskDefault::getRouteParameters()
+ */
+ public function testGetRouteParametersForDynamicRouteWithUpcastedParameters() {
+ $this->pluginDefinition = array(
+ 'route_name' => 'test_route'
+ );
+
+ $this->routeProvider->expects($this->once())
+ ->method('getRouteByName')
+ ->with('test_route')
+ ->will($this->returnValue(new Route('/test-route/{parameter}')));
+
+ $this->setupLocalTaskDefault();
+
+ $request = new Request();
+ $raw_variables = new ParameterBag();
+ $raw_variables->set('parameter', 'example');
+ $request->attributes->set('parameter', (object) array('example2'));
+ $request->attributes->set('_raw_variables', $raw_variables);
+
+ $this->assertEquals(array('parameter' => 'example'), $this->localTaskBase->getRouteParameters($request));
+ }
+
+ /**
+ * Defines a test provider for getWeight()
+ *
+ * @see self::getWeight()
+ *
+ * @return array
+ * A list or test plugin definition and expected weight.
+ */
+ public function providerTestGetWeight() {
+ return array(
+ array(array('weight' => 314), 314),
+ // Ensure that a default tab get a lower weight.
+ array(
+ array(
+ 'tab_root_id' => 'local_task_default',
+ 'id' => 'local_task_default'
+ ),
+ -10
+ ),
+ array(
+ array(
+ 'tab_root_id' => 'local_task_example',
+ 'id' => 'local_task_default'
+ ),
+ 0
+ ),
+ );
+ }
+
+ /**
+ * Tests the getWeight method.
+ *
+ * @dataProvider providerTestGetWeight
+ *
+ * @see \Drupal\Core\Menu\LocalTaskDefault::getWeight()
+ */
+ public function testGetWeight(array $plugin_definition, $expected_weight) {
+ $this->pluginDefinition = $plugin_definition;
+ $this->setupLocalTaskDefault();
+
+ $this->assertEquals($expected_weight, $this->localTaskBase->getWeight());
+ }
+
+ /**
+ * Tests getActive/setActive() method.
+ *
+ * @see \Drupal\Core\Menu\LocalTaskDefault::getActive()
+ * @see \Drupal\Core\Menu\LocalTaskDefault::setActive()
+ */
+ public function testActive() {
+ $this->setupLocalTaskDefault();
+
+ $this->assertFalse($this->localTaskBase->getActive());
+ $this->localTaskBase->setActive();
+ $this->assertTrue($this->localTaskBase->getActive());
+ }
+
+ /**
+ * Tests the getTitle method.
+ *
+ * @see \Drupal\Core\Menu\LocalTaskDefault::getTitle()
+ */
+ public function testGetTitle() {
+ $this->pluginDefinition['title'] = 'Example';
+ $this->stringTranslation->expects($this->once())
+ ->method('translate', $this->pluginDefinition['title'])
+ ->will($this->returnValue('Example translated'));
+
+ $this->setupLocalTaskDefault();
+ $this->assertEquals('Example translated', $this->localTaskBase->getTitle());
+ }
+
+ /**
+ * Tests the getOption method.
+ *
+ * @see \Drupal\Core\Menu\LocalTaskDefault::getOption()
+ */
+ public function testGetOptions() {
+ $this->pluginDefinition['options'] = array(
+ 'attributes' => array('class' => array('example')),
+ );
+
+ $this->setupLocalTaskDefault();
+
+ $request = Request::create('/');
+ $this->assertEquals($this->pluginDefinition['options'], $this->localTaskBase->getOptions($request));
+
+ $this->localTaskBase->setActive(TRUE);
+
+ $this->assertEquals(array(
+ 'attributes' => array(
+ 'class' => array(
+ 'example',
+ 'active'
+ )
+ )
+ ), $this->localTaskBase->getOptions($request));
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
index 082a130..f10bb38 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
@@ -69,6 +69,13 @@ class LocalTaskManagerTest extends UnitTestCase {
*/
protected $cacheBackend;
+ /**
+ * The mocked access manager.
+ *
+ * @var \Drupal\Core\Access\AccessManager|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $accessManager;
+
public static function getInfo() {
return array(
'name' => 'Local tasks manager.',
@@ -89,6 +96,9 @@ protected function setUp() {
$this->pluginDiscovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface');
$this->factory = $this->getMock('Drupal\Component\Plugin\Factory\FactoryInterface');
$this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+ $this->accessManager = $this->getMockBuilder('Drupal\Core\Access\AccessManager')
+ ->disableOriginalConstructor()
+ ->getMock();
$this->setupLocalTaskManager();
}
@@ -208,24 +218,6 @@ public function testGetTitle() {
}
/**
- * Tests the getPath method.
- *
- * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager::getPath()
- */
- public function testGetPath() {
- $menu_local_task = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
- $menu_local_task->expects($this->once())
- ->method('getPath');
-
- $this->controllerResolver->expects($this->once())
- ->method('getArguments')
- ->with($this->request, array($menu_local_task, 'getPath'))
- ->will($this->returnValue(array()));
-
- $this->manager->getPath($menu_local_task);
- }
-
- /**
* Setups the local task manager for the test.
*/
protected function setupLocalTaskManager() {
@@ -243,6 +235,10 @@ protected function setupLocalTaskManager() {
$property->setAccessible(TRUE);
$property->setValue($this->manager, $this->request);
+ $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'accessManager');
+ $property->setAccessible(TRUE);
+ $property->setValue($this->manager, $this->accessManager);
+
$property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery');
$property->setAccessible(TRUE);
$property->setValue($this->manager, $this->pluginDiscovery);
@@ -274,14 +270,12 @@ protected function getLocalTaskFixtures() {
'route_name' => 'menu_local_task_test_tasks_settings',
'title' => 'Settings',
'tab_root_id' => 'menu_local_task_test_tasks_view',
- 'class' => 'Drupal\menu_test\Plugin\Menu\MenuLocalTasksTestTasksSettings',
);
$definitions['menu_local_task_test_tasks_edit'] = array(
'id' => 'menu_local_task_test_tasks_edit',
'route_name' => 'menu_local_task_test_tasks_edit',
'title' => 'Settings',
'tab_root_id' => 'menu_local_task_test_tasks_view',
- 'class' => 'Drupal\menu_test\Plugin\Menu\MenuLocalTasksTestTasksEdit',
'weight' => 20,
);
$definitions['menu_local_task_test_tasks_view'] = array(
@@ -289,13 +283,13 @@ protected function getLocalTaskFixtures() {
'route_name' => 'menu_local_task_test_tasks_view',
'title' => 'Settings',
'tab_root_id' => 'menu_local_task_test_tasks_view',
- 'class' => 'Drupal\menu_test\Plugin\Menu\MenuLocalTasksTestTasksView',
);
// Add the defaults from the LocalTaskManager.
foreach ($definitions as $id => &$info) {
$info += array(
'id' => '',
'route_name' => '',
+ 'route_parameters' => array(),
'title' => '',
'tab_root_id' => '',
'tab_parent_id' => NULL,