diff --git a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php index 74fc537..800c10b 100644 --- a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php +++ b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php @@ -154,11 +154,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 64afbd7..50a48c4 100644 --- a/core/modules/comment/src/Tests/CommentTranslationUITest.php +++ b/core/modules/comment/src/Tests/CommentTranslationUITest.php @@ -119,31 +119,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..21ff011 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,6 +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_indicator')->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 7ca20d4..4f59ad3 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\ContentEntityFormInterface; 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; @@ -85,13 +86,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 @@ -107,6 +118,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'])) { @@ -141,6 +155,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: @@ -231,21 +268,20 @@ function content_translation_translate_access(EntityInterface $entity) { } /** - * Content translation controller factory. + * Content translation handler factory. * * @param string $entity_type_id * The type of the entity being translated. * * @return \Drupal\content_translation\ContentTranslationHandlerInterface - * An instance of the content translation controller interface. + * An instance of the content translation handler. * - * @todo Move to \Drupal\content_translation\ContentTranslationManager. + * @deprecated in Drupal 8.0, will be removed in Drupal 9.0. + * + * @see \Drupal\content_translation\ContentTranslationManagerInterface::getTranslationHandler() */ 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::service('content_translation.manager')->getTranslationHandler($entity_type_id); } /** @@ -311,58 +347,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]); } } } @@ -370,75 +365,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() { @@ -499,11 +425,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/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php index 88f57ae..5faafb7 100644 --- a/core/modules/content_translation/src/ContentTranslationHandler.php +++ b/core/modules/content_translation/src/ContentTranslationHandler.php @@ -8,11 +8,15 @@ namespace Drupal\content_translation; use Drupal\Core\Access\AccessResult; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; 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\Render\Element; +use Drupal\user\Entity\User; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Base class for content translation handlers. @@ -20,6 +24,7 @@ * @ingroup entity_api */ class ContentTranslationHandler implements ContentTranslationHandlerInterface { + use DependencySerializationTrait; /** * The type of the entity being translated. @@ -36,14 +41,130 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface { protected $entityType; /** + * The content translation manager. + * + * @var \Drupal\content_translation\ContentTranslationManagerInterface + */ + protected $manager; + + /** * 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->manager = $manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static($entity_type, $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())); } /** @@ -51,9 +172,9 @@ public function __construct(EntityTypeInterface $entity_type) { */ 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); } } @@ -208,20 +329,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; } @@ -237,7 +354,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', @@ -261,8 +378,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', @@ -273,13 +390,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)) { @@ -395,35 +512,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..6e53355 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,14 @@ * Defines a set of methods to allow any entity to be processed by the entity * translation UI. */ -interface ContentTranslationHandlerInterface { +interface ContentTranslationHandlerInterface extends EntityHandlerInterface { + + /** + * 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 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 @@ +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()); @@ -146,13 +177,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 a343190..dfa2b4d 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 dda1ff2..0f287a6 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 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 8480073..0ed4af2 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 e5f7065..153393b 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_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/TermTranslationFieldViewTest.php b/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php index fc3267c..9078880 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 5899705..9d8233e 100644 --- a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php +++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php @@ -105,6 +105,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(); @@ -123,6 +124,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 2ac5fec..65c9273 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -52,7 +52,7 @@ * "edit-form" = "entity.user.edit_form", * "cancel-form" = "entity.user.cancel_form", * }, - * 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 4750f0b..ac0ff64 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->type . '][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(