From 74db053104cd07bdebcaedeb5ee6165814fe811e Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole Date: Fri, 8 May 2015 21:53:09 +0100 Subject: [PATCH] Issue #2428103 by dawehner, amateescu, Berdir, AjitS: String formatter should link to its translation --- core/lib/Drupal/Core/Entity/Entity.php | 22 +++-- .../src/Tests/EntityUrlLanguageTest.php | 69 +++++++++++++ .../src/Tests/StatisticsReportsTest.php | 4 +- .../taxonomy/src/Tests/TermIndexTest.php | 4 +- .../src/Tests/Handler/FieldEntityLinkTest.php | 4 +- .../src/Tests/Handler/FieldFieldTest.php | 3 +- .../Tests/Core/Entity/EntityLinkTest.php | 25 ++++- .../Tests/Core/Entity/EntityUrlTest.php | 97 +++++++++++++------ 8 files changed, 186 insertions(+), 42 deletions(-) create mode 100644 core/modules/language/src/Tests/EntityUrlLanguageTest.php diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 1b384776a9..a5c3d0f0a4 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -204,8 +204,15 @@ public function urlInfo($rel = 'canonical', array $options = []) { $uri ->setOption('entity_type', $this->getEntityTypeId()) ->setOption('entity', $this); + + // Display links by default based on the current language. + if ($rel !== 'collection') { + $options += ['language' => $this->language()]; + } + $uri_options = $uri->getOptions(); $uri_options += $options; + return $uri->setOptions($uri_options); } @@ -323,13 +330,16 @@ public function access($operation, AccountInterface $account = NULL, $return_as_ * {@inheritdoc} */ public function language() { - $langcode = $this->{$this->getEntityType()->getKey('langcode')}; - $language = $this->languageManager()->getLanguage($langcode); - if (!$language) { - // Make sure we return a proper language object. - $langcode = $this->langcode ?: LanguageInterface::LANGCODE_NOT_SPECIFIED; - $language = new Language(array('id' => $langcode)); + if ($key = $this->getEntityType()->getKey('langcode')) { + $langcode = $this->$key; + $language = $this->languageManager()->getLanguage($langcode); + if ($language) { + return $language; + } } + // Make sure we return a proper language object. + $langcode = !empty($this->langcode) ? $this->langcode : LanguageInterface::LANGCODE_NOT_SPECIFIED; + $language = new Language(array('id' => $langcode)); return $language; } diff --git a/core/modules/language/src/Tests/EntityUrlLanguageTest.php b/core/modules/language/src/Tests/EntityUrlLanguageTest.php new file mode 100644 index 0000000000..915f7f605e --- /dev/null +++ b/core/modules/language/src/Tests/EntityUrlLanguageTest.php @@ -0,0 +1,69 @@ +installEntitySchema('entity_test'); + $this->installEntitySchema('configurable_language'); + $this->installSchema('system', 'router'); + \Drupal::service('router.builder')->rebuild(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + ConfigurableLanguage::create(['id' => 'es'])->save(); + ConfigurableLanguage::create(['id' => 'fr'])->save(); + + $this->config('language.types')->setData([ + 'configurable' => ['language_interface'], + 'negotiation' => ['language_interface' => ['enabled' => ['language-url' => 0]]], + ])->save(); + $this->config('language.negotiation')->setData([ + 'url' => [ + 'source' => 'path_prefix', + 'prefixes' => ['en' => 'en', 'es' => 'es', 'fr' => 'fr'] + ], + ])->save(); + $this->kernel->rebuildContainer(); + $this->container = $this->kernel->getContainer(); + \Drupal::setContainer($this->container); + } + + /** + * Ensures that entity URLs in a language have the right language prefix. + */ + public function testEntityUrlLanguage() { + $entity = EntityTest::create(); + $entity->addTranslation('es', ['name' => 'name spanish']); + $entity->addTranslation('fr', ['name' => 'name french']); + $entity->save(); + + $this->assertTrue(strpos($entity->urlInfo()->toString(), '/en/entity_test/' . $entity->id()) !== FALSE); + $this->assertTrue(strpos($entity->getTranslation('es')->urlInfo()->toString(), '/es/entity_test/' . $entity->id()) !== FALSE); + $this->assertTrue(strpos($entity->getTranslation('fr')->urlInfo()->toString(), '/fr/entity_test/' . $entity->id()) !== FALSE); + } + +} diff --git a/core/modules/statistics/src/Tests/StatisticsReportsTest.php b/core/modules/statistics/src/Tests/StatisticsReportsTest.php index 3ec956a06b..7f2937d08f 100644 --- a/core/modules/statistics/src/Tests/StatisticsReportsTest.php +++ b/core/modules/statistics/src/Tests/StatisticsReportsTest.php @@ -49,7 +49,9 @@ function testPopularContentBlock() { $this->assertText('All time', 'Found the all time popular content.'); $this->assertText('Last viewed', 'Found the last viewed popular content.'); - $this->assertRaw(\Drupal::l($node->label(), $node->urlInfo()), 'Found link to visited node.'); + // statistics.module doesn't use node entities, prevent the node language + // from being added to the options. + $this->assertRaw(\Drupal::l($node->label(), $node->urlInfo('canonical', ['language' => NULL])), 'Found link to visited node.'); } } diff --git a/core/modules/taxonomy/src/Tests/TermIndexTest.php b/core/modules/taxonomy/src/Tests/TermIndexTest.php index 43f97d57d9..aecb90a64c 100644 --- a/core/modules/taxonomy/src/Tests/TermIndexTest.php +++ b/core/modules/taxonomy/src/Tests/TermIndexTest.php @@ -211,6 +211,8 @@ function testTaxonomyTermHierarchyBreadcrumbs() { // Verify that the page breadcrumbs include a link to the parent term. $this->drupalGet('taxonomy/term/' . $term1->id()); - $this->assertRaw(\Drupal::l($term2->getName(), $term2->urlInfo()), 'Parent term link is displayed when viewing the node.'); + // Breadcrumbs are not rendered with a language, prevent the term + // language from being added to the options. + $this->assertRaw(\Drupal::l($term2->getName(), $term2->urlInfo('canonical', ['language' => NULL])), 'Parent term link is displayed when viewing the node.'); } } diff --git a/core/modules/views/src/Tests/Handler/FieldEntityLinkTest.php b/core/modules/views/src/Tests/Handler/FieldEntityLinkTest.php index d00b3afd09..08c6ae7567 100644 --- a/core/modules/views/src/Tests/Handler/FieldEntityLinkTest.php +++ b/core/modules/views/src/Tests/Handler/FieldEntityLinkTest.php @@ -122,10 +122,10 @@ protected function doTestEntityLink(AccountInterface $account, $expected_results if ($expected_result) { $path = $entity->url($template); $destination = $info[$template]['destination'] ? '?destination=/' : ''; - $expected_link = '' . $info[$template]['label'] . ''; + $expected_link = '' . $info[$template]['label'] . ''; } $link = $view->style_plugin->getField($index, $info[$template]['field_id']); - $this->assertEqual($link, $expected_link, SafeMarkup::format('@template entity link behaves as expected.', ['@template' => $template])); + $this->assertEqual($link, $expected_link); } $index++; } diff --git a/core/modules/views/src/Tests/Handler/FieldFieldTest.php b/core/modules/views/src/Tests/Handler/FieldFieldTest.php index 942379b9b4..14b114c8dd 100644 --- a/core/modules/views/src/Tests/Handler/FieldFieldTest.php +++ b/core/modules/views/src/Tests/Handler/FieldFieldTest.php @@ -302,7 +302,8 @@ public function testFieldAliasRender() { for ($i = 0; $i < 5; $i++) { $this->assertEqual($i + 1, $executable->getStyle()->getField($i, 'id')); $this->assertEqual('test ' . $i, $executable->getStyle()->getField($i, 'name')); - $this->assertEqual('test ' . $i . '', $executable->getStyle()->getField($i, 'name_alias')); + $entity = EntityTest::load($i + 1); + $this->assertEqual('test ' . $i . '', $executable->getStyle()->getField($i, 'name_alias')); } } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityLinkTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityLinkTest.php index 361a7ea456..9e54ebedb1 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityLinkTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityLinkTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\Core\Entity; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Language\Language; use Drupal\Core\Link; use Drupal\Tests\UnitTestCase; @@ -31,6 +32,13 @@ class EntityLinkTest extends UnitTestCase { */ protected $linkGenerator; + /** + * The mocked language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $languageManager; + /** * {@inheritdoc} */ @@ -39,10 +47,12 @@ protected function setUp() { $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); $this->linkGenerator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface'); + $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); $container->set('link_generator', $this->linkGenerator); + $container->set('language_manager', $this->languageManager); \Drupal::setContainer($container); } @@ -52,6 +62,13 @@ protected function setUp() { * @dataProvider providerTestLink */ public function testLink($entity_label, $link_text, $expected_text, $link_rel = 'canonical', array $link_options = []) { + $language = new Language(['id' => 'es']); + $link_options += ['language' => $language]; + $this->languageManager->expects($this->any()) + ->method('getLanguage') + ->with('es') + ->willReturn($language); + $route_name_map = [ 'canonical' => 'entity.test_entity_type.canonical', 'edit-form' => 'entity.test_entity_type.edit_form', @@ -67,8 +84,10 @@ public function testLink($entity_label, $link_text, $expected_text, $link_rel = ->willReturn($route_name_map); $entity_type->expects($this->any()) ->method('getKey') - ->with('label') - ->willReturn('label'); + ->willReturnMap([ + ['label', 'label'], + ['langcode', 'langcode'], + ]); $this->entityManager ->expects($this->any()) @@ -78,7 +97,7 @@ public function testLink($entity_label, $link_text, $expected_text, $link_rel = /** @var \Drupal\Core\Entity\Entity $entity */ $entity = $this->getMockForAbstractClass('Drupal\Core\Entity\Entity', [ - ['id' => $entity_id, 'label' => $entity_label], + ['id' => $entity_id, 'label' => $entity_label, 'langcode' => 'es'], $entity_type_id ]); diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php index 390eaf7d3a..9cab4fa6f2 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php @@ -7,8 +7,11 @@ namespace Drupal\Tests\Core\Entity; +use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Language\Language; +use Drupal\Core\Language\LanguageInterface; use Drupal\Tests\UnitTestCase; /** @@ -51,13 +54,39 @@ protected function setUp() { * * @dataProvider providerTestUrlInfo */ - public function testUrlInfo($entity_class, $link_template, $expected) { + public function testUrlInfo($entity_class, $link_template, $expected, $langcode = NULL) { /** @var $entity \Drupal\Core\Entity\EntityInterface */ $entity = $this->getMockForAbstractClass($entity_class, array(array('id' => 'test_entity_id'), 'test_entity_type')); - $uri = $this->getTestUrlInfo($entity, $link_template); + $uri = $this->getTestUrlInfo($entity, $link_template, [], $langcode); $this->assertSame($expected, $uri->getRouteName()); $this->assertSame($entity, $uri->getOption('entity')); + + if ($langcode) { + $this->assertEquals($langcode, $uri->getOption('language')->getId()); + } + else { + // The expected langcode for a config entity is 'en', because it sets the + // value as default property. + $expected_langcode = $entity instanceof ConfigEntityInterface ? 'en' : LanguageInterface::LANGCODE_NOT_SPECIFIED; + $this->assertEquals($expected_langcode, $uri->getOption('language')->getId()); + } + } + + /** + * @covers ::urlInfo + */ + public function testUrlInfoWithSpecificLanguageInOptions() { + /** @var $entity \Drupal\Core\Entity\EntityInterface */ + $entity = $this->getMockForAbstractClass('Drupal\Core\Entity\Entity', array(array('id' => 'test_entity_id'), 'test_entity_type')); + + // Ensure that a specified language overrides the current translation + // language. + $uri = $this->getTestUrlInfo($entity, 'edit-form', [], 'en'); + $this->assertEquals('en', $uri->getOption('language')->getId()); + + $uri = $this->getTestUrlInfo($entity, 'edit-form', ['language' => new Language(['id' => 'fr'])], 'en'); + $this->assertEquals('fr', $uri->getOption('language')->getId()); } /** @@ -65,10 +94,13 @@ public function testUrlInfo($entity_class, $link_template, $expected) { */ public function providerTestUrlInfo() { return array( - array('Drupal\Core\Entity\Entity', 'edit-form', 'entity.test_entity_type.edit_form'), - array('Drupal\Core\Config\Entity\ConfigEntityBase', 'edit-form', 'entity.test_entity_type.edit_form'), + array('Drupal\Core\Entity\Entity', 'edit-form', 'entity.test_entity_type.edit_form', NULL), + // Specify a langcode. + array('Drupal\Core\Entity\Entity', 'edit-form', 'entity.test_entity_type.edit_form', 'es'), + array('Drupal\Core\Entity\Entity', 'edit-form', 'entity.test_entity_type.edit_form', 'en'), + array('Drupal\Core\Config\Entity\ConfigEntityBase', 'edit-form', 'entity.test_entity_type.edit_form', NULL), // Test that overriding the default $rel parameter works. - array('Drupal\Core\Config\Entity\ConfigEntityBase', FALSE, 'entity.test_entity_type.edit_form'), + array('Drupal\Core\Config\Entity\ConfigEntityBase', FALSE, 'entity.test_entity_type.edit_form', NULL), ); } @@ -108,18 +140,24 @@ public function providerTestUrlInfoForInvalidLinkTemplate() { * The test entity. * @param string $link_template * The link template. + * @param string $langcode + * The langcode. * * @return \Drupal\Core\Url * The URL for this entity's link template. */ - protected function getTestUrlInfo(EntityInterface $entity, $link_template) { + protected function getTestUrlInfo(EntityInterface $entity, $link_template, array $options = [], $langcode = NULL) { $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); - $entity_type->expects($this->once()) + $entity_type->expects($this->any()) ->method('getLinkTemplates') ->will($this->returnValue(array( 'edit-form' => 'test_entity_type.edit', ))); + if ($langcode) { + $entity->langcode = $langcode; + } + $this->entityManager ->expects($this->any()) ->method('getDefinition') @@ -128,10 +166,15 @@ protected function getTestUrlInfo(EntityInterface $entity, $link_template) { // If no link template is given, call without a value to test the default. if ($link_template) { - $uri = $entity->urlInfo($link_template); + $uri = $entity->urlInfo($link_template, $options); } else { - $uri = $entity->urlInfo(); + if ($entity instanceof ConfigEntityInterface) { + $uri = $entity->urlInfo('edit-form', $options); + } + else { + $uri = $entity->urlInfo('canonical', $options); + } } return $uri; @@ -158,14 +201,14 @@ public function testUrlInfoForNewEntity() { */ public function testUrl() { $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); - $entity_type->expects($this->exactly(5)) + $entity_type->expects($this->any()) ->method('getLinkTemplates') ->will($this->returnValue(array( 'canonical' => 'test_entity_type.view', ))); $this->entityManager - ->expects($this->exactly(5)) + ->expects($this->any()) ->method('getDefinition') ->with('test_entity_type') ->will($this->returnValue($entity_type)); @@ -177,22 +220,20 @@ public function testUrl() { $this->assertSame('', $no_link_entity->url('banana')); $valid_entity = $this->getMockForAbstractClass('Drupal\Core\Entity\Entity', array(array('id' => 'test_entity_id'), 'test_entity_type')); - $this->urlGenerator->expects($this->exactly(2)) + + $language = new Language(array('id' => LanguageInterface::LANGCODE_NOT_SPECIFIED)); + $this->urlGenerator->expects($this->any()) ->method('generateFromRoute') - ->will($this->returnValueMap(array( - array( - 'entity.test_entity_type.canonical', - array('test_entity_type' => 'test_entity_id'), - array('entity_type' => 'test_entity_type', 'entity' => $valid_entity), - '/entity/test_entity_type/test_entity_id', - ), - array( - 'entity.test_entity_type.canonical', - array('test_entity_type' => 'test_entity_id'), - array('absolute' => TRUE, 'entity_type' => 'test_entity_type', 'entity' => $valid_entity), - 'http://drupal/entity/test_entity_type/test_entity_id', - ), - ))); + // Sadly returnValueMap() uses ===, see \PHPUnit_Framework_MockObject_Stub_ReturnValueMap::invoke + // so the $language object can't be compared directly. + ->willReturnCallback(function ($route_name, $route_parameters, $options) use ($language) { + if ($route_name === 'entity.test_entity_type.canonical' && $route_parameters === array('test_entity_type' => 'test_entity_id') && array_keys($options) === ['entity_type', 'entity', 'language'] && $options['language'] == $language) { + return '/entity/test_entity_type/test_entity_id'; + } + if ($route_name === 'entity.test_entity_type.canonical' && $route_parameters === array('test_entity_type' => 'test_entity_id') && array_keys($options) === ['absolute', 'entity_type', 'entity', 'language'] && $options['language'] == $language) { + return 'http://drupal/entity/test_entity_type/test_entity_id'; + } + }); $this->assertSame('/entity/test_entity_type/test_entity_id', $valid_entity->url()); $this->assertSame('http://drupal/entity/test_entity_type/test_entity_id', $valid_entity->url('canonical', array('absolute' => TRUE))); @@ -205,14 +246,14 @@ public function testUrl() { */ public function testGetSystemPath() { $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); - $entity_type->expects($this->exactly(3)) + $entity_type->expects($this->any()) ->method('getLinkTemplates') ->will($this->returnValue(array( 'canonical' => 'entity.test_entity_type.canonical', ))); $this->entityManager - ->expects($this->exactly(3)) + ->expects($this->any()) ->method('getDefinition') ->with('test_entity_type') ->will($this->returnValue($entity_type)); -- GitLab