diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index 36ac92e..f86d91e 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -93,6 +93,8 @@ function _content_translation_form_language_content_settings_form_alter(array &$
// entity might have fields and if there are fields to translate.
if ($entity_type->isFieldable()) {
$fields = $entity_manager->getFieldDefinitions($entity_type_id, $bundle);
+ $translation_metadata = content_translation_entity_base_field_info($entity_type);
+
if ($fields) {
$form['settings'][$entity_type_id][$bundle]['translatable'] = array(
'#type' => 'checkbox',
@@ -101,7 +103,8 @@ function _content_translation_form_language_content_settings_form_alter(array &$
foreach ($fields as $field_name => $definition) {
// Allow to configure only fields supporting multilingual storage.
- if (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable()) {
+ // We skip our own fields as they are always translatable.
+ if (!isset($translation_metadata[$field_name]) && !empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable()) {
$form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = array(
'#label' => $definition->getLabel(),
'#type' => 'checkbox',
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 5c86c4e..45d703e 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -6,8 +6,11 @@
*/
use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
@@ -129,6 +132,61 @@ function content_translation_entity_type_alter(array &$entity_types) {
}
/**
+ * Implements hook_entity_base_field_info().
+ */
+function content_translation_entity_base_field_info(EntityTypeInterface $entity_type) {
+ $info = array();
+
+ if ($entity_type instanceof ContentEntityTypeInterface && $entity_type->isTranslatable()) {
+ $info['translation_source'] = BaseFieldDefinition::create('language')
+ ->setLabel(t('Translation source'))
+ ->setDescription(t('The source language from which this translation was created.'))
+ ->setCustomStorage(TRUE)
+ ->setTranslatable(TRUE);
+
+ $info['translation_outdated'] = BaseFieldDefinition::create('boolean')
+ ->setLabel(t('Translation outdated'))
+ ->setDescription(t('A boolean indicating whether this translation needs to be updated.'))
+ ->setCustomStorage(TRUE)
+ ->setTranslatable(TRUE);
+
+ $info['translation_uid'] = BaseFieldDefinition::create('entity_reference')
+ ->setLabel(t('Translation author'))
+ ->setDescription(t('The author of this translation.'))
+ ->setSettings(array(
+ 'target_type' => 'user',
+ 'default_value' => 0,
+ ))
+ ->setCustomStorage(TRUE)
+ ->setTranslatable(TRUE);
+
+ $info['translation_status'] = BaseFieldDefinition::create('boolean')
+ ->setLabel(t('Translation status'))
+ ->setDescription(t('A boolean indicating whether the translation is visible to non-translators.'))
+ ->setSettings(array(
+ 'default_value' => 1,
+ ))
+ ->setCustomStorage(TRUE)
+ ->setTranslatable(TRUE);
+
+ $info['translation_created'] = BaseFieldDefinition::create('created')
+ ->setLabel(t('Translation created time'))
+ ->setDescription(t('The Unix timestamp when the translation was created.'))
+ ->setCustomStorage(TRUE)
+ ->setTranslatable(TRUE);
+
+ $info['translation_changed'] = BaseFieldDefinition::create('changed')
+ ->setLabel(t('Translation changed time'))
+ ->setDescription(t('The Unix timestamp when the translation was most recently saved.'))
+ ->setPropertyConstraints('value', array('EntityChanged' => array()))
+ ->setCustomStorage(TRUE)
+ ->setTranslatable(TRUE);
+ }
+
+ return $info;
+}
+
+/**
* Implements hook_entity_bundle_info_alter().
*/
function content_translation_entity_bundle_info_alter(&$bundles) {
@@ -456,7 +514,8 @@ function content_translation_language_fallback_candidates_entity_view_alter(&$ca
$current_user = \Drupal::currentuser();
if (!$current_user->hasPermission('translate any entity') && !$current_user->hasPermission($permission)) {
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
- if (empty($entity->translation[$langcode]['status'])) {
+ $translation = $entity->getTranslation($langcode);
+ if (empty($translation->translation_status->value)) {
unset($candidates[$langcode]);
}
}
@@ -494,15 +553,19 @@ function content_translation_load_translation_metadata(array $entities, $entity_
$query = 'SELECT * FROM {content_translation} te WHERE te.entity_type = :entity_type AND te.entity_id IN (:entity_id)';
$result = db_query($query, array(':entity_type' => $entity_type, ':entity_id' => array_keys($entities)));
$exclude = array('entity_type', 'entity_id', 'langcode');
+
foreach ($result as $record) {
$entity = $entities[$record->entity_id];
- // @todo Declare these as entity (translation?) properties.
- foreach ($record as $field_name => $value) {
- if (!in_array($field_name, $exclude)) {
- $langcode = $record->langcode;
- $entity->translation[$langcode][$field_name] = $value;
- if (!$entity->hasTranslation($langcode)) {
- $entity->initTranslation($langcode);
+ if ($entity instanceof ContentEntityInterface) {
+ foreach ($record as $key => $value) {
+ if (!in_array($key, $exclude)) {
+ $langcode = $record->langcode;
+ if (!$entity->hasTranslation($langcode)) {
+ $entity->initTranslation($langcode);
+ }
+ $name = 'translation_' . $key;
+ $item = $entity->getTranslation($langcode)->get($name);
+ $item->{$item[0]->getFieldDefinition()->getMainPropertyName()} = $value;
}
}
}
@@ -518,14 +581,27 @@ function content_translation_entity_insert(EntityInterface $entity) {
return;
}
+ $definitions = $entity->getFieldDefinitions();
$fields = array('entity_type', 'entity_id', 'langcode', 'source', 'outdated', 'uid', 'status', 'created', 'changed');
$query = db_insert('content_translation')->fields($fields);
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
- $translation = isset($entity->translation[$langcode]) ? $entity->translation[$langcode] : array();
+ $translation = $entity->getTranslation($langcode);
+
+ $record = array();
+ foreach ($fields as $key) {
+ $name = 'translation_' . $key;
+ if (isset($definitions[$name])) {
+ $items = $translation->get($name);
+ $property_name = $items[0]->mainPropertyName();
+ if ($property_name) {
+ $record[$key] = $items->{$property_name};
+ }
+ }
+ }
- $translation += array(
- 'source' => '',
+ $record += array(
+ 'source' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'uid' => \Drupal::currentUser()->id(),
'outdated' => FALSE,
'status' => TRUE,
@@ -533,15 +609,14 @@ function content_translation_entity_insert(EntityInterface $entity) {
'changed' => REQUEST_TIME,
);
- $translation['entity_type'] = $entity->getEntityTypeId();
- $translation['entity_id'] = $entity->id();
- $translation['langcode'] = $langcode;
+ $record['entity_type'] = $entity->getEntityTypeId();
+ $record['entity_id'] = $entity->id();
+ $record['langcode'] = $langcode;
// Reorder values to match the schema.
$values = array();
- foreach ($fields as $field_name) {
- $value = is_bool($translation[$field_name]) ? intval($translation[$field_name]) : $translation[$field_name];
- $values[$field_name] = $value;
+ foreach ($fields as $key) {
+ $values[$key] = is_bool($record[$key]) ? intval($record[$key]) : $record[$key];
}
$query->values($values);
}
@@ -649,11 +724,16 @@ function content_translation_entity_presave(EntityInterface $entity) {
if ($entity->getEntityTypeId() === 'field_instance_config') {
$entity->settings += array('translation_sync' => FALSE);
}
- if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) {
- // @todo Avoid using request attributes once translation metadata become
- // regular fields.
- $attributes = \Drupal::request()->attributes;
- \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $entity->language()->id, $attributes->get('source_langcode'));
+ if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && !$entity->isNew()) {
+ // If we are creating a new translation we need to use the source language
+ // as original language, since source values are the only ones available to
+ // compare against.
+ if (!isset($entity->original)) {
+ $entity->original = entity_load_unchanged($entity->entityType(), $entity->id());
+ }
+ $langcode = $entity->language()->id;
+ $source_langcode = !$entity->original->hasTranslation($langcode) ? $entity->translation_source->id : NULL;
+ \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $langcode, $source_langcode);
}
}
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 60eb6f3..c1a27ea 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -52,7 +52,7 @@ public function retranslate(EntityInterface $entity, $langcode = NULL) {
$updated_langcode = !empty($langcode) ? $langcode : $entity->language()->id;
$translations = $entity->getTranslationLanguages();
foreach ($translations as $langcode => $language) {
- $entity->translation[$langcode]['outdated'] = $langcode != $updated_langcode;
+ $entity->getTranslation($langcode)->translation_outdated->value = $langcode != $updated_langcode;
}
}
@@ -193,7 +193,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
);
// A new translation is enabled by default.
- $status = $new_translation || $entity->translation[$form_langcode]['status'];
+ $status = $new_translation || $entity->translation_status->value;
// If there is only one published translation we cannot unpublish it,
// since there would be nothing left to display.
$enabled = TRUE;
@@ -204,8 +204,8 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
// When creating a brand new translation, $entity->translation is not
// set.
if (!$new_translation) {
- foreach ($entity->translation as $translation) {
- $published += $translation['status'];
+ foreach ($entity->getTranslationLanguages() as $langcode => $language) {
+ $published += $entity->getTranslation($langcode)->translation_status->value;
}
}
$enabled = $published > 1;
@@ -222,7 +222,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
'#disabled' => !$enabled,
);
- $translate = !$new_translation && $entity->translation[$form_langcode]['outdated'];
+ $translate = !$new_translation && $entity->translation_outdated->value;
if (!$translate) {
$form['content_translation']['retranslate'] = array(
'#type' => 'checkbox',
@@ -245,8 +245,8 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
if ($new_translation) {
$name = \Drupal::currentUser()->getUsername();
}
- elseif ($entity->translation[$form_langcode]['uid']) {
- $name = user_load($entity->translation[$form_langcode]['uid'])->getUsername();
+ elseif ($entity->translation_uid->target_id) {
+ $name = $entity->translation_uid->entity->getUsername();
}
$form['content_translation']['name'] = array(
'#type' => 'textfield',
@@ -257,7 +257,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
'#description' => t('Leave blank for %anonymous.', array('%anonymous' => \Drupal::config('user.settings')->get('anonymous'))),
);
- $date = $new_translation ? REQUEST_TIME : $entity->translation[$form_langcode]['created'];
+ $date = $new_translation ? REQUEST_TIME : $entity->translation_created->value;
$form['content_translation']['created'] = array(
'#type' => 'textfield',
'#title' => t('Authored on'),
@@ -379,35 +379,22 @@ protected function addTranslatabilityClue(&$element) {
public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
$form_object = $form_state->getFormObject();
$form_langcode = $form_object->getFormLangcode($form_state);
-
- if (!isset($entity->translation[$form_langcode])) {
- $entity->translation[$form_langcode] = array();
- }
$values = $form_state->getValue('content_translation', array());
- $translation = &$entity->translation[$form_langcode];
- // @todo Use the entity setter when all entities support multilingual
- // properties.
- $translation['uid'] = !empty($values['name']) && ($account = user_load_by_name($values['name'])) ? $account->id() : 0;
- $translation['status'] = !empty($values['status']);
- $translation['created'] = !empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME;
- $translation['changed'] = REQUEST_TIME;
+ $entity->translation_uid->target_id = !empty($values['name']) && ($account = user_load_by_name($values['name'])) ? $account->id() : 0;
+ $entity->translation_status->value = !empty($values['status']);
+ $entity->translation_created->value = !empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME;
+ $entity->translation_changed->value = REQUEST_TIME;
$source_langcode = $this->getSourceLangcode($form_state);
if ($source_langcode) {
- $translation['source'] = $source_langcode;
+ $entity->translation_source->value = $source_langcode;
}
- $translation['outdated'] = !empty($values['outdated']);
+ $entity->translation_outdated->value = !empty($values['outdated']);
if (!empty($values['retranslate'])) {
$this->retranslate($entity, $form_langcode);
}
-
- // Set contextual information that can be reused during the storage phase.
- // @todo Remove this once translation metadata are converted to regular
- // fields.
- $attributes = \Drupal::request()->attributes;
- $attributes->set('source_langcode', $source_langcode);
}
/**
diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php
index 5f2c460..f780b73 100644
--- a/core/modules/content_translation/src/Controller/ContentTranslationController.php
+++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
@@ -46,6 +46,7 @@ public function prepareTranslation(ContentEntityInterface $entity, LanguageInter
* Array of page elements to render.
*/
public function overview(Request $request, $entity_type_id = NULL) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $request->attributes->get($entity_type_id);
$account = $this->currentUser();
$handler = $this->entityManager()->getHandler($entity_type_id, 'translation');
@@ -69,8 +70,9 @@ public function overview(Request $request, $entity_type_id = NULL) {
}
// Show source-language column if there are non-original source langcodes.
- $additional_source_langcodes = array_filter($entity->translation, function ($translation) use ($original) {
- return !empty($translation['source']) && $translation['source'] != $original;
+ $additional_source_langcodes = array_filter(array_keys($translations), function ($langcode) use ($entity, $original) {
+ $translation = $entity->getTranslation($langcode);
+ return $translation->translation_source->value != $original;
});
$show_source_column = !empty($additional_source_langcodes);
@@ -119,7 +121,8 @@ public function overview(Request $request, $entity_type_id = NULL) {
$links = &$operations['data']['#links'];
if (array_key_exists($langcode, $translations)) {
// Existing translation in the translation set: display status.
- $source = isset($entity->translation[$langcode]['source']) ? $entity->translation[$langcode]['source'] : '';
+ $translation = $entity->getTranslation($langcode);
+ $source = isset($translation->translation_source) ? $translation->translation_source->value : LanguageInterface::LANGCODE_NOT_SPECIFIED;
$is_original = $langcode == $original;
$label = $entity->getTranslation($langcode)->label();
$link = isset($links->links[$langcode]['href']) ? $links->links[$langcode] : array('href' => $entity->getSystemPath());
@@ -146,13 +149,12 @@ public function overview(Request $request, $entity_type_id = NULL) {
if (isset($links['edit'])) {
$links['edit']['title'] = $this->t('Edit');
}
- $translation = $entity->translation[$langcode];
$status = array('data' => array(
'#type' => 'inline_template',
'#template' => '{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}{% if outdated %} {{ "outdated"|t }}{% endif %}',
'#context' => array(
- 'status' => $translation['status'],
- 'outdated' => $translation['outdated'],
+ 'status' => $translation->translation_status->value,
+ 'outdated' => $translation->translation_outdated->value,
),
));
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php b/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
index 98b9540..659dc6e 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
@@ -115,10 +115,6 @@ function testImageFieldSync() {
$default_langcode = $this->langcodes[0];
$langcode = $this->langcodes[1];
- // Populate the required contextual values.
- $attributes = \Drupal::request()->attributes;
- $attributes->set('source_langcode', $default_langcode);
-
// Populate the test entity with some random initial values.
$values = array(
'name' => $this->randomMachineName(),
@@ -186,6 +182,7 @@ function testImageFieldSync() {
// Perform synchronization: the translation language is used as source,
// while the default language is used as target.
+ $translation->translation_source->id = $default_langcode;
$entity = $this->saveEntity($translation);
$translation = $entity->getTranslation($langcode);
@@ -215,8 +212,6 @@ function testImageFieldSync() {
'title' => $langcode . '_' . $removed_fid . '_' . $this->randomMachineName(),
);
$translation->{$this->fieldName}->setValue(array_values($values[$langcode]));
- // When updating an entity we do not have a source language defined.
- $attributes->remove('source_langcode');
$entity = $this->saveEntity($translation);
$translation = $entity->getTranslation($langcode);
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
index 58d0c59..7c91943 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
@@ -160,7 +160,7 @@ protected function doTestOutdatedStatus() {
$this->drupalGet($path);
$this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is now shown.');
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
- $this->assertFalse($entity->translation[$added_langcode]['outdated'], 'The "outdated" status has been correctly stored.');
+ $this->assertFalse($entity->getTranslation($added_langcode)->translation_outdated->value, 'The "outdated" status has been correctly stored.');
}
}
}
@@ -178,7 +178,7 @@ protected function doTestPublishedStatus() {
$edit = array('content_translation[status]' => FALSE);
$this->drupalPostForm($langcode . '/' . $path, $edit, $this->getFormSubmitAction($entity, $langcode));
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
- $this->assertFalse($entity->translation[$langcode]['status'], 'The translation has been correctly unpublished.');
+ $this->assertFalse($entity->getTranslation($langcode)->translation_status->value, 'The translation has been correctly unpublished.');
}
}
@@ -212,8 +212,9 @@ protected function doTestAuthoringInfo() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
foreach ($this->langcodes as $langcode) {
- $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly stored.');
- $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly stored.');
+ $translation = $entity->getTranslation($langcode);
+ $this->assertEqual($translation->translation_uid->target_id == $values[$langcode]['uid'], 'Translation author correctly stored.');
+ $this->assertEqual($translation->translation_created->value == $values[$langcode]['created'], 'Translation date correctly stored.');
}
// Try to post non valid values and check that they are rejected.
@@ -225,8 +226,9 @@ protected function doTestAuthoringInfo() {
);
$this->drupalPostForm($path, $edit, $this->getFormSubmitAction($entity, $langcode));
$this->assertTrue($this->xpath('//div[contains(@class, "error")]//ul'), 'Invalid values generate a list of form errors.');
- $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly kept.');
- $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly kept.');
+ $translation = $entity->getTranslation($langcode);
+ $this->assertEqual($translation->translation_uid->target_id == $values[$langcode]['uid'], 'Translation author correctly kept.');
+ $this->assertEqual($translation->translation_created->value == $values[$langcode]['created'], 'Translation date correctly kept.');
}
/**
diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php
index a43a642..28bd630 100644
--- a/core/modules/node/src/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/src/Tests/NodeTranslationUITest.php
@@ -126,7 +126,7 @@ protected function doTestPublishedStatus() {
// The node is created as unpublished thus we switch to the published
// status first.
$status = !$index;
- $this->assertEqual($status, $entity->translation[$langcode]['status'], 'The translation has been correctly unpublished.');
+ $this->assertEqual($status, $entity->getTranslation($langcode)->translation_status->value, 'The translation has been correctly unpublished.');
$translation = $entity->getTranslation($langcode);
$this->assertEqual($status, $translation->isPublished(), 'The status of the translation has been correctly saved.');
}
@@ -163,9 +163,9 @@ protected function doTestAuthoringInfo() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
foreach ($this->langcodes as $langcode) {
- $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly stored.');
- $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly stored.');
$translation = $entity->getTranslation($langcode);
+ $this->assertEqual($translation->translation_uid->target_id, $values[$langcode]['uid'], 'Translation author correctly stored.');
+ $this->assertEqual($translation->translation_created->value, $values[$langcode]['created'], 'Translation date correctly stored.');
$this->assertEqual($translation->getOwnerId(), $values[$langcode]['uid'], 'Author of translation correctly stored.');
$this->assertEqual($translation->getCreatedTime(), $values[$langcode]['created'], 'Date of Translation correctly stored.');
$this->assertEqual($translation->isSticky(), $values[$langcode]['sticky'], 'Sticky of Translation correctly stored.');