diff --git a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php
index 49acdf1..3d27fe6 100644
--- a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php
+++ b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php
@@ -158,11 +158,9 @@ public function testDisabledBundle() {
$enabled_block_content = $this->createBlockContent();
$disabled_block_content = $this->createBlockContent(FALSE, $bundle->id());
- // Make sure that only a single row was inserted into the
- // {content_translation} table.
- $rows = db_query('SELECT * FROM {content_translation}')->fetchAll();
+ // Make sure that only a single row was inserted into the block table.
+ $rows = db_query('SELECT * FROM {block_content_field_data} WHERE id = :id', array(':id' => $enabled_block_content->id()))->fetchAll();
$this->assertEqual(1, count($rows));
- $this->assertEqual($enabled_block_content->id(), reset($rows)->entity_id);
}
}
diff --git a/core/modules/comment/src/CommentTranslationHandler.php b/core/modules/comment/src/CommentTranslationHandler.php
index aa796b1..e9a538b 100644
--- a/core/modules/comment/src/CommentTranslationHandler.php
+++ b/core/modules/comment/src/CommentTranslationHandler.php
@@ -9,6 +9,7 @@
use Drupal\Core\Entity\EntityInterface;
use Drupal\content_translation\ContentTranslationHandler;
+use Drupal\Core\Form\FormStateInterface;
/**
* Defines the translation handler for comments.
@@ -18,8 +19,36 @@ class CommentTranslationHandler extends ContentTranslationHandler {
/**
* {@inheritdoc}
*/
+ public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
+ parent::entityFormAlter($form, $form_state, $entity);
+
+ if (isset($form['content_translation'])) {
+ // We do not need to show these values on comment forms: they inherit the
+ // basic comment property values.
+ $form['content_translation']['status']['#access'] = FALSE;
+ $form['content_translation']['name']['#access'] = FALSE;
+ $form['content_translation']['created']['#access'] = FALSE;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
protected function entityFormTitle(EntityInterface $entity) {
return t('Edit comment @subject', array('@subject' => $entity->label()));
}
+ /**
+ * {@inheritdoc}
+ */
+ public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
+ if ($form_state->hasValue('content_translation')) {
+ $translation = &$form_state->getValue('content_translation');
+ /** @var \Drupal\comment\CommentInterface $entity */
+ $translation['status'] = $entity->isPublished();
+ $translation['name'] = $entity->getAuthorName();
+ }
+ parent::entityFormEntityBuild($entity_type, $entity, $form, $form_state);
+ }
+
}
diff --git a/core/modules/comment/src/Tests/CommentTranslationUITest.php b/core/modules/comment/src/Tests/CommentTranslationUITest.php
index 0067297..df28517 100644
--- a/core/modules/comment/src/Tests/CommentTranslationUITest.php
+++ b/core/modules/comment/src/Tests/CommentTranslationUITest.php
@@ -108,31 +108,58 @@ protected function getNewEntityValues($langcode) {
}
/**
- * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertPublishedStatus().
+ * {@inheritdoc}
*/
- protected function assertPublishedStatus() {
- parent::assertPublishedStatus();
- $entity = entity_load($this->entityTypeId, $this->entityId);
- $user = $this->drupalCreateUser(array('access comments'));
- $this->drupalLogin($user);
- $languages = $this->container->get('language_manager')->getLanguages();
+ protected function doTestPublishedStatus() {
+ $entity_manager = \Drupal::entityManager();
+ $storage = $entity_manager->getStorage($this->entityTypeId);
+
+ $storage->resetCache();
+ $entity = $storage->load($this->entityId);
+ $path = $entity->getSystemPath('edit-form');
- // Check that simple users cannot see unpublished field translations.
- $path = $entity->getSystemPath();
+ // Unpublish translations.
foreach ($this->langcodes as $index => $langcode) {
- $translation = $this->getTranslation($entity, $langcode);
- $value = $this->getValue($translation, 'comment_body', $langcode);
- $this->drupalGet($path, array('language' => $languages[$langcode]));
if ($index > 0) {
- $this->assertNoRaw($value, 'Unpublished field translation is not shown.');
- }
- else {
- $this->assertRaw($value, 'Published field translation is shown.');
+ $edit = array('status' => 0);
+ $this->drupalPostForm($langcode . '/' . $path, $edit, $this->getFormSubmitAction($entity, $langcode));
+ $storage->resetCache();
+ $entity = $storage->load($this->entityId);
+ $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
}
}
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doTestAuthoringInfo() {
+ $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+ $path = $entity->getSystemPath('edit-form');
+ $languages = $this->container->get('language_manager')->getLanguages();
+ $values = array();
+
+ // Post different authoring information for each translation.
+ foreach ($this->langcodes as $langcode) {
+ $user = $this->drupalCreateUser();
+ $values[$langcode] = array(
+ 'uid' => $user->id(),
+ 'created' => REQUEST_TIME - mt_rand(0, 1000),
+ );
+ $edit = array(
+ 'name' => $user->getUsername(),
+ 'date[date]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d'),
+ 'date[time]' => format_date($values[$langcode]['created'], 'custom', 'H:i:s'),
+ );
+ $this->drupalPostForm($path, $edit, $this->getFormSubmitAction($entity, $langcode), array('language' => $languages[$langcode]));
+ }
- // Login as translator again to ensure subsequent tests do not break.
- $this->drupalLogin($this->translator);
+ $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+ foreach ($this->langcodes as $langcode) {
+ $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
+ $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
+ $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
+ }
}
/**
diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index d812652..5bd5dcf 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -97,7 +97,8 @@ function _content_translation_form_language_content_settings_form_alter(array &$
if ($fields) {
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 (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable() && $storage_definitions[$field_name]->getProvider() != 'content_translation') {
$form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = array(
'#label' => $definition->getLabel(),
'#type' => 'checkbox',
@@ -338,4 +339,8 @@ function content_translation_form_language_content_settings_submit(array $form,
// Ensure entity and menu router information are correctly rebuilt.
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder_indicator')->setRebuildNeeded();
+ \Drupal::service('content_translation.updates_manager')->updateDefinitions($entity_types);
+
+ drupal_set_message(t('Settings successfully updated.'));
}
+
diff --git a/core/modules/content_translation/content_translation.install b/core/modules/content_translation/content_translation.install
index 34d2717..cd8f8f0 100644
--- a/core/modules/content_translation/content_translation.install
+++ b/core/modules/content_translation/content_translation.install
@@ -5,82 +5,10 @@
* Installation functions for Content Translation module.
*/
-use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
/**
- * Implements hook_schema().
- */
-function content_translation_schema() {
- $schema['content_translation'] = array(
- 'description' => 'Table to track content translations',
- 'fields' => array(
- 'entity_type' => array(
- 'type' => 'varchar',
- 'length' => EntityTypeInterface::ID_MAX_LENGTH,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The entity type this translation relates to',
- ),
- 'entity_id' => array(
- 'type' => 'varchar',
- 'length' => 128,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The entity id this translation relates to',
- ),
- 'langcode' => array(
- 'type' => 'varchar',
- 'length' => 32,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The target language for this translation.',
- ),
- 'source' => array(
- 'type' => 'varchar',
- 'length' => 32,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The source language from which this translation was created.',
- ),
- 'outdated' => array(
- 'description' => 'A boolean indicating whether this translation needs to be updated.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'uid' => array(
- 'description' => 'The author of this translation.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'status' => array(
- 'description' => 'Boolean indicating whether the translation is visible to non-translators.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 1,
- ),
- 'created' => array(
- 'description' => 'The Unix timestamp when the translation was created.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'changed' => array(
- 'description' => 'The Unix timestamp when the translation was most recently saved.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- ),
- ),
- 'primary key' => array('entity_type', 'entity_id', 'langcode'),
- );
- return $schema;
-}
-
-/**
* Implements hook_install().
*/
function content_translation_install() {
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index f1d01ef..5370b3c 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -6,9 +6,10 @@
*/
use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityFormInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
@@ -83,13 +84,23 @@ function content_translation_language_types_info_alter(array &$language_types) {
* "/taxonomy/term/{taxonomy_term}"), in which case at least the 'canonical' key
* in the 'links' entity info property must be defined.
*
- * Every entity type needs a translation controller to be translated. This can
+ * Every entity type needs a translation handler to be translated. This can
* be specified through the 'translation' key in the 'handlers' entity
* annotation property. If an entity type is translatable and no translation
* handler is defined, \Drupal\content_translation\ContentTranslationHandler
* will be assumed. Every translation handler must implement
* \Drupal\content_translation\ContentTranslationHandlerInterface.
*
+ * To implement its business logic the content translation UI relies on various
+ * metadata items describing the translation state. The default implementation
+ * is provided by \Drupal\content_translation\ContentTranslationMetadataWrapper,
+ * which is relying on one field for each metadata item (field definitions are
+ * provided by the translation handler). Entity types needing to customize this
+ * behavior can specify an alternative class through the
+ * 'content_translation_metadata' key in the entity type definition. Every
+ * content translation metadata wrapper needs to implement
+ * \Drupal\content_translation\ContentTranslationMetadataWrapperInterface.
+ *
* If the entity paths match the default pattern above and there is no need for
* an entity-specific translation handler, Content Translation will
* provide built-in support for the entity. However enabling translation for
@@ -105,6 +116,9 @@ function content_translation_entity_type_alter(array &$entity_types) {
if (!$entity_type->hasHandlerClass('translation')) {
$entity_type->setHandlerClass('translation', 'Drupal\content_translation\ContentTranslationHandler');
}
+ if (!$entity_type->get('content_translation_metadata')) {
+ $entity_type->set('content_translation_metadata', 'Drupal\content_translation\ContentTranslationMetadataWrapper');
+ }
$translation = $entity_type->get('translation');
if (!$translation || !isset($translation['content_translation'])) {
@@ -139,6 +153,29 @@ function content_translation_entity_bundle_info_alter(&$bundles) {
}
/**
+ * Implements hook_entity_base_field_info().
+ */
+function content_translation_entity_base_field_info(EntityTypeInterface $entity_type) {
+ /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
+ $manager = \Drupal::service('content_translation.manager');
+ $entity_type_id = $entity_type->id();
+ if ($manager->isSupported($entity_type_id)) {
+ $definitions = $manager->getTranslationHandler($entity_type_id)->getFieldDefinitions();
+ $installed_storage_definitions = \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($entity_type_id);
+ // We return metadata storage fields whenever content translation is enabled
+ // or it was enabled before, so that we keep translation metadata around
+ // when translation is disabled.
+ // @todo Re-evaluate this approach and consider removing field storage
+ // definitions and the related field data if the entity type has no bundle
+ // enabled for translation, once base field purging is supported.
+ // See https://www.drupal.org/node/2282119.
+ if ($manager->isEnabled($entity_type_id) || array_intersect_key($definitions, $installed_storage_definitions)) {
+ return $definitions;
+ }
+ }
+}
+
+/**
* Implements hook_field_info_alter().
*
* Content translation extends the @FieldType annotation with following key:
@@ -241,58 +278,17 @@ function content_translation_language_fallback_candidates_entity_view_alter(&$ca
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $context['data'];
$entity_type_id = $entity->getEntityTypeId();
- $entity_type = $entity->getEntityType();
- $permission = $entity_type->getPermissionGranularity() == 'bundle' ? $permission = "translate {$entity->bundle()} $entity_type_id" : "translate $entity_type_id";
- $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'])) {
- unset($candidates[$langcode]);
- }
- }
- }
-}
-
-/**
- * Implements hook_entity_storage_load().
- */
-function content_translation_entity_storage_load(array $entities, $entity_type) {
- $enabled_entities = array();
-
- if (\Drupal::service('content_translation.manager')->isEnabled($entity_type)) {
- foreach ($entities as $entity) {
- if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) {
- $enabled_entities[$entity->id()] = $entity;
- }
- }
- }
-
- if (!empty($enabled_entities)) {
- content_translation_load_translation_metadata($enabled_entities, $entity_type);
- }
-}
-
-/**
- * Loads translation data into the given entities.
- *
- * @param array $entities
- * The entities keyed by entity ID.
- * @param string $entity_type
- * The type of the entities.
- */
-function content_translation_load_translation_metadata(array $entities, $entity_type) {
- $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);
+ /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
+ $manager = \Drupal::service('content_translation.manager');
+ if ($manager->isEnabled($entity_type_id, $entity->bundle())) {
+ $entity_type = $entity->getEntityType();
+ $permission = $entity_type->getPermissionGranularity() == 'bundle' ? $permission = "translate {$entity->bundle()} $entity_type_id" : "translate $entity_type_id";
+ $current_user = \Drupal::currentuser();
+ if (!$current_user->hasPermission('translate any entity') && !$current_user->hasPermission($permission)) {
+ foreach ($entity->getTranslationLanguages() as $langcode => $language) {
+ $metadata = $manager->getTranslationMetadata($entity->getTranslation($langcode));
+ if (!$metadata->isPublished()) {
+ unset($candidates[$langcode]);
}
}
}
@@ -300,75 +296,6 @@ function content_translation_load_translation_metadata(array $entities, $entity_
}
/**
- * Implements hook_entity_insert().
- */
-function content_translation_entity_insert(EntityInterface $entity) {
- // Only do something if translation support for the given entity is enabled.
- if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
- return;
- }
-
- $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 += array(
- 'source' => '',
- 'uid' => \Drupal::currentUser()->id(),
- 'outdated' => FALSE,
- 'status' => TRUE,
- 'created' => REQUEST_TIME,
- 'changed' => REQUEST_TIME,
- );
-
- $translation['entity_type'] = $entity->getEntityTypeId();
- $translation['entity_id'] = $entity->id();
- $translation['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;
- }
- $query->values($values);
- }
-
- $query->execute();
-}
-
-/**
- * Implements hook_entity_delete().
- */
-function content_translation_entity_delete(EntityInterface $entity) {
- // Only do something if translation support for the given entity is enabled.
- if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
- return;
- }
-
- db_delete('content_translation')
- ->condition('entity_type', $entity->getEntityTypeId())
- ->condition('entity_id', $entity->id())
- ->execute();
-}
-
-/**
- * Implements hook_entity_update().
- */
-function content_translation_entity_update(EntityInterface $entity) {
- // Only do something if translation support for the given entity is enabled.
- if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
- return;
- }
-
- // Delete and create to ensure no stale value remains behind.
- content_translation_entity_delete($entity);
- content_translation_entity_insert($entity);
-}
-
-/**
* Implements hook_entity_extra_field_info().
*/
function content_translation_entity_extra_field_info() {
@@ -429,11 +356,18 @@ function content_translation_form_field_ui_field_edit_form_alter(array &$form, F
* Implements hook_entity_presave().
*/
function content_translation_entity_presave(EntityInterface $entity) {
- 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()->getId(), $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()->getId();
+ /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
+ $manager = \Drupal::service('content_translation.manager');
+ $source_langcode = !$entity->original->hasTranslation($langcode) ? $manager->getTranslationMetadata($entity)->getSource() : NULL;
+ \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $langcode, $source_langcode);
}
}
diff --git a/core/modules/content_translation/content_translation.services.yml b/core/modules/content_translation/content_translation.services.yml
index 8d4740f..a06deae 100644
--- a/core/modules/content_translation/content_translation.services.yml
+++ b/core/modules/content_translation/content_translation.services.yml
@@ -24,3 +24,9 @@ services:
content_translation.manager:
class: Drupal\content_translation\ContentTranslationManager
arguments: ['@entity.manager']
+
+ content_translation.updates_manager:
+ class: Drupal\content_translation\ContentTranslationUpdatesManager
+ arguments: ['@entity.manager', '@entity.definition_update_manager']
+ tags:
+ - { name: event_subscriber }
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index eb80547..2d6b19d 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -8,13 +8,16 @@
namespace Drupal\content_translation;
use Drupal\Core\Access\AccessResult;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityHandlerInterface;
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\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
+use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -23,6 +26,7 @@
* @ingroup entity_api
*/
class ContentTranslationHandler implements ContentTranslationHandlerInterface, EntityHandlerInterface {
+ use DependencySerializationTrait;
/**
* The type of the entity being translated.
@@ -46,14 +50,11 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
protected $languageManager;
/**
- * {@inheritdoc}
+ * The content translation manager.
+ *
+ * @var \Drupal\content_translation\ContentTranslationManagerInterface
*/
- public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
- return new static(
- $entity_type,
- $container->get('language_manager')
- );
- }
+ protected $manager;
/**
* Initializes an instance of the content translation controller.
@@ -62,11 +63,124 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
* The info array of the given entity type.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
+ * @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
+ * The content translation manager service.
*/
- public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager) {
+ public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->languageManager = $language_manager;
+ $this->manager = $manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+ return new static(
+ $entity_type,
+ $container->get('language_manager'),
+ $container->get('content_translation.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFieldDefinitions() {
+ $definitions = array();
+
+ $definitions['content_translation_source'] = BaseFieldDefinition::create('language')
+ ->setLabel(t('Translation source'))
+ ->setDescription(t('The source language from which this translation was created.'))
+ ->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED)
+ ->setRevisionable(TRUE)
+ ->setTranslatable(TRUE);
+
+ $definitions['content_translation_outdated'] = BaseFieldDefinition::create('boolean')
+ ->setLabel(t('Translation outdated'))
+ ->setDescription(t('A boolean indicating whether this translation needs to be updated.'))
+ ->setDefaultValue(FALSE)
+ ->setRevisionable(TRUE)
+ ->setTranslatable(TRUE);
+
+ if (!$this->hasAuthor()) {
+ $definitions['content_translation_uid'] = BaseFieldDefinition::create('entity_reference')
+ ->setLabel(t('Translation author'))
+ ->setDescription(t('The author of this translation.'))
+ ->setSetting('target_type', 'user')
+ ->setSetting('handler', 'default')
+ ->setRevisionable(TRUE)
+ ->setTranslatable(TRUE);
+ }
+
+ if (!$this->hasPublishedStatus()) {
+ $definitions['content_translation_status'] = BaseFieldDefinition::create('boolean')
+ ->setLabel(t('Translation status'))
+ ->setDescription(t('A boolean indicating whether the translation is visible to non-translators.'))
+ ->setDefaultValue(TRUE)
+ ->setRevisionable(TRUE)
+ ->setTranslatable(TRUE);
+ }
+
+ if (!$this->hasCreatedTime()) {
+ $definitions['content_translation_created'] = BaseFieldDefinition::create('created')
+ ->setLabel(t('Translation created time'))
+ ->setDescription(t('The Unix timestamp when the translation was created.'))
+ ->setRevisionable(TRUE)
+ ->setTranslatable(TRUE);
+ }
+
+ if (!$this->hasChangedTime()) {
+ $definitions['content_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()))
+ ->setRevisionable(TRUE)
+ ->setTranslatable(TRUE);
+ }
+
+ return $definitions;
+ }
+
+ /**
+ * Checks whether the entity type supports author natively.
+ *
+ * @return bool
+ * TRUE if metadata is natively supported, FALSE otherwise.
+ */
+ protected function hasAuthor() {
+ return is_subclass_of($this->entityType->getClass(), '\Drupal\user\EntityOwnerInterface');
+ }
+
+ /**
+ * Checks whether the entity type supports published status natively.
+ *
+ * @return bool
+ * TRUE if metadata is natively supported, FALSE otherwise.
+ */
+ protected function hasPublishedStatus() {
+ return array_key_exists('status', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id()));
+ }
+
+ /**
+ * Checks whether the entity type supports modification time natively.
+ *
+ * @return bool
+ * TRUE if metadata is natively supported, FALSE otherwise.
+ */
+ protected function hasChangedTime() {
+ return is_subclass_of($this->entityType->getClass(), '\Drupal\Core\Entity\EntityChangedInterface');
+ }
+
+ /**
+ * Checks whether the entity type supports creation time natively.
+ *
+ * @return bool
+ * TRUE if metadata is natively supported, FALSE otherwise.
+ */
+ protected function hasCreatedTime() {
+ return array_key_exists('created', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id()));
}
/**
@@ -74,9 +188,9 @@ public function __construct(EntityTypeInterface $entity_type, LanguageManagerInt
*/
public function retranslate(EntityInterface $entity, $langcode = NULL) {
$updated_langcode = !empty($langcode) ? $langcode : $entity->language()->getId();
- $translations = $entity->getTranslationLanguages();
- foreach ($translations as $langcode => $language) {
- $entity->translation[$langcode]['outdated'] = $langcode != $updated_langcode;
+ foreach ($entity->getTranslationLanguages() as $langcode => $language) {
+ $this->manager->getTranslationMetadata($entity->getTranslation($langcode))
+ ->setOutdated($langcode != $updated_langcode);
}
}
@@ -231,20 +345,16 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
);
// A new translation is enabled by default.
- $status = $new_translation || $entity->translation[$form_langcode]['status'];
+ $metadata = $this->manager->getTranslationMetadata($entity);
+ $status = $new_translation || $metadata->isPublished();
// If there is only one published translation we cannot unpublish it,
// since there would be nothing left to display.
$enabled = TRUE;
if ($status) {
- // A new translation is not available in the translation metadata, hence
- // it should count as one more.
- $published = $new_translation;
- // When creating a brand new translation, $entity->translation is not
- // set.
- if (!$new_translation) {
- foreach ($entity->translation as $translation) {
- $published += $translation['status'];
- }
+ $published = 0;
+ foreach ($entity->getTranslationLanguages() as $langcode => $language) {
+ $published += $this->manager->getTranslationMetadata($entity->getTranslation($langcode))
+ ->isPublished();
}
$enabled = $published > 1;
}
@@ -260,7 +370,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
'#disabled' => !$enabled,
);
- $translate = !$new_translation && $entity->translation[$form_langcode]['outdated'];
+ $translate = !$new_translation && $metadata->isOutdated();
if (!$translate) {
$form['content_translation']['retranslate'] = array(
'#type' => 'checkbox',
@@ -284,8 +394,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 (($account = $metadata->getAuthor()) && $account->id()) {
+ $name = $account->getUsername();
}
$form['content_translation']['name'] = array(
'#type' => 'textfield',
@@ -296,13 +406,13 @@ 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 : $metadata->getCreatedTime();
$form['content_translation']['created'] = array(
'#type' => 'textfield',
'#title' => t('Authored on'),
'#maxlength' => 25,
- '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => format_date($date, 'custom', 'Y-m-d H:i:s O'), '%timezone' => format_date($date, 'custom', 'O'))),
- '#default_value' => $new_translation ? '' : format_date($date, 'custom', 'Y-m-d H:i:s O'),
+ '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => format_date(REQUEST_TIME, 'custom', 'Y-m-d H:i:s O'), '%timezone' => format_date(REQUEST_TIME, 'custom', 'O'))),
+ '#default_value' => $new_translation || !$date ? '' : format_date($date, 'custom', 'Y-m-d H:i:s O'),
);
if (isset($language_widget)) {
@@ -418,35 +528,27 @@ 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);
+ $values = &$form_state->getValue('content_translation', array());
- if (!isset($entity->translation[$form_langcode])) {
- $entity->translation[$form_langcode] = array();
+ if ($values['name'] == \Drupal::config('user.settings')->get('anonymous')) {
+ $values['name'] = '';
}
- $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;
+ $metadata = $this->manager->getTranslationMetadata($entity);
+ $metadata->setAuthor(!empty($values['name']) && ($account = user_load_by_name($values['name'])) ? $account : User::load(0));
+ $metadata->setPublished(!empty($values['status']));
+ $metadata->setCreatedTime(!empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME);
+ $metadata->setChangedTime(REQUEST_TIME);
$source_langcode = $this->getSourceLangcode($form_state);
if ($source_langcode) {
- $translation['source'] = $source_langcode;
+ $metadata->setSource($source_langcode);
}
- $translation['outdated'] = !empty($values['outdated']);
+ $metadata->setOutdated(!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/ContentTranslationHandlerInterface.php b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
index 5ab8858..12f59c1 100644
--- a/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
+++ b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
@@ -19,6 +19,13 @@
interface ContentTranslationHandlerInterface {
/**
+ * Returns a set of field definitions to be used to store metadata items.
+ *
+ * @return \Drupal\Core\Field\FieldDefinitionInterface[]
+ */
+ public function getFieldDefinitions();
+
+ /**
* Checks if the user can perform the given operation on translations of the
* wrapped entity.
*
diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php
index cff64c3..a4d529a 100644
--- a/core/modules/content_translation/src/ContentTranslationManager.php
+++ b/core/modules/content_translation/src/ContentTranslationManager.php
@@ -7,7 +7,9 @@
namespace Drupal\content_translation;
+use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\language\Entity\ContentLanguageSettings;
/**
@@ -35,6 +37,23 @@ public function __construct(EntityManagerInterface $manager) {
/**
* {@inheritdoc}
*/
+ function getTranslationHandler($entity_type_id) {
+ return $this->entityManager->getHandler($entity_type_id, 'translation');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTranslationMetadata(EntityInterface $translation) {
+ // We need a new instance of the metadata handler wrapping each translation.
+ $entity_type = $translation->getEntityType();
+ $class = $entity_type->get('content_translation_metadata');
+ return new $class($translation, $this->getTranslationHandler($entity_type->id()));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function isSupported($entity_type_id) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
return $entity_type->isTranslatable() && $entity_type->hasLinkTemplate('drupal:content-translation-overview');
diff --git a/core/modules/content_translation/src/ContentTranslationManagerInterface.php b/core/modules/content_translation/src/ContentTranslationManagerInterface.php
index e3ae29f..5afa2de 100644
--- a/core/modules/content_translation/src/ContentTranslationManagerInterface.php
+++ b/core/modules/content_translation/src/ContentTranslationManagerInterface.php
@@ -7,6 +7,9 @@
namespace Drupal\content_translation;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+
/**
* Provides an interface for common functionality for content translation.
*/
@@ -32,6 +35,28 @@ public function getSupportedEntityTypes();
public function isSupported($entity_type_id);
/**
+ * Returns an instance of the Content translation handler.
+ *
+ * @param string $entity_type_id
+ * The type of the entity being translated.
+ *
+ * @return \Drupal\content_translation\ContentTranslationHandlerInterface
+ * An instance of the content translation handler.
+ */
+ public function getTranslationHandler($entity_type_id);
+
+ /**
+ * Returns an instance of the Content translation metadata.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $translation
+ * The entity translation whose metadata needs to be retrieved.
+ *
+ * @return \Drupal\content_translation\ContentTranslationMetadataWrapperInterface
+ * An instance of the content translation metadata.
+ */
+ public function getTranslationMetadata(EntityInterface $translation);
+
+ /**
* Sets the value for translatability of the given entity type bundle.
*
* @param string $entity_type_id
diff --git a/core/modules/content_translation/src/ContentTranslationMetadataWrapper.php b/core/modules/content_translation/src/ContentTranslationMetadataWrapper.php
new file mode 100644
index 0000000..4b4d43e
--- /dev/null
+++ b/core/modules/content_translation/src/ContentTranslationMetadataWrapper.php
@@ -0,0 +1,145 @@
+translation = $translation;
+ $this->handler = $handler;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSource() {
+ return $this->translation->get('content_translation_source')->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setSource($source) {
+ $this->translation->set('content_translation_source', $source);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isOutdated() {
+ return (bool) $this->translation->get('content_translation_outdated')->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOutdated($outdated) {
+ $this->translation->set('content_translation_outdated', $outdated);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAuthor() {
+ return $this->translation->hasField('content_translation_uid') ? $this->translation->get('content_translation_uid')->entity : $this->translation->getOwner();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setAuthor(UserInterface $account) {
+ if ($this->translation->hasField('content_translation_uid')) {
+ $this->translation->set('content_translation_uid', $account->id());
+ }
+ else {
+ $this->translation->setOwner($account);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isPublished() {
+ $field_name = $this->translation->hasField('content_translation_status') ? 'content_translation_status' : 'status';
+ return (bool) $this->translation->get($field_name)->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setPublished($published) {
+ $field_name = $this->translation->hasField('content_translation_status') ? 'content_translation_status' : 'status';
+ $this->translation->set($field_name, $published);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCreatedTime() {
+ $field_name = $this->translation->hasField('content_translation_created') ? 'content_translation_created' : 'created';
+ return $this->translation->get($field_name)->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setCreatedTime($timestamp) {
+ $field_name = $this->translation->hasField('content_translation_created') ? 'content_translation_created' : 'created';
+ $this->translation->set($field_name, $timestamp);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getChangedTime() {
+ return $this->translation->hasField('content_translation_changed') ? $this->translation->get('content_translation_changed')->value : $this->translation->getChangedTime();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setChangedTime($timestamp) {
+ $field_name = $this->translation->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
+ $this->translation->set($field_name, $timestamp);
+ return $this;
+ }
+
+}
diff --git a/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
new file mode 100644
index 0000000..4a0a65b
--- /dev/null
+++ b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
@@ -0,0 +1,122 @@
+entityManager = $entity_manager;
+ $this->updateManager = $update_manager;
+ }
+
+ /**
+ * Executes field storage definition updates if needed.
+ *
+ * @param array $entity_types
+ * A list of entity type definitions to be processed.
+ */
+ public function updateDefinitions(array $entity_types) {
+ // Handle field storage definition creation, if needed.
+ // @todo Generalize this code in https://www.drupal.org/node/2346013.
+ // @todo Handle initial values in https://www.drupal.org/node/2346019.
+ if ($this->updateManager->needsUpdates()) {
+ foreach ($entity_types as $entity_type_id => $entity_type) {
+ $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
+ $installed_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
+ foreach (array_diff_key($storage_definitions, $installed_storage_definitions) as $storage_definition) {
+ /** @var $storage_definition \Drupal\Core\Field\FieldStorageDefinitionInterface */
+ if ($storage_definition->getProvider() == 'content_translation') {
+ $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Listener for the ConfigImporter import event.
+ */
+ public function onConfigImporterImport() {
+ $entity_types = array_filter(\Drupal::entityManager()->getDefinitions(), function (EntityTypeInterface $entity_type) {
+ return $entity_type->isTranslatable();
+ });
+ $this->updateDefinitions($entity_types);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ $events[ConfigEvents::IMPORT][] = ['onConfigImporterImport', 60];
+ return $events;
+ }
+
+}
diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php
index dc74210..d00dee2 100644
--- a/core/modules/content_translation/src/Controller/ContentTranslationController.php
+++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
@@ -7,11 +7,13 @@
namespace Drupal\content_translation\Controller;
+use Drupal\content_translation\ContentTranslationManagerInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for entity translation controllers.
@@ -19,6 +21,30 @@
class ContentTranslationController extends ControllerBase {
/**
+ * The content translation manager.
+ *
+ * @var \Drupal\content_translation\ContentTranslationManagerInterface
+ */
+ protected $manager;
+
+ /**
+ * Initializes a content translation controller.
+ *
+ * @param \Drupal\content_translation\ContentTranslationManagerInterface
+ * A content translation manager instance.
+ */
+ public function __construct(ContentTranslationManagerInterface $manager) {
+ $this->manager = $manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static($container->get('content_translation.manager'));
+ }
+
+ /**
* Populates target values with the source values.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
@@ -48,6 +74,7 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
$entity = $route_match->getParameter($entity_type_id);
$account = $this->currentUser();
$handler = $this->entityManager()->getHandler($entity_type_id, 'translation');
+ $manager = $this->manager;
$languages = $this->languageManager()->getLanguages();
$original = $entity->getUntranslated()->language()->getId();
@@ -68,8 +95,9 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
}
// 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, $manager) {
+ $source = $manager->getTranslationMetadata($entity->getTranslation($langcode))->getSource();
+ return $source != $original && $source != LanguageInterface::LANGCODE_NOT_SPECIFIED;
});
$show_source_column = !empty($additional_source_langcodes);
@@ -118,7 +146,9 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
$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);
+ $metadata = $manager->getTranslationMetadata($translation);
+ $source = $metadata->getSource() ?: LanguageInterface::LANGCODE_NOT_SPECIFIED;
$is_original = $langcode == $original;
$label = $entity->getTranslation($langcode)->label();
$link = isset($links->links[$langcode]['url']) ? $links->links[$langcode] : array('url' => $entity->urlInfo());
@@ -145,13 +175,12 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
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' => $metadata->isPublished(),
+ 'outdated' => $metadata->isOutdated(),
),
));
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationConfigImportTest.php b/core/modules/content_translation/src/Tests/ContentTranslationConfigImportTest.php
new file mode 100644
index 0000000..c06fdb2
--- /dev/null
+++ b/core/modules/content_translation/src/Tests/ContentTranslationConfigImportTest.php
@@ -0,0 +1,111 @@
+installEntitySchema('entity_test_mul');
+ $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
+
+ // Set up the ConfigImporter object for testing.
+ $storage_comparer = new StorageComparer(
+ $this->container->get('config.storage.staging'),
+ $this->container->get('config.storage'),
+ $this->container->get('config.manager')
+ );
+ $this->configImporter = new ConfigImporter(
+ $storage_comparer->createChangelist(),
+ $this->container->get('event_dispatcher'),
+ $this->container->get('config.manager'),
+ $this->container->get('lock'),
+ $this->container->get('config.typed'),
+ $this->container->get('module_handler'),
+ $this->container->get('module_installer'),
+ $this->container->get('theme_handler'),
+ $this->container->get('string_translation')
+ );
+ }
+
+ /**
+ * Tests config import updates.
+ */
+ function testConfigImportUpdates() {
+ $entity_type_id = 'entity_test_mul';
+ $config_id = $entity_type_id . '.' . $entity_type_id;
+ $config_name = 'language.content_settings.' . $config_id;
+ $storage = $this->container->get('config.storage');
+ $staging = $this->container->get('config.storage.staging');
+
+ // Verify the configuration to create does not exist yet.
+ $this->assertIdentical($storage->exists($config_name), FALSE, $config_name . ' not found.');
+
+ // Create new config entity.
+ $data = array(
+ 'uuid' => 'a019d89b-c4d9-4ed4-b859-894e4e2e93cf',
+ 'langcode' => 'en',
+ 'status' => TRUE,
+ 'dependencies' => array(
+ 'module' => array('content_translation')
+ ),
+ 'id' => $config_id,
+ 'target_entity_type_id' => 'entity_test_mul',
+ 'target_bundle' => 'entity_test_mul',
+ 'default_langcode' => 'site_default',
+ 'language_alterable' => FALSE,
+ 'third_party_settings' => array(
+ 'content_translation' => array('enabled' => TRUE),
+ ),
+ );
+ $staging->write($config_name, $data);
+ $this->assertIdentical($staging->exists($config_name), TRUE, $config_name . ' found.');
+
+ // Import.
+ $this->configImporter->reset()->import();
+
+ // Verify the values appeared.
+ $config = $this->config($config_name);
+ $this->assertIdentical($config->get('id'), $config_id);
+
+ // Verify that updates were performed.
+ $entity_type = $this->container->get('entity.manager')->getDefinition($entity_type_id);
+ $table = $entity_type->getDataTable();
+ $db_schema = $this->container->get('database')->schema();
+ $result = $db_schema->fieldExists($table, 'content_translation_source') && $db_schema->fieldExists($table, 'content_translation_outdated');
+ $this->assertTrue($result, 'Content translation updates were successfully performed during config import.');
+ }
+
+}
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php b/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
index 1efe28a..17d40cc 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
@@ -116,10 +116,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(),
@@ -187,6 +183,7 @@ function testImageFieldSync() {
// Perform synchronization: the translation language is used as source,
// while the default language is used as target.
+ $this->manager->getTranslationMetadata($translation)->setSource($default_langcode);
$entity = $this->saveEntity($translation);
$translation = $entity->getTranslation($langcode);
@@ -216,8 +213,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/ContentTranslationTestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationTestBase.php
index 9077f26..a9d2c5b 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationTestBase.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationTestBase.php
@@ -79,6 +79,11 @@
*/
protected $controller;
+ /**
+ * @var \Drupal\content_translation\ContentTranslationManagerInterface
+ */
+ protected $manager;
+
protected function setUp() {
parent::setUp();
@@ -88,7 +93,8 @@ protected function setUp() {
$this->setupUsers();
$this->setupTestFields();
- $this->controller = \Drupal::entityManager()->getHandler($this->entityTypeId, 'translation');
+ $this->manager = $this->container->get('content_translation.manager');
+ $this->controller = $this->manager->getTranslationHandler($this->entityTypeId);
// Rebuild the container so that the new languages are picked up by services
// that hold a list of languages.
@@ -167,6 +173,7 @@ protected function enableTranslation() {
drupal_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
+ \Drupal::service('entity.definition_update_manager')->applyUpdates();
}
/**
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
index 0581893..bf8a026 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
@@ -161,7 +161,7 @@ protected function doTestOutdatedStatus() {
$this->drupalGet($path, $options);
$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($this->manager->getTranslationMetadata($entity->getTranslation($added_langcode))->isOutdated(), 'The "outdated" status has been correctly stored.');
}
}
}
@@ -179,7 +179,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($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
}
}
@@ -213,8 +213,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.');
+ $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
+ $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
+ $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
}
// Try to post non valid values and check that they are rejected.
@@ -226,8 +227,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.');
+ $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
+ $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.');
+ $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.');
}
/**
diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php
index a1d7102..c52cea7 100644
--- a/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php
+++ b/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php
@@ -171,6 +171,7 @@ protected function enableTranslation() {
drupal_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
+ \Drupal::service('entity.definition_update_manager')->applyUpdates();
}
/**
diff --git a/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php b/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php
index 1b5bef2..4841d02 100644
--- a/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php
+++ b/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php
@@ -68,6 +68,7 @@ protected function setUp() {
"settings[node][page][fields][$this->fieldName]" => 1,
);
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
+ \Drupal::entityManager()->clearCachedDefinitions();
}
/**
diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php
index 153e87c..ed03f87 100644
--- a/core/modules/node/src/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/src/Tests/NodeTranslationUITest.php
@@ -111,9 +111,8 @@ 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.');
$translation = $entity->getTranslation($langcode);
- $this->assertEqual($status, $translation->isPublished(), 'The status of the translation has been correctly saved.');
+ $this->assertEqual($status, $this->manager->getTranslationMetadata($translation)->isPublished(), 'The translation has been correctly unpublished.');
}
}
}
@@ -148,11 +147,10 @@ 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->getOwnerId(), $values[$langcode]['uid'], 'Author of translation correctly stored.');
- $this->assertEqual($translation->getCreatedTime(), $values[$langcode]['created'], 'Date of Translation correctly stored.');
+ $metadata = $this->manager->getTranslationMetadata($translation);
+ $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
+ $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
$this->assertEqual($translation->isSticky(), $values[$langcode]['sticky'], 'Sticky of Translation correctly stored.');
$this->assertEqual($translation->isPromoted(), $values[$langcode]['promote'], 'Promoted of Translation correctly stored.');
}
@@ -199,7 +197,7 @@ public function testDisabledBundle() {
));
// Make sure that nothing was inserted into the {content_translation} table.
- $rows = db_query('SELECT * FROM {content_translation}')->fetchAll();
+ $rows = db_query('SELECT nid, count(nid) AS count FROM {node_field_data} WHERE type <> :type GROUP BY nid HAVING count >= 2', array(':type' => $this->bundle))->fetchAll();
$this->assertEqual(0, count($rows));
// Ensure the translation tab is not accessible.
diff --git a/core/modules/path/src/Tests/PathLanguageTest.php b/core/modules/path/src/Tests/PathLanguageTest.php
index 01c7f70..93cd776 100644
--- a/core/modules/path/src/Tests/PathLanguageTest.php
+++ b/core/modules/path/src/Tests/PathLanguageTest.php
@@ -66,6 +66,7 @@ protected function setUp() {
'settings[node][page][settings][language][language_alterable]' => 1,
);
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
+ \Drupal::entityManager()->clearCachedDefinitions();
$definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'page');
$this->assertTrue($definitions['path']->isTranslatable(), 'Node path is translatable.');
diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php b/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php
index e813ced..9b4995a 100644
--- a/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php
+++ b/core/modules/taxonomy/src/Tests/TaxonomyTranslationTestTrait.php
@@ -68,6 +68,7 @@ protected function enableTranslation() {
drupal_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
+ \Drupal::service('entity.definition_update_manager')->applyUpdates();
}
/**
@@ -79,7 +80,6 @@ protected function setUpTermReferenceField() {
'entity_type' => 'node',
'type' => 'taxonomy_term_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
- 'translatable' => FALSE,
'settings' => array(
'allowed_values' => array(
array(
@@ -94,6 +94,7 @@ protected function setUpTermReferenceField() {
'field_name' => $this->termFieldName,
'bundle' => 'article',
'entity_type' => 'node',
+ 'translatable' => FALSE,
));
$field->save();
entity_get_form_display('node', 'article', 'default')
diff --git a/core/modules/taxonomy/src/Tests/TermTranslationUITest.php b/core/modules/taxonomy/src/Tests/TermTranslationUITest.php
index 5c0e9d5..07400a5 100644
--- a/core/modules/taxonomy/src/Tests/TermTranslationUITest.php
+++ b/core/modules/taxonomy/src/Tests/TermTranslationUITest.php
@@ -95,9 +95,9 @@ public function testTranslationUI() {
// Make sure that no row was inserted for taxonomy vocabularies which do
// not have translations enabled.
- $rows = db_query('SELECT * FROM {content_translation}')->fetchAll();
+ $rows = db_query('SELECT tid, count(tid) AS count FROM {taxonomy_term_field_data} WHERE vid <> :vid GROUP BY tid', array(':vid' => $this->bundle))->fetchAll();
foreach ($rows as $row) {
- $this->assertEqual('taxonomy_term', $row->entity_type, 'Row contains a taxonomy term.');
+ $this->assertTrue($row->count < 2, 'Term does not have translations.');
}
}
diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php
index d6bb322..7b37eb4 100644
--- a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php
+++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php
@@ -119,6 +119,7 @@ public function testTaxonomyTermView() {
drupal_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
+ \Drupal::service('entity.definition_update_manager')->applyUpdates();
$edit['title[0][value]'] = $translated_title = $this->randomMachineName();
@@ -137,6 +138,7 @@ public function testTaxonomyTermView() {
// Uninstall language module and ensure that the language is not part of the
// query anymore.
// @see \Drupal\views\Plugin\views\filter\LanguageFilter::query()
+ $node->delete();
\Drupal::service('module_installer')->uninstall(['content_translation', 'language']);
$view = Views::getView('taxonomy_term');
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index 7c8af82..b31d426 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -55,7 +55,7 @@
* "edit-form" = "/user/{user}/edit",
* "cancel-form" = "/user/{user}/cancel",
* },
- * field_ui_base_route = "entity.user.admin_form",
+ * field_ui_base_route = "entity.user.admin_form"
* )
*/
class User extends ContentEntityBase implements UserInterface {
diff --git a/core/modules/user/src/ProfileTranslationHandler.php b/core/modules/user/src/ProfileTranslationHandler.php
index cd8befc..b24e634 100644
--- a/core/modules/user/src/ProfileTranslationHandler.php
+++ b/core/modules/user/src/ProfileTranslationHandler.php
@@ -19,6 +19,22 @@ class ProfileTranslationHandler extends ContentTranslationHandler {
/**
* {@inheritdoc}
*/
+ protected function hasPublishedStatus() {
+ // User status has nothing to do with translations visibility.
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function hasCreatedTime() {
+ // User creation date has nothing to do with translation creation date.
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
parent::entityFormAlter($form, $form_state, $entity);
$form['actions']['submit']['#submit'][] = array($this, 'entityFormSave');
@@ -31,7 +47,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
*
* @see \Drupal\Core\Entity\EntityForm::build().
*/
- function entityFormSave(array $form, FormStateInterface $form_state) {
+ public function entityFormSave(array $form, FormStateInterface $form_state) {
if ($this->getSourceLangcode($form_state)) {
$entity = $form_state->getFormObject()->getEntity();
// We need a redirect here, otherwise we would get an access denied page
@@ -40,4 +56,5 @@ function entityFormSave(array $form, FormStateInterface $form_state) {
$form_state->setRedirectUrl($entity->urlInfo());
}
}
+
}
diff --git a/core/modules/views/src/Tests/SearchMultilingualTest.php b/core/modules/views/src/Tests/SearchMultilingualTest.php
index 4a6d4c7..4fb6b54 100644
--- a/core/modules/views/src/Tests/SearchMultilingualTest.php
+++ b/core/modules/views/src/Tests/SearchMultilingualTest.php
@@ -55,6 +55,7 @@ public function testMultilingualSearchFilter() {
'settings[node][' . $type->id() . '][fields][body]' => TRUE,
);
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
+ \Drupal::entityManager()->clearCachedDefinitions();
// Add a node in English, with title "sandwich".
$values = array(