entity_reference_inline-8.x-1.x-dev/tests/src/Functional/Translation/ContentTranslation/TranslateWithLastElementHavingTheTargetTranslationTest.php
tests/src/Functional/Translation/ContentTranslation/TranslateWithLastElementHavingTheTargetTranslationTest.php
<?php
namespace Drupal\Tests\entity_reference_inline\Functional\Translation\ContentTranslation;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
/**
* Tests content translation for the inline entity reference widget.
*
* @group entity_reference_inline
*/
class TranslateWithLastElementHavingTheTargetTranslationTest extends BrowserTestBase {
/**
* The entity types to test with.
*
* Will be set by
* ::testTranslatingWithLastLevelReferenceHavingTheTargetTranslation and used
* by :: doTestTranslatingWithLastLevelReferenceHavingTheTargetTranslation.
*
* @var array
*/
protected $entityTypes = [];
/**
* The entity types to enable content translation for.
*
* Will be set by
* ::testTranslatingWithLastLevelReferenceHavingTheTargetTranslation and used
* by :: doTestTranslatingWithLastLevelReferenceHavingTheTargetTranslation.
*
* @var array
*/
protected $entityTypesEnableContentTranslation = [];
/**
* {@inheritdoc}
*/
public static $modules = ['entity_reference_inline', 'entity_test', 'content_translation'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create three languages.
ConfigurableLanguage::createFromLangcode('l1')->save();
ConfigurableLanguage::createFromLangcode('l2')->save();
ConfigurableLanguage::createFromLangcode('l3')->save();
}
/**
* Tests the correct content translation of an entity structure with the last
* element being already translated into the target translation and having as
* a source language of the target translation a language different than the
* source translation language used to translate the whole entity structure.
*/
public function testTranslatingWithLastLevelReferenceHavingTheTargetTranslation() {
$test_entity_types_combinations = $this->getEntityTypes();
$entity_types_enable_content_translation = $this->getEntityTypesEnableContentTranslation();
foreach ($test_entity_types_combinations as $delta => $entity_types) {
$this->entityTypes = $entity_types;
$this->entityTypesEnableContentTranslation = $entity_types_enable_content_translation[$delta];
foreach ($this->entityTypesEnableContentTranslation as $entity_type_data) {
$this->setEnabledContentTranslation($entity_type_data['entity_type'], $entity_type_data['bundle'], TRUE);
}
$this->doTestCorrectTestSetup();
$this->doTestTranslatingWithLastLevelReferenceHavingTheTargetTranslation(TRUE);
// Delete all entities in order to execute the test again with the field
// definition installed non-translatable otherwise it could not be
// uninstalled.
foreach ($entity_types as $entity_type_data) {
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_data['entity_type']);
$storage->resetCache();
$storage->delete($storage->loadMultiple());
}
$this->doTestTranslatingWithLastLevelReferenceHavingTheTargetTranslation(FALSE);
foreach ($this->entityTypesEnableContentTranslation as $entity_type_data) {
$this->setEnabledContentTranslation($entity_type_data['entity_type'], $entity_type_data['bundle'], FALSE);
}
}
}
/**
* Ensures a correct test setup.
*
* The first and the last entity types should be enabled for content
* translation.
*/
protected function doTestCorrectTestSetup() {
reset($this->entityTypes);
$first_entity_type_data = current($this->entityTypes);
$last_entity_type_data = end($this->entityTypes);
$content_translation_manager = \Drupal::service('content_translation.manager');
$ct_first_entity_type = $content_translation_manager->isEnabled($first_entity_type_data['entity_type'], $first_entity_type_data['bundle']);
$ct_last_entity_type = $content_translation_manager->isEnabled($last_entity_type_data['entity_type'], $last_entity_type_data['bundle']);
$this->assertTrue($ct_first_entity_type && $ct_last_entity_type, 'The first and the last levels of the entity structure have to be enabled for content translation for this test.');
}
/**
* Helper method for testTranslatingWithLastLevelReferenceHavingTheTargetTranslation.
*
* @param $translatable_inline_field
* Whether to test with a translatable or non-translatable field.
*/
protected function doTestTranslatingWithLastLevelReferenceHavingTheTargetTranslation($translatable_inline_field) {
$this->createInlineReferenceFields($translatable_inline_field);
$web_user = $this->drupalCreateUser($this->getTranslatorPermissions());
$this->drupalLogin($web_user);
$entity_type_manager = \Drupal::entityTypeManager();
$default_langcode = 'l1';
$target_langcode = 'l3';
$last_level_src_translation_of_target = 'l2';
// Create the entity structure with all the entities having one translation
// with the default language code and the last entity having three
// translations - the default one, an intermediate one and the target
// translation of the test having as a source language the intermediate one.
$reversed_entity_types = array_reverse($this->entityTypes);
$reversed_entity_structure = [];
$count = count($reversed_entity_types);
for ($i = 0; $i < $count; $i++) {
$entity_type_id = $reversed_entity_types[$i]['entity_type'];
$bundle = $reversed_entity_types[$i]['bundle'];
$entity_type = $entity_type_manager->getDefinition($entity_type_id);
$bundle_key = $entity_type->getKey('bundle');
$label_key = $entity_type->getKey('label');
$langcode_key = $entity_type->getKey('langcode');
$level_name = $count - 1 - $i;
$values = [$label_key => "level_{$level_name}", $langcode_key => $default_langcode];
if ($bundle_key) {
$values[$bundle_key] = $bundle;
}
if ($i != 0) {
$field_name_inline_reference = $reversed_entity_types[$i]['field_name'];
$values[$field_name_inline_reference] = $reversed_entity_structure[$i - 1];
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->createEntity($entity_type_manager->getStorage($entity_type_id), $entity_type_id, $values);
}
// The last entity should get also the target translation language.
else {
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
$content_translation_manager = \Drupal::service('content_translation.manager');
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->createEntity($entity_type_manager->getStorage($entity_type_id), $entity_type_id, $values);
// Translate to an intermediate translation to be used as a source for
// the target translation.
$translation = $entity->addTranslation($last_level_src_translation_of_target, $entity->toArray());
$metadata = $content_translation_manager->getTranslationMetadata($translation);
$metadata->setSource($default_langcode);
$translation = $translation->addTranslation($target_langcode, $translation->toArray());
$metadata_l3 = $content_translation_manager->getTranslationMetadata($translation);
$metadata_l3->setSource($last_level_src_translation_of_target);
}
$reversed_entity_structure[$i] = $entity;
}
// Saving only the main entity should trigger saving the referenced
// entities as well.
/** @var \Drupal\Core\Entity\ContentEntityInterface $first_level_entity */
$first_level_entity = end($reversed_entity_structure);
$first_level_entity->save();
$first_level_entity = $entity_type_manager->getStorage($first_level_entity->getEntityTypeId())->load($first_level_entity->id());
// Assert that all the entities have only the default language code and the
// last one has the target and an intermediate one translation.
/** @var \Drupal\Core\Entity\ContentEntityInterface $current_entity */
$current_entity = $first_level_entity;
$translation_langcodes = array_keys($current_entity->getTranslationLanguages());
$this->assertEquals([$default_langcode], $translation_langcodes);
$entity_data[0][$default_langcode] = $current_entity->getTranslation($default_langcode)->toArray();
for ($i = 1; $i < $count; $i++) {
$field_name_inline_reference = $this->entityTypes[$i - 1]['field_name'];
$current_entity = $current_entity->$field_name_inline_reference->entity;
$this->assertNotNull($current_entity);
$expected = [$default_langcode];
// Intermediate levels.
if ($i != ($count - 1)) {
$translation_langcodes = array_keys($current_entity->getTranslationLanguages());
$this->assertEquals([$default_langcode], $translation_langcodes);
$entity_data[$i][$default_langcode] = $current_entity->toArray();
}
// Last level.
else {
$translation_langcodes = array_keys($current_entity->getTranslationLanguages());
$expected[] = $last_level_src_translation_of_target;
$expected[] = $target_langcode;
sort($expected);
sort($translation_langcodes);
$this->assertEquals($expected, $translation_langcodes);
}
foreach ($translation_langcodes as $langcode) {
$entity_data[$i][$langcode] = $current_entity->getTranslation($langcode)->toArray();
}
}
// Translate the entity structure.
$add_url = Url::fromRoute("entity.{$first_level_entity->getEntityTypeId()}.content_translation_add",
[
$first_level_entity->getEntityTypeId() => $first_level_entity->id(),
'source' => $default_langcode,
'target' => $target_langcode,
],
[
'language' => ConfigurableLanguage::load($target_langcode),
]
);
$this->drupalPostForm($add_url, [], $this->getFormSubmitAction());
// Reset the entity cache of the whole entity structure.
foreach ($this->entityTypes as $entity_type_data) {
$entity_type_manager->getStorage($entity_type_data['entity_type'])->resetCache();
}
// Assert correct translations without changes to already existing
// translations.
$first_level_entity = $entity_type_manager->getStorage($first_level_entity->getEntityTypeId())->load($first_level_entity->id());
$entity_data_after_translation = $this->assertEntityReferenceCorrectTranslation($first_level_entity, $default_langcode, $target_langcode, $last_level_src_translation_of_target);
$this->assertEntityDataCorrectAfterTranslation($entity_data, $entity_data_after_translation);
}
/**
* Check if the translations are correctly set in all references.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $current_entity
*
* @return array
* Array structure of all entities by level and langcode
* level =>
* langcode =>
* entity_data
*/
protected function assertEntityReferenceCorrectTranslation($current_entity, $default_langcode, $target_langcode, $last_level_src_translation_of_target) {
$translation_langcodes = array_keys($current_entity->getTranslationLanguages());
$expected = [$default_langcode, $target_langcode];
sort($expected);
sort($translation_langcodes);
$this->assertEquals($expected, $translation_langcodes);
$entity_data_after_translation = [];
foreach (array_keys($current_entity->getTranslationLanguages()) as $langcode) {
$entity_data_after_translation[0][$langcode] = $current_entity->getTranslation($langcode)->toArray();
}
$count = count($this->entityTypes);
for ($i = 1; $i < $count; $i++) {
$field_name_inline_reference = $this->entityTypes[$i - 1]['field_name'];
$current_entity = $current_entity->$field_name_inline_reference->entity;
$expected = [$default_langcode];
// Intermediate levels.
if ($i != ($count - 1)) {
$translation_langcodes = array_keys($current_entity->getTranslationLanguages());
sort($expected);
sort($translation_langcodes);
$this->assertEquals($expected, $translation_langcodes, 'Translation mismatch for level ' . $i);
foreach ($translation_langcodes as $langcode) {
$entity_data_after_translation[$i][$langcode] = $current_entity->getTranslation($langcode)->toArray();
}
}
// Last level.
else {
$translation_langcodes = array_keys($current_entity->getTranslationLanguages());
$expected[] = $target_langcode;
$expected[] = $last_level_src_translation_of_target;
sort($expected);
sort($translation_langcodes);
$this->assertEquals($expected, $translation_langcodes);
foreach ($translation_langcodes as $langcode) {
$entity_data_after_translation[$i][$langcode] = $current_entity->getTranslation($langcode)->toArray();
}
}
}
return $entity_data_after_translation;
}
/**
* Checks for correct entity data after the content translation.
*
* @param $entity_data_before
* The entity data before the translation.
* @param $entity_data_after
* The entity data after the translation.
*/
protected function assertEntityDataCorrectAfterTranslation($entity_data_before, $entity_data_after) {
reset($entity_data_before);
end($entity_data_before);
$last_level = key($entity_data_before);
foreach ($entity_data_before as $level => $entity_level_data_before) {
$entity_level_data_after = $entity_data_after[$level];
if ($level != $last_level) {
foreach ($entity_level_data_before as $langcode => $data) {
$this->assertEquals($data, $entity_level_data_after[$langcode]);
}
}
else {
$this->assertEquals($entity_level_data_before, $entity_level_data_after);
}
}
}
/**
* Returns an array of entity types structure to test with.
*
* @return array
*/
protected function getEntityTypes() {
$entityTypes = [
0 => [
0 => ['entity_type' => 'entity_test_mul', 'bundle' => 'entity_test_mul', 'field_name' => 'entity_reference_inline', 'field_type' => 'entity_reference_inline'],
1 => ['entity_type' => 'entity_test_mul', 'bundle' => 'entity_test_mul', 'field_name' => 'entity_reference_inline', 'field_type' => 'entity_reference_inline'],
2 => ['entity_type' => 'entity_test_mul', 'bundle' => 'entity_test_mul', 'field_name' => 'entity_reference_inline'],
],
];
return $entityTypes;
}
/**
* Returns an array of entity types structure to enable content translation
* for.
*
* @return array
*/
protected function getEntityTypesEnableContentTranslation() {
$entityTypesEnableContentTranslation = [
0 => [
0 => ['entity_type' => 'entity_test_mul', 'bundle' => 'entity_test_mul'],
],
];
return $entityTypesEnableContentTranslation;
}
/**
* Creates an inline reference field or alters its translatability.
*
* @param $translatable_inline_field
* Whether the inline reference field should be translatable or not.
*/
protected function createInlineReferenceFields($translatable_inline_field) {
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
$entity_field_manager = \Drupal::service('entity_field.manager');
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
for ($i = 0; $i < count($this->entityTypes) - 1; $i++) {
$entity_type = $this->entityTypes[$i]['entity_type'];
$bundle = $this->entityTypes[$i]['bundle'];
$field_name = $this->entityTypes[$i]['field_name'];
$referenced_entity_type = $this->entityTypes[$i + 1]['entity_type'];
$referenced_entity_bundle = $this->entityTypes[$i + 1]['bundle'];
// Allow for base fields to be used as inline reference field.
$field_definitions = $entity_field_manager->getFieldDefinitions($entity_type, $bundle);
if (isset($field_definitions[$field_name])) {
$field_definitions[$field_name]->getConfig($bundle)->setTranslatable($translatable_inline_field)->save();
}
else {
if ($field_storage = FieldStorageConfig::load($entity_type . '.' . $field_name)) {
$field_storage->setTranslatable($translatable_inline_field);
$field_storage->save();
$entity_definition_update_manager->updateFieldStorageDefinition($field_storage);
}
else {
$field_type = $this->entityTypes[$i]['field_type'];
FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => $entity_type,
'type' => $field_type,
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'translatable' => $translatable_inline_field,
'settings' => [
'target_type' => $referenced_entity_type,
],
])
->save();
}
if ($field_config = FieldConfig::load($entity_type . '.' . $bundle . '.' . $field_name)) {
$field_config->setTranslatable($translatable_inline_field);
$field_config->save();
}
else {
FieldConfig::create([
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'translatable' => $translatable_inline_field,
'settings' => [
'handler' => 'default',
'handler_settings' => [
'target_bundles' => [
$referenced_entity_type => $referenced_entity_bundle,
],
],
],
])->save();
// Activate the widget in case the field is being created now.
// @todo: fails currently with "schema errors for core.entity_form_display.entity_test_mul.entity_test_mul.default
// with the following errors: core.entity_form_display.entity_test_mul.entity_test_mul.default:content.entity_reference_inline.settings.form_modes_bundles
// missing schema"
// entity_get_form_display($entity_type, $bundle, 'default')
// ->setComponent($field_name)
// ->save();
}
}
// Make sure both overrides are present.
$entity_field_manager->clearCachedFieldDefinitions();
}
}
/**
* Returns an array of permissions needed to translate the entity structure.
*
* @return array
* An array of user permissions.
*/
protected function getTranslatorPermissions() {
$entity_type_bundle_level_zero_data = reset($this->entityTypes);
$entity_type = $entity_type_bundle_level_zero_data['entity_type'];
$bundle = $entity_type_bundle_level_zero_data['bundle'];
$translate_permission = \Drupal::entityTypeManager()->getDefinition($entity_type)->getPermissionGranularity() == 'bundle' ? "translate {$bundle} {$entity_type}" : "translate {$entity_type}";
$permissions = ["administer entity_test content", "view test entity", 'create content translations', 'update content translations', $translate_permission];
return $permissions;
}
/**
* The submit action name to save the translated form content.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup|string
*/
protected function getFormSubmitAction() {
return t('Save');
}
/**
* Creates an entity based on the provided values.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The storage to use to create the entity.
* @param $entity_type_id
* The entity type id.
* @param $values
* The values to use to create the entity.
*
* @return \Drupal\Core\Entity\EntityInterface
* The created entity.
*/
protected function createEntity(EntityStorageInterface $storage, $entity_type_id, $values) {
return $storage->create($values);
}
/**
* Enables translation for the current entity type and bundle.
*/
protected function setEnabledContentTranslation($entity_type_id, $bundle, $enabled) {
// Enable translation for the current entity type and ensure the change is
// picked up.
\Drupal::service('content_translation.manager')->setEnabled($entity_type_id, $bundle, $enabled);
drupal_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
\Drupal::service('entity.definition_update_manager')->applyUpdates();
}
}
