diff --git a/core/lib/Drupal/Core/Menu/LocalActionManager.php b/core/lib/Drupal/Core/Menu/LocalActionManager.php index 32ae80e57bb272624ab64f99fa8c37f116d90658..79e40c9f365891017e20d25d8d406900dc4c76c3 100644 --- a/core/lib/Drupal/Core/Menu/LocalActionManager.php +++ b/core/lib/Drupal/Core/Menu/LocalActionManager.php @@ -11,6 +11,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManager; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Menu\LocalActionInterface; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\Component\Plugin\Discovery\ProcessDecorator; @@ -94,7 +95,7 @@ class LocalActionManager extends DefaultPluginManager { /** * The plugin instances. * - * @var array + * @var \Drupal\Core\Menu\LocalActionInterface[] */ protected $instances = array(); @@ -112,12 +113,14 @@ class LocalActionManager extends DefaultPluginManager { * The module handler. * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend * Cache backend instance to use. - * @param \Drupal\Core\Language\LanguageManager $language_manager + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. * @param \Drupal\Core\Access\AccessManager $access_manager * The access manager. + * @param \Drupal\Core\Session\AccountInterface $account + * The current user. */ - public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManager $language_manager, AccessManager $access_manager, AccountInterface $account) { + public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, AccessManager $access_manager, AccountInterface $account) { // Skip calling the parent constructor, since that assumes annotation-based // discovery. $this->discovery = new YamlDiscovery('local_actions', $module_handler->getModuleDirectories()); @@ -179,10 +182,10 @@ public function getActionsForRoute($route_appears) { } } $links = array(); - foreach ($this->instances[$route_appears] as $plugin) { + foreach ($this->instances[$route_appears] as $plugin_id => $plugin) { $route_name = $plugin->getRouteName(); $route_parameters = $plugin->getRouteParameters($this->request); - $links[$route_name] = array( + $links[$plugin_id] = array( '#theme' => 'menu_local_action', '#link' => array( 'title' => $this->getTitle($plugin), diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6685b9c8d641a4644e0055e4c61dd9583e2491c4 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php @@ -0,0 +1,386 @@ +<?php + +/** + * @file + * Contains \Drupal\Tests\Core\Menu\LocalActionManagerTest. + */ + +namespace Drupal\Tests\Core\Menu; + +use Drupal\Component\Plugin\Discovery\DiscoveryInterface; +use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\Core\Access\AccessManager; +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Menu\LocalActionManager; +use Drupal\Core\Routing\RouteProviderInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Tests\UnitTestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; + +/** + * Tests the local action manager. + * + * @group Drupal + * @group Menu + * + * @covers \Drupal\Core\Menu\LocalActionManager + */ +class LocalActionManagerTest extends UnitTestCase { + + /** + * The mocked controller resolver. + * + * @var \Drupal\Core\Controller\ControllerResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $controllerResolver; + + /** + * The mocked request. + * + * @var \Symfony\Component\HttpFoundation\Request|\PHPUnit_Framework_MockObject_MockObject + */ + protected $request; + + /** + * The mocked module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $moduleHandler; + + /** + * The mocked router provider. + * + * @var \Drupal\Core\Routing\RouteProviderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $routeProvider; + + /** + * The mocked cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheBackend; + + /** + * The mocked language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $languageManager; + + /** + * The mocked access manager. + * + * @var \Drupal\Core\Access\AccessManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $accessManager; + + /** + * The mocked account. + * + * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $account; + + /** + * The mocked factory. + * + * @var \Drupal\Component\Plugin\Factory\FactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $factory; + + /** + * The mocked plugin discovery. + * + * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $discovery; + + /** + * The tested local action manager + * + * @var \Drupal\Tests\Core\Menu\TestLocalActionManager + */ + protected $localActionManager; + + /** + * {@inheritdoc} + */ + public static function getInfo() { + return array( + 'name' => 'Local actions manager', + 'description' => 'Tests the local action manager.', + 'group' => 'Menu', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->controllerResolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface'); + $this->request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $this->languageManager->expects($this->any()) + ->method('getCurrentLanguage') + ->will($this->returnValue( + new Language(array('langcode' => 'en')) + )); + + $this->accessManager = $this->getMockBuilder('Drupal\Core\Access\AccessManager') + ->disableOriginalConstructor() + ->getMock(); + $this->account = $this->getMock('Drupal\Core\Session\AccountInterface'); + $this->discovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface'); + $this->factory = $this->getMock('Drupal\Component\Plugin\Factory\FactoryInterface'); + + $this->localActionManager = new TestLocalActionManager($this->controllerResolver, $this->request, $this->routeProvider, $this->moduleHandler, $this->cacheBackend, $this->languageManager, $this->accessManager, $this->account, $this->discovery, $this->factory); + } + + /** + * @covers \Drupal\Core\Menu\LocalActionManager::getTitle() + */ + public function testGetTitle() { + $local_action = $this->getMock('Drupal\Core\Menu\LocalActionInterface'); + $local_action->expects($this->once()) + ->method('getTitle') + ->with('test'); + + $this->controllerResolver->expects($this->once()) + ->method('getArguments') + ->with($this->request, array($local_action, 'getTitle')) + ->will($this->returnValue(array('test'))); + + $this->localActionManager->getTitle($local_action); + } + + /** + * @covers \Drupal\Core\Menu\LocalActionManager::getActionsForRoute() + * + * @dataProvider getActionsForRouteProvider + */ + public function testGetActionsForRoute($route_appears, array $plugin_definitions, array $expected_actions) { + $this->discovery->expects($this->any()) + ->method('getDefinitions') + ->will($this->returnValue($plugin_definitions)); + $map = array(); + foreach ($plugin_definitions as $plugin_id => $plugin_definition) { + $plugin = $this->getMock('Drupal\Core\Menu\LocalActionInterface'); + $plugin->expects($this->any()) + ->method('getRouteName') + ->will($this->returnValue($plugin_definition['route_name'])); + $plugin->expects($this->any()) + ->method('getRouteParameters') + ->will($this->returnValue(isset($plugin_definition['route_parameters']) ? $plugin_definition['route_parameters'] : array())); + $plugin->expects($this->any()) + ->method('getTitle') + ->will($this->returnValue($plugin_definition['title'])); + $this->controllerResolver->expects($this->any()) + ->method('getArguments') + ->with($this->request, array($plugin, 'getTitle')) + ->will($this->returnValue(array())); + + $plugin->expects($this->any()) + ->method('getWeight') + ->will($this->returnValue($plugin_definition['weight'])); + $this->controllerResolver->expects($this->any()) + ->method('getArguments') + ->with($this->request, array($plugin, 'getTitle')) + ->will($this->returnValue(array())); + $map[] = array($plugin_id, array(), $plugin); + } + $this->factory->expects($this->any()) + ->method('createInstance') + ->will($this->returnValueMap($map)); + + $this->assertEquals($expected_actions, $this->localActionManager->getActionsForRoute($route_appears)); + } + + public function getActionsForRouteProvider() { + // Single available and single expected plugins. + $data[] = array( + 'test_route', + array( + 'plugin_id_1' => array( + 'appears_on' => array( + 'test_route', + ), + 'route_name' => 'test_route_2', + 'title' => 'Plugin ID 1', + 'weight' => 0, + ), + ), + array( + 'plugin_id_1' => array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => 'Plugin ID 1', + 'route_name' => 'test_route_2', + 'route_parameters' => array(), + 'localized_options' => '', + ), + '#access' => NULL, + '#weight' => 0, + ), + ), + ); + // Multiple available and single expected plugins. + $data[] = array( + 'test_route', + array( + 'plugin_id_1' => array( + 'appears_on' => array( + 'test_route', + ), + 'route_name' => 'test_route_2', + 'title' => 'Plugin ID 1', + 'weight' => 0, + ), + 'plugin_id_2' => array( + 'appears_on' => array( + 'test_route2', + ), + 'route_name' => 'test_route_3', + 'title' => 'Plugin ID 2', + 'weight' => 0, + ), + ), + array( + 'plugin_id_1' => array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => 'Plugin ID 1', + 'route_name' => 'test_route_2', + 'route_parameters' => array(), + 'localized_options' => '', + ), + '#access' => NULL, + '#weight' => 0, + ), + ), + ); + + // Multiple available and multiple expected plugins and specified weight. + $data[] = array( + 'test_route', + array( + 'plugin_id_1' => array( + 'appears_on' => array( + 'test_route', + ), + 'route_name' => 'test_route_2', + 'title' => 'Plugin ID 1', + 'weight' => 1, + ), + 'plugin_id_2' => array( + 'appears_on' => array( + 'test_route', + ), + 'route_name' => 'test_route_3', + 'title' => 'Plugin ID 2', + 'weight' => 0, + ), + ), + array( + 'plugin_id_1' => array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => 'Plugin ID 1', + 'route_name' => 'test_route_2', + 'route_parameters' => array(), + 'localized_options' => '', + ), + '#access' => NULL, + '#weight' => 1, + ), + 'plugin_id_2' => array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => 'Plugin ID 2', + 'route_name' => 'test_route_3', + 'route_parameters' => array(), + 'localized_options' => '', + ), + '#access' => NULL, + '#weight' => 0, + ), + ), + ); + + // Two plugins with the same route name but different route parameters. + $data[] = array( + 'test_route', + array( + 'plugin_id_1' => array( + 'appears_on' => array( + 'test_route', + ), + 'route_name' => 'test_route_2', + 'route_parameters' => array('test1'), + 'title' => 'Plugin ID 1', + 'weight' => 1, + ), + 'plugin_id_2' => array( + 'appears_on' => array( + 'test_route', + ), + 'route_name' => 'test_route_2', + 'route_parameters' => array('test2'), + 'title' => 'Plugin ID 2', + 'weight' => 0, + ), + ), + array( + 'plugin_id_1' => array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => 'Plugin ID 1', + 'route_name' => 'test_route_2', + 'route_parameters' => array('test1'), + 'localized_options' => '', + ), + '#access' => NULL, + '#weight' => 1, + ), + 'plugin_id_2' => array( + '#theme' => 'menu_local_action', + '#link' => array( + 'title' => 'Plugin ID 2', + 'route_name' => 'test_route_2', + 'route_parameters' => array('test2'), + 'localized_options' => '', + ), + '#access' => NULL, + '#weight' => 0, + ), + ), + ); + + return $data; + } + +} + +class TestLocalActionManager extends LocalActionManager { + + public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, AccessManager $access_manager, AccountInterface $account, DiscoveryInterface $discovery, FactoryInterface $factory) { + $this->discovery = $discovery; + $this->factory = $factory; + $this->routeProvider = $route_provider; + $this->accessManager = $access_manager; + $this->account = $account; + $this->controllerResolver = $controller_resolver; + $this->request = $request; + $this->alterInfo($module_handler, 'menu_local_actions'); + $this->setCacheBackend($cache_backend, $language_manager, 'local_action_plugins', array('local_action' => TRUE)); + } + +}