diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php index 4f92c8a..336a43f 100644 --- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php +++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php @@ -107,13 +107,6 @@ public function getChangeSummary() { */ public function applyUpdates() { foreach ($this->getChangeList() as $entity_type_id => $change_list) { - // Process entity type definition changes. - if (!empty($change_list['entity_type']) && $change_list['entity_type'] == static::DEFINITION_UPDATED) { - $entity_type = $this->entityManager->getDefinition($entity_type_id); - $original = $this->entityManager->getLastInstalledDefinition($entity_type_id); - $this->entityManager->onEntityTypeUpdate($entity_type, $original); - } - // Process field storage definition changes. if (!empty($change_list['field_storage_definitions'])) { $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); @@ -135,6 +128,16 @@ public function applyUpdates() { } } } + + // Process entity type definition changes after storage definitions ones, + // as in some conditions a change in the field storage definition can also + // be detected as an entity type definition change. This avoids the change + // to be applied twice (resulting in an unrecoverable error). + if (!empty($change_list['entity_type']) && $change_list['entity_type'] == static::DEFINITION_UPDATED) { + $entity_type = $this->entityManager->getDefinition($entity_type_id); + $original = $this->entityManager->getLastInstalledDefinition($entity_type_id); + $this->entityManager->onEntityTypeUpdate($entity_type, $original); + } } } diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php index dac6584..99bbbd6 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php @@ -1100,7 +1100,13 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names); if (!$only_save) { foreach ($schema[$table_name]['fields'] as $name => $specifier) { - $schema_handler->addField($table_name, $name, $specifier); + try { + $schema_handler->addField($table_name, $name, $specifier); + } + catch (\Exception $e) { + // FIXME + watchdog_exception('php', $e); + } } if (!empty($schema[$table_name]['indexes'])) { foreach ($schema[$table_name]['indexes'] as $name => $specifier) { diff --git a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php index aec6f69..a97348d 100644 --- a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php +++ b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php @@ -143,9 +143,8 @@ public function testDisabledBundle() { // Make sure that only a single row was inserted into the // {content_translation} table. - $rows = db_query('SELECT * FROM {content_translation}')->fetchAll(); + $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..826db8e 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,39 @@ 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 node 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(); + if ($translation['name'] == \Drupal::config('user.settings')->get('anonymous')) { + $translation['name'] = ''; + } + } + 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 14ea0db..e914801 100644 --- a/core/modules/comment/src/Tests/CommentTranslationUITest.php +++ b/core/modules/comment/src/Tests/CommentTranslationUITest.php @@ -112,31 +112,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 92b75bf..3849dc7 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', @@ -336,8 +337,25 @@ function content_translation_form_language_content_settings_submit(array $form, } // Ensure entity and menu router information are correctly rebuilt. - \Drupal::entityManager()->clearCachedDefinitions(); + $entity_manager = \Drupal::entityManager(); + $entity_manager->clearCachedDefinitions(); \Drupal::service('router.builder')->setRebuildNeeded(); + // 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 (\Drupal::service('entity.definition_update_manager')->needsUpdates()) { + foreach ($entity_types as $entity_type_id => $entity_type) { + $storage_definitions = $entity_manager->getFieldStorageDefinitions($entity_type_id); + $installed_storage_definitions = $entity_manager->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') { + $entity_manager->onFieldStorageDefinitionCreate($storage_definition); + } + } + } + } + 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 49e4b67..d7a7f04 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -9,6 +9,7 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityFormInterface; 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; @@ -91,6 +92,16 @@ function content_translation_language_types_info_alter(array &$language_types) { * 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\ContentTranslationMetadata, which + * defines one field for each metadata item. Entity types needing to customize + * this behavior can specify an alternative class through the + * 'content_translation_metadata' key in the 'handlers' entity type definition + * property. See \Drupal\node\NodeTranslationMetadata for an example. Every + * content translation metadata handler needs to implement + * \Drupal\content_translation\ContentTranslationMetadataInterface. + * * 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 @@ -106,6 +117,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->hasHandlerClass('content_translation_metadata')) { + $entity_type->setHandlerClass('content_translation_metadata', 'Drupal\content_translation\ContentTranslationMetadata'); + } $translation = $entity_type->get('translation'); if (!$translation || !isset($translation['content_translation'])) { @@ -141,6 +155,25 @@ 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->getEntityTypeTranslationMetadata($entity_type)->getFieldDefinitions(); + $installed_storage_definitions = \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($entity_type_id); + // @todo Consider removing field storage definitions if the entity type has + // no bundle enabled for translation once base field purging is supported. + // See https://www.drupal.org/node/2282119. + if (content_translation_enabled($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: @@ -332,10 +365,7 @@ function content_translation_enabled($entity_type, $bundle = NULL) { * @todo Move to \Drupal\content_translation\ContentTranslationManager. */ function content_translation_controller($entity_type_id) { - $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id); - // @todo Throw an exception if the key is missing. - $class = $entity_type->getHandlerClass('translation'); - return new $class($entity_type); + return \Drupal::entityManager()->getHandler($entity_type_id, 'translation'); } /** @@ -400,58 +430,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 (content_translation_enabled($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); + if (content_translation_enabled($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)) { + /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */ + $manager = \Drupal::service('content_translation.manager'); + foreach ($entity->getTranslationLanguages() as $langcode => $language) { + $metadata = $manager->getTranslationMetadata($entity->getTranslation($langcode)); + if (!$metadata->isPublished()) { + unset($candidates[$langcode]); } } } @@ -459,75 +448,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() { @@ -588,11 +508,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()->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; + /** @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/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php index b619036..7c797c5 100644 --- a/core/modules/content_translation/src/ContentTranslationHandler.php +++ b/core/modules/content_translation/src/ContentTranslationHandler.php @@ -13,6 +13,9 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Render\Element; +use Drupal\user\Entity\User; +use Drupal\user\EntityOwnerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Base class for content translation handlers. @@ -36,14 +39,49 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface { protected $entityType; /** + * A content translation metadata handler instance. + * + * @var \Drupal\content_translation\ContentTranslationMetadataInterface + */ + protected $metadata; + + /** * Initializes an instance of the content translation controller. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * The info array of the given entity type. + * @param \Drupal\content_translation\ContentTranslationManagerInterface $manager + * The content translation manager service. */ - public function __construct(EntityTypeInterface $entity_type) { + public function __construct(EntityTypeInterface $entity_type, ContentTranslationManagerInterface $manager) { $this->entityTypeId = $entity_type->id(); $this->entityType = $entity_type; + $this->metadata = $manager->getEntityTypeTranslationMetadata($entity_type); + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static($entity_type, $container->get('content_translation.manager')); + } + + /** + * Returns the translation metadata for the specified translation. + * + * @param EntityInterface $translation + * The entity translation. + * + * @return ContentTranslationMetadataInterface + * The translation metadata. + */ + protected function getMetadata(EntityInterface $translation) { + // We need this metadata factory method to avoid storing the translation + // manager in the local state, as this would cause the DIC to be serialized + // as part of the entity manager state. + $metadata = clone $this->metadata; + $metadata->setTranslation($translation); + return $metadata; } /** @@ -51,9 +89,9 @@ public function __construct(EntityTypeInterface $entity_type) { */ 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; + foreach ($entity->getTranslationLanguages() as $langcode => $language) { + $this->getMetadata($entity->getTranslation($langcode)) + ->setOutdated($langcode != $updated_langcode); } } @@ -197,20 +235,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->getMetadata($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->getMetadata($entity->getTranslation($langcode)) + ->isPublished(); } $enabled = $published > 1; } @@ -226,7 +260,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', @@ -249,8 +283,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 ($metadata->getAuthor()->id()) { + $name = $metadata->getAuthor()->getUsername(); } $form['content_translation']['name'] = array( '#type' => 'textfield', @@ -261,7 +295,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 : $metadata->getCreatedTime(); $form['content_translation']['created'] = array( '#type' => 'textfield', '#title' => t('Authored on'), @@ -383,35 +417,23 @@ 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; + $metadata = $this->getMetadata($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..e42dbdf 100644 --- a/core/modules/content_translation/src/ContentTranslationHandlerInterface.php +++ b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php @@ -7,6 +7,7 @@ namespace Drupal\content_translation; +use Drupal\Core\Entity\EntityHandlerInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; @@ -16,7 +17,7 @@ * Defines a set of methods to allow any entity to be processed by the entity * translation UI. */ -interface ContentTranslationHandlerInterface { +interface ContentTranslationHandlerInterface extends EntityHandlerInterface { /** * Checks if the user can perform the given operation on translations of the diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php index 817d3f8..b5d108c 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; /** * Provides common functionality for content translation. @@ -34,6 +36,23 @@ public function __construct(EntityManagerInterface $manager) { /** * {@inheritdoc} */ + public function getEntityTypeTranslationMetadata(EntityTypeInterface $entity_type) { + return $this->entityManager->getHandler($entity_type->id(), 'content_translation_metadata'); + } + + /** + * {@inheritdoc} + */ + public function getTranslationMetadata(EntityInterface $translation) { + // We need a new instance of the metadata handler wrapping each translation. + $metadata = clone $this->getEntityTypeTranslationMetadata($translation->getEntityType()); + $metadata->setTranslation($translation); + return $metadata; + } + + /** + * {@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 8591138..a2951ed 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. */ @@ -31,4 +34,26 @@ public function getSupportedEntityTypes(); */ public function isSupported($entity_type_id); + /** + * Content translation metadata factory. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The type of entity whose metadata needs to be retrieved. + * + * @return \Drupal\content_translation\ContentTranslationMetadataInterface + * An instance of the content translation metadata handler. + */ + public function getEntityTypeTranslationMetadata(EntityTypeInterface $entity_type); + + /** + * Content translation metadata factory. + * + * @param \Drupal\Core\Entity\EntityInterface $translation + * The entity translation whose metadata needs to be retrieved. + * + * @return \Drupal\content_translation\ContentTranslationMetadataInterface + * An instance of the content translation metadata handler. + */ + public function getTranslationMetadata(EntityInterface $translation); + } diff --git a/core/modules/content_translation/src/ContentTranslationMetadata.php b/core/modules/content_translation/src/ContentTranslationMetadata.php new file mode 100644 index 0000000..da25eed --- /dev/null +++ b/core/modules/content_translation/src/ContentTranslationMetadata.php @@ -0,0 +1,251 @@ +entityType = $entity_type; + } + + /** + * {@inheritdoc} + */ + public function getFieldDefinitions() { + $definitions = array(); + + $definitions['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['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->supportsNativeAuthor()) { + $definitions['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->supportsNativePublishedStatus()) { + $definitions['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->supportsNativeCreatedTime()) { + $definitions['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->supportsNativeChangedTime()) { + $definitions['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 supportsNativeAuthor() { + 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 supportsNativePublishedStatus() { + 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 supportsNativeChangedTime() { + 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 supportsNativeCreatedTime() { + return array_key_exists('created', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id())); + } + + /** + * {@inheritdoc} + */ + public function setTranslation(EntityInterface $translation) { + if ($translation->getEntityTypeId() == $this->entityType->id()) { + $this->translation = $translation; + return $this; + } + else { + $args = array( + '@entity_type_id' => $translation->getEntityTypeId(), + '@expected_entity_type_id' => $this->entityType->id(), + ); + throw new \LogicException(String::format('The translation object has the wrong entity type @entity_type_id (expected @expected_entity_type_id).', $args)); + } + } + + /** + * {@inheritdoc} + */ + public function getSource() { + return $this->translation->get('translation_source')->value; + } + + /** + * {@inheritdoc} + */ + public function setSource($source) { + $this->translation->set('translation_source', $source); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isOutdated() { + return (bool) $this->translation->get('translation_outdated')->value; + } + + /** + * {@inheritdoc} + */ + public function setOutdated($outdated) { + $this->translation->set('translation_outdated', $outdated); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAuthor() { + return $this->supportsNativeAuthor() ? $this->translation->getOwner() : $this->translation->get('translation_uid')->entity; + } + + /** + * {@inheritdoc} + */ + public function setAuthor(UserInterface $account) { + if ($this->supportsNativeAuthor()) { + $this->translation->setOwner($account); + } + else { + $this->translation->set('translation_uid', $account->id()); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function isPublished() { + return (bool) $this->translation->get($this->supportsNativePublishedStatus() ? 'status' : 'translation_status')->value; + } + + /** + * {@inheritdoc} + */ + public function setPublished($published) { + $this->translation->set($this->supportsNativePublishedStatus() ? 'status' : 'translation_status', $published); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCreatedTime() { + return $this->translation->get($this->supportsNativeCreatedTime() ? 'created' : 'translation_created')->value; + } + + /** + * {@inheritdoc} + */ + public function setCreatedTime($timestamp) { + $this->translation->set($this->supportsNativeCreatedTime() ? 'created' : 'translation_created', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getChangedTime() { + return $this->supportsNativeChangedTime() ? $this->translation->getChangedTime() : $this->translation->get('translation_changed')->value; + } + + /** + * {@inheritdoc} + */ + public function setChangedTime($timestamp) { + $this->translation->set($this->supportsNativeChangedTime() ? 'changed' : 'translation_changed', $timestamp); + return $this; + } + +} diff --git a/core/modules/content_translation/src/ContentTranslationMetadataInterface.php b/core/modules/content_translation/src/ContentTranslationMetadataInterface.php new file mode 100644 index 0000000..7a0cc95 --- /dev/null +++ b/core/modules/content_translation/src/ContentTranslationMetadataInterface.php @@ -0,0 +1,141 @@ +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 @@ -46,9 +72,11 @@ 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'); + $manager = $this->manager; $languages = $this->languageManager()->getLanguages(); $original = $entity->getUntranslated()->language()->getId(); @@ -69,8 +97,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, $manager) { + $source = $manager->getTranslationMetadata($entity->getTranslation($langcode))->getSource(); + return $source != $original && $source != LanguageInterface::LANGCODE_NOT_SPECIFIED; }); $show_source_column = !empty($additional_source_langcodes); @@ -119,7 +148,9 @@ 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); + $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 +176,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' => $metadata->isPublished(), + 'outdated' => $metadata->isOutdated(), ), )); diff --git a/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php b/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php index a1b3f16..c154582 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php @@ -117,10 +117,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(), @@ -188,6 +184,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); @@ -217,8 +214,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 3e14a6a..cf0d908 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,6 +93,7 @@ protected function setUp() { $this->setupUsers(); $this->setupTestFields(); + $this->manager = $this->container->get('content_translation.manager'); $this->controller = content_translation_controller($this->entityTypeId); // Rebuild the container so that the new languages are picked up by services @@ -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 58d0c59..7e132f7 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php @@ -8,7 +8,6 @@ namespace Drupal\content_translation\Tests; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageInterface; @@ -160,7 +159,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($this->manager->getTranslationMetadata($entity->getTranslation($added_langcode))->isOutdated(), 'The "outdated" status has been correctly stored.'); } } } @@ -178,7 +177,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.'); } } @@ -212,8 +211,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. @@ -225,8 +225,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/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php index 13eced4..e4baf7c 100644 --- a/core/modules/node/src/Tests/NodeTranslationUITest.php +++ b/core/modules/node/src/Tests/NodeTranslationUITest.php @@ -127,9 +127,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.'); } } } @@ -164,11 +163,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.'); } @@ -215,7 +213,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 d6399a0..712e88b 100644 --- a/core/modules/path/src/Tests/PathLanguageTest.php +++ b/core/modules/path/src/Tests/PathLanguageTest.php @@ -59,6 +59,7 @@ protected function setUp() { 'settings[node][page][settings][language][language_show]' => 1, ); $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save')); + \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/TermTranslationFieldViewTest.php b/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php index f9feddf..10e450b 100644 --- a/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php +++ b/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php @@ -144,6 +144,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/taxonomy/src/Tests/TermTranslationUITest.php b/core/modules/taxonomy/src/Tests/TermTranslationUITest.php index f96f357..18616e1 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 2ca8062..ca1a737 100644 --- a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php +++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php @@ -103,6 +103,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(); diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 6bb533c..580b04e 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -35,7 +35,8 @@ * "cancel" = "Drupal\user\Form\UserCancelForm", * "register" = "Drupal\user\RegisterForm" * }, - * "translation" = "Drupal\user\ProfileTranslationHandler" + * "translation" = "Drupal\user\ProfileTranslationHandler", + * "content_translation_metadata" = "Drupal\user\ProfileTranslationMetadata" * }, * admin_permission = "administer user", * base_table = "users", diff --git a/core/modules/user/src/ProfileTranslationMetadata.php b/core/modules/user/src/ProfileTranslationMetadata.php new file mode 100644 index 0000000..7d2c020 --- /dev/null +++ b/core/modules/user/src/ProfileTranslationMetadata.php @@ -0,0 +1,33 @@ +type . '][fields][body]' => TRUE, ); $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save')); + \Drupal::entityManager()->clearCachedDefinitions(); // Add a node in English, with title "sandwich". $values = array( diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index ccd1c92..32344d6 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -226,7 +226,7 @@ function seven_form_node_form_alter(&$form, FormStateInterface $form_state) { 'author' => array( '#type' => 'item', '#wrapper_attributes' => array('class' => array('author', 'container-inline')), - '#markup' => '

' . t('Author') . '

' . $node->getOwner()->getUsername(), +// '#markup' => '

' . t('Author') . '

' . $node->getOwner()->getUsername(), ), ); $form['revision_information']['#type'] = 'container';