diff --git a/core/core.services.yml b/core/core.services.yml index 465d145..8422ec5 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -375,7 +375,7 @@ services: class: Drupal\Core\EventSubscriber\ViewSubscriber tags: - { name: event_subscriber } - arguments: ['@content_negotiation'] + arguments: ['@content_negotiation', '@title_resolver'] private_key: class: Drupal\Core\PrivateKey arguments: ['@state'] diff --git a/core/lib/Drupal/Core/Controller/TitleResolver.php b/core/lib/Drupal/Core/Controller/TitleResolver.php index 17b7a2e..359af1d 100644 --- a/core/lib/Drupal/Core/Controller/TitleResolver.php +++ b/core/lib/Drupal/Core/Controller/TitleResolver.php @@ -57,8 +57,12 @@ public function getTitle(Request $request, Route $route) { $route_title = call_user_func_array($callable, $arguments); } elseif ($title = $route->getDefault('_title')) { + $options = array(); + if ($context = $route->getDefault('_title_context')) { + $options['context'] = $context; + } // Fall back to a static string from the route. - $route_title = $this->translationManager->translate($title); + $route_title = $this->translationManager->translate($title, array(), $options); } return $route_title; } diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php index ae1d083..d85dded 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php @@ -7,6 +7,8 @@ namespace Drupal\Core\EventSubscriber; +use Drupal\Core\Controller\TitleResolverInterface; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\KernelEvents; @@ -25,10 +27,31 @@ */ class ViewSubscriber implements EventSubscriberInterface { + /** + * The content negotiation. + * + * @var \Drupal\Core\ContentNegotiation + */ protected $negotiation; - public function __construct(ContentNegotiation $negotiation) { + /** + * The title resolver. + * + * @var \Drupal\Core\Controller\TitleResolverInterface + */ + protected $titleResolver; + + /** + * Constructs a new ViewSubscriber. + * + * @param \Drupal\Core\ContentNegotiation $negotiation + * The content negotiation. + * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver + * The title resolver. + */ + public function __construct(ContentNegotiation $negotiation, TitleResolverInterface $title_resolver) { $this->negotiation = $negotiation; + $this->titleResolver = $title_resolver; } /** @@ -73,8 +96,8 @@ public function onView(GetResponseForControllerResultEvent $event) { } // If no title was returned fall back to one defined in the route. - if (!isset($page_result['#title']) && $request->attributes->has('_title')) { - $page_result['#title'] = $request->attributes->get('_title'); + if (!isset($page_result['#title'])) { + $page_result['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); } $event->setResponse(new Response(drupal_render_page($page_result))); @@ -91,8 +114,8 @@ public function onView(GetResponseForControllerResultEvent $event) { } // If no title was returned fall back to one defined in the route. - if (!isset($page_result['#title']) && $request->attributes->has('_title')) { - $page_result['#title'] = $request->attributes->get('_title'); + if (!isset($page_result['#title'])) { + $page_result['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); } $event->setResponse(new Response(drupal_render($page_result))); @@ -154,6 +177,13 @@ public function onIframeUpload(GetResponseForControllerResultEvent $event) { */ public function onHtml(GetResponseForControllerResultEvent $event) { $page_callback_result = $event->getControllerResult(); + $request = $event->getRequest(); + + // If no title was returned fall back to one defined in the route. + if (!isset($page_callback_result['#title'])) { + $page_callback_result['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); + } + return new Response(drupal_render_page($page_callback_result)); } diff --git a/core/lib/Drupal/Core/Menu/LocalActionDefault.php b/core/lib/Drupal/Core/Menu/LocalActionDefault.php index be6e6ab..1661ac9 100644 --- a/core/lib/Drupal/Core/Menu/LocalActionDefault.php +++ b/core/lib/Drupal/Core/Menu/LocalActionDefault.php @@ -67,7 +67,11 @@ public function getRouteName() { */ public function getTitle() { // Subclasses may pull in the request or specific attributes as parameters. - return $this->t($this->pluginDefinition['title']); + $options = array(); + if (!empty($this->pluginDefinition['title_context'])) { + $options['context'] = $this->pluginDefinition['title_context']; + } + return $this->t($this->pluginDefinition['title'], array(), $options); } /** diff --git a/core/lib/Drupal/Core/Menu/LocalTaskDefault.php b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php index 058de09..ef8d023 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskDefault.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php @@ -75,7 +75,11 @@ public function getRouteParameters(Request $request) { */ public function getTitle() { // Subclasses may pull in the request or specific attributes as parameters. - return $this->t($this->pluginDefinition['title']); + $options = array(); + if (!empty($this->pluginDefinition['title_context'])) { + $options['context'] = $this->pluginDefinition['title_context']; + } + return $this->t($this->pluginDefinition['title'], array(), $options); } /** diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 4d1c90ac..7874406 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -239,6 +239,8 @@ system.date_format_localize_reset: system.modules_list: path: '/admin/modules' defaults: + _title: 'Extend' + _title_context: 'With components' _form: 'Drupal\system\Form\ModulesListForm' requirements: _permission: 'administer modules' diff --git a/core/tests/Drupal/Tests/Core/Controller/TitleResolverTest.php b/core/tests/Drupal/Tests/Core/Controller/TitleResolverTest.php new file mode 100644 index 0000000..085ef60 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Controller/TitleResolverTest.php @@ -0,0 +1,135 @@ + 'Title resolver', + 'description' => 'Tests the title resolver.', + 'group' => 'Routing', + ); + } + + protected function setUp() { + $this->controllerResolver = $this->getMock('\Drupal\Core\Controller\ControllerResolverInterface'); + $this->translationManager = $this->getMock('\Drupal\Core\StringTranslation\TranslationInterface'); + + $this->titleResolver = new TitleResolver($this->controllerResolver, $this->translationManager); + } + + /** + * Tests a static title without a context. + * + * @see \Drupal\Core\Controller\TitleResolver::getTitle() + */ + public function testStaticTitle() { + $request = new Request(); + $route = new Route('/test-route', array('_title' => 'static title')); + + $this->translationManager->expects($this->once()) + ->method('translate') + ->with('static title', array(), array()) + ->will($this->returnValue('translated title')); + + $this->assertEquals('translated title', $this->titleResolver->getTitle($request, $route)); + } + + /** + * Tests a static title with a context. + * + * @see \Drupal\Core\Controller\TitleResolver::getTitle() + */ + public function testStaticTitleWithContext() { + $request = new Request(); + $route = new Route('/test-route', array('_title' => 'static title', '_title_context' => 'context')); + + $this->translationManager->expects($this->once()) + ->method('translate') + ->with('static title', array(), array('context' => 'context')) + ->will($this->returnValue('translated title with context')); + + $this->assertEquals('translated title with context', $this->titleResolver->getTitle($request, $route)); + } + + /** + * Tests a dynamic title. + * + * @see \Drupal\Core\Controller\TitleResolver::getTitle() + */ + public function testDynamicTitle() { + $request = new Request(); + $route = new Route('/test-route', array('_title' => 'static title', '_title_callback' => 'Drupal\Tests\Core\Controller\TitleCallback::example')); + + $callable = array(new TitleCallback(), 'example'); + $this->controllerResolver->expects($this->once()) + ->method('getControllerFromDefinition') + ->with('Drupal\Tests\Core\Controller\TitleCallback::example') + ->will($this->returnValue($callable)); + $this->controllerResolver->expects($this->once()) + ->method('getArguments') + ->with($request, $callable) + ->will($this->returnValue(array('example'))); + + $this->assertEquals('test example', $this->titleResolver->getTitle($request, $route)); + } + +} + +/** + * Provides an example title callback for the testDynamicTitle method above. + */ +class TitleCallback { + + /** + * Gets the example string. + * + * @param string $value + * The dynamic value. + * + * @return string + * Returns the example string. + */ + public function example($value) { + return String::format('test @value', array('@value' => $value)); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalActionDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalActionDefaultTest.php new file mode 100644 index 0000000..6039e83 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Menu/LocalActionDefaultTest.php @@ -0,0 +1,124 @@ + 'local_action_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 actions default plugin.', + 'description' => 'Tests the local action 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 action default. + */ + protected function setupLocalActionDefault() { + $container = new ContainerBuilder(); + $container->set('string_translation', $this->stringTranslation); + \Drupal::setContainer($container); + + $this->localActionDefault = new LocalActionDefault($this->config, $this->pluginId, $this->pluginDefinition, $this->routeProvider); + } + + /** + * Tests the getTitle method without a translation context. + * + * @see \Drupal\Core\Menu\LocalTaskDefault::getTitle() + */ + public function testGetTitle() { + $this->pluginDefinition['title'] = 'Example'; + $this->stringTranslation->expects($this->once()) + ->method('translate') + ->with($this->pluginDefinition['title'], array(), array()) + ->will($this->returnValue('Example translated')); + + $this->setupLocalActionDefault(); + $this->assertEquals('Example translated', $this->localActionDefault->getTitle()); + } + + /** + * Tests the getTitle method with a translation context. + * + * @see \Drupal\Core\Menu\LocalTaskDefault::getTitle() + */ + public function testGetTitleWithContext() { + $this->pluginDefinition['title'] = 'Example'; + $this->pluginDefinition['title_context'] = 'context'; + $this->stringTranslation->expects($this->once()) + ->method('translate') + ->with($this->pluginDefinition['title'], array(), array('context' => 'context')) + ->will($this->returnValue('Example translated with context')); + + $this->setupLocalActionDefault(); + $this->assertEquals('Example translated with context', $this->localActionDefault->getTitle()); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php index 8cf9e96..e708c6a 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php @@ -253,14 +253,15 @@ public function testActive() { } /** - * Tests the getTitle method. + * Tests the getTitle method without a translation context. * * @see \Drupal\Core\Menu\LocalTaskDefault::getTitle() */ public function testGetTitle() { $this->pluginDefinition['title'] = 'Example'; $this->stringTranslation->expects($this->once()) - ->method('translate', $this->pluginDefinition['title']) + ->method('translate') + ->with($this->pluginDefinition['title'], array(), array()) ->will($this->returnValue('Example translated')); $this->setupLocalTaskDefault(); @@ -268,6 +269,23 @@ public function testGetTitle() { } /** + * Tests the getTitle method with a translation context. + * + * @see \Drupal\Core\Menu\LocalTaskDefault::getTitle() + */ + public function testGetTitleWithContext() { + $this->pluginDefinition['title'] = 'Example'; + $this->pluginDefinition['title_context'] = 'context'; + $this->stringTranslation->expects($this->once()) + ->method('translate') + ->with($this->pluginDefinition['title'], array(), array('context' => 'context')) + ->will($this->returnValue('Example translated with context')); + + $this->setupLocalTaskDefault(); + $this->assertEquals('Example translated with context', $this->localTaskBase->getTitle()); + } + + /** * Tests the getOption method. * * @see \Drupal\Core\Menu\LocalTaskDefault::getOption()