diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 5b4688f8187..d9e1df863f9 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -386,6 +386,8 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface * {@inheritdoc} */ public function urlInfo($rel = 'edit-form', array $options = []) { + // Unless language was already provided, avoid setting an explicit language. + $options += ['language' => NULL]; return parent::urlInfo($rel, $options); } diff --git a/core/modules/config/src/Tests/ConfigEntityListMultilingualTest.php b/core/modules/config/src/Tests/ConfigEntityListMultilingualTest.php new file mode 100644 index 00000000000..005940971d8 --- /dev/null +++ b/core/modules/config/src/Tests/ConfigEntityListMultilingualTest.php @@ -0,0 +1,68 @@ +getStorage('config_test')->load('override')->delete(); + ConfigurableLanguage::createFromLangcode('hu')->save(); + + $this->drupalPlaceBlock('local_actions_block'); + } + + /** + * Tests the listing UI with different language scenarios. + */ + function testListUI() { + // Log in as an administrative user to access the full menu trail. + $this->drupalLogin($this->drupalCreateUser(array('access administration pages', 'administer site configuration'))); + + // Get the list page. + $this->drupalGet('admin/structure/config_test'); + $this->assertLinkByHref('admin/structure/config_test/manage/dotted.default'); + + // Add a new entity using the action link. + $this->clickLink('Add test configuration'); + $edit = array( + 'label' => 'Antilop', + 'id' => 'antilop', + 'langcode' => 'hu', + ); + $this->drupalPostForm(NULL, $edit, t('Save')); + // Ensure that operations for editing the Hungarian entity appear in English. + $this->assertLinkByHref('admin/structure/config_test/manage/antilop'); + + // Get the list page in Hungarian and assert Hungarian admin links + // regardless of language of config entities. + $this->drupalGet('hu/admin/structure/config_test'); + $this->assertLinkByHref('hu/admin/structure/config_test/manage/dotted.default'); + $this->assertLinkByHref('hu/admin/structure/config_test/manage/antilop'); + } + +} diff --git a/core/modules/config/tests/config_test/src/ConfigTestForm.php b/core/modules/config/tests/config_test/src/ConfigTestForm.php index ac0bb17ae8f..14f4bef81be 100644 --- a/core/modules/config/tests/config_test/src/ConfigTestForm.php +++ b/core/modules/config/tests/config_test/src/ConfigTestForm.php @@ -10,6 +10,7 @@ namespace Drupal\config_test; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -126,6 +127,13 @@ class ConfigTestForm extends EntityForm { '#access' => !empty($size), ); + $form['langcode'] = array( + '#type' => 'language_select', + '#title' => t('Language'), + '#languages' => LanguageInterface::STATE_ALL, + '#default_value' => $entity->language()->getId(), + ); + $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', diff --git a/core/modules/views/src/Plugin/views/field/EntityOperations.php b/core/modules/views/src/Plugin/views/field/EntityOperations.php index 35fbfc3eaf1..7dfbe9fe9e0 100644 --- a/core/modules/views/src/Plugin/views/field/EntityOperations.php +++ b/core/modules/views/src/Plugin/views/field/EntityOperations.php @@ -9,7 +9,9 @@ namespace Drupal\views\Plugin\views\field; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Routing\RedirectDestinationTrait; +use Drupal\views\Entity\Render\EntityTranslationRenderTrait; use Drupal\views\ResultRow; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -22,6 +24,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class EntityOperations extends FieldPluginBase { + use EntityTranslationRenderTrait; use RedirectDestinationTrait; /** @@ -31,6 +34,13 @@ class EntityOperations extends FieldPluginBase { */ protected $entityManager; + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + /** * Constructor. * @@ -42,10 +52,14 @@ class EntityOperations extends FieldPluginBase { * The plugin implementation definition. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager) { + public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->entityManager = $entity_manager; + $this->languageManager = $language_manager; } /** @@ -56,7 +70,8 @@ class EntityOperations extends FieldPluginBase { $configuration, $plugin_id, $plugin_definition, - $container->get('entity.manager') + $container->get('entity.manager'), + $container->get('language_manager') ); } @@ -98,7 +113,7 @@ class EntityOperations extends FieldPluginBase { * {@inheritdoc} */ public function render(ResultRow $values) { - $entity = $this->getEntity($values); + $entity = $this->getEntityTranslation($this->getEntity($values), $values); $operations = $this->entityManager->getListBuilder($entity->getEntityTypeId())->getOperations($entity); if ($this->options['destination']) { foreach ($operations as &$operation) { @@ -120,8 +135,39 @@ class EntityOperations extends FieldPluginBase { * {@inheritdoc} */ public function query() { - // There is nothing to ensure or add for this handler, so we purposefully do - // nothing here and do not call parent::query() either. + // We purposefully do not call parent::query() because we do not want the + // default query behavior for Views fields. Instead, let the entity + // translation renderer provide the correct query behavior. + if ($this->languageManager->isMultilingual()) { + $this->getEntityTranslationRenderer()->query($this->query, $this->relationship); + } + } + + /** + * {@inheritdoc} + */ + public function getEntityTypeId() { + return $this->getEntityType(); + } + + /** + * {@inheritdoc} + */ + protected function getEntityManager() { + return $this->entityManager; + } + + /** + * {@inheritdoc} + */ + protected function getLanguageManager() { + return $this->languageManager; + } + /** + * {@inheritdoc} + */ + protected function getView() { + return $this->view; } } diff --git a/core/modules/views/src/Tests/Handler/FieldEntityOperationsTest.php b/core/modules/views/src/Tests/Handler/FieldEntityOperationsTest.php index 990c4c4da9c..e92b0249b40 100644 --- a/core/modules/views/src/Tests/Handler/FieldEntityOperationsTest.php +++ b/core/modules/views/src/Tests/Handler/FieldEntityOperationsTest.php @@ -9,6 +9,8 @@ namespace Drupal\views\Tests\Handler; use Drupal\Core\Url; use Drupal\entity_test\Entity\EntityTest; +use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\node\Entity\Node; /** * Tests the core Drupal\views\Plugin\views\field\EntityOperations handler. @@ -29,30 +31,62 @@ class FieldEntityOperationsTest extends HandlerTestBase { * * @var array */ - public static $modules = array('entity_test'); + public static $modules = array('node', 'language'); + + function setUp() { + parent::setUp(); + + // Create Article content type. + $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + } /** * Tests entity operations field. */ public function testEntityOperations() { - // Create some test entities. + // Add languages and refresh the container so the entity manager will have + // fresh data. + ConfigurableLanguage::createFromLangcode('hu')->save(); + ConfigurableLanguage::createFromLangcode('es')->save(); + $this->rebuildContainer(); + + // Create some test entities. Every other entity is Hungarian while all + // have a Spanish translation. + $entities = array(); for ($i = 0; $i < 5; $i++) { - EntityTest::create(array( - 'name' => $this->randomString(), - ))->save(); + $entity = Node::create([ + 'title' => $this->randomString(), + 'type' => 'article', + 'langcode' => $i % 2 === 0 ? 'hu' : 'en', + ]); + $entity->save(); + $translation = $entity->addTranslation('es'); + $translation->set('title', $entity->getTitle() . ' in Spanish'); + $translation->save(); + $entities[$i] = $entity; } - $entities = EntityTest::loadMultiple(); - $admin_user = $this->drupalCreateUser(array('access administration pages', 'administer entity_test content')); - $this->drupalLogin($admin_user); + $admin_user = $this->drupalCreateUser(array('access administration pages', 'administer nodes', 'bypass node access')); + $this->drupalLogin($this->rootUser); $this->drupalGet('test-entity-operations'); - + /** @var $entity \Drupal\entity_test\Entity\EntityTest */ foreach ($entities as $entity) { - $operations = \Drupal::entityManager()->getListBuilder('entity_test')->getOperations($entity); - foreach ($operations as $operation) { - $expected_destination = Url::fromUri('internal:/test-entity-operations')->toString(); - $result = $this->xpath('//ul[contains(@class, dropbutton)]/li/a[contains(@href, :path) and text()=:title]', array(':path' => $operation['url']->toString() . '?destination=' . $expected_destination, ':title' => $operation['title'])); - $this->assertEqual(count($result), 1, t('Found entity @operation link with destination parameter.', array('@operation' => $operation['title']))); + /** @var \Drupal\Core\Language\LanguageInterface $language */ + foreach ($entity->getTranslationLanguages() as $language) { + $entity = $entity->getTranslation($language->getId()); + $operations = \Drupal::entityManager()->getListBuilder('node')->getOperations($entity); + $this->assertTrue(count($operations) > 0, 'There are operations.'); + foreach ($operations as $operation) { + $expected_destination = Url::fromUri('internal:/test-entity-operations')->toString(); + $result = $this->xpath('//ul[contains(@class, dropbutton)]/li/a[@href=:path and text()=:title]', array(':path' => $operation['url']->toString() . '?destination=' . $expected_destination, ':title' => $operation['title'])); + $this->assertEqual(count($result), 1, t('Found entity @operation link with destination parameter.', array('@operation' => $operation['title']))); + // Entities which were created in Hungarian should link to the Hungarian + // edit form, others to the English one (which has no path prefix here). + $base_path = \Drupal::request()->getBasePath(); + $parts = explode('/', str_replace($base_path, '', $operation['url']->toString())); + $expected_prefix = ($language->getId() != 'en' ? $language->getId() : 'node'); + $this->assertEqual($parts[1], $expected_prefix, 'Entity operation links to the correct language for the entity.'); + } } } } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml index 9ccd9a9e8cf..8276613a349 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_operations.yml @@ -2,13 +2,13 @@ langcode: en status: true dependencies: module: - - entity_test + - node id: test_entity_operations label: test_entity_operations module: views description: '' tag: '' -base_table: entity_test +base_table: node_field_data base_field: id core: 8.x display: @@ -35,60 +35,29 @@ display: row: type: fields fields: - name: - id: name - table: entity_test - field: name - relationship: none - group_type: group - admin_label: '' - label: '' + title: + id: title + table: node_field_data + field: title + label: Title exclude: false alter: alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: false - element_wrapper_type: '' - element_wrapper_class: '' element_default_classes: true empty: '' hide_empty: false empty_zero: false hide_alter_empty: true - entity_type: entity_test - entity_field: name + entity_type: node + entity_field: title + type: string + settings: + link_to_entity: true plugin_id: field operations: id: operations - table: entity_test + table: node field: operations relationship: none group_type: group diff --git a/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php index c7c70026527..7300118caa3 100644 --- a/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php @@ -24,6 +24,13 @@ class EntityOperationsUnitTest extends UnitTestCase { */ protected $entityManager; + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $languageManager; + /** * The plugin under test. * @@ -38,13 +45,14 @@ class EntityOperationsUnitTest extends UnitTestCase { */ public function setUp() { $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'); + $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface'); $configuration = array(); $plugin_id = $this->randomMachineName(); $plugin_definition = array( 'title' => $this->randomMachineName(), ); - $this->plugin = new EntityOperations($configuration, $plugin_id, $plugin_definition, $this->entityManager); + $this->plugin = new EntityOperations($configuration, $plugin_id, $plugin_definition, $this->entityManager, $this->languageManager); $redirect_service = $this->getMock('Drupal\Core\Routing\RedirectDestinationInterface'); $redirect_service->expects($this->any()) diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php index 022a232d624..d19f473d160 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php @@ -66,10 +66,13 @@ class EntityUrlTest extends UnitTestCase { $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()); + if ($entity instanceof ConfigEntityInterface) { + // Config entities do not provide a language with their URIs. + $this->assertEquals(NULL, $uri->getOption('language')); + } + else { + $this->assertEquals(LanguageInterface::LANGCODE_NOT_SPECIFIED, $uri->getOption('language')->getId()); + } } }