diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php index df6b524..2e850c5 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php @@ -1093,7 +1093,15 @@ 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 (DatabaseExceptionWrapper $e) { + // @todo In some cases a "Data truncated for column" exception + // is thrown, but thing seem to work properly nonetheless. + // See https://www.drupal.org/node/2347301. + watchdog_exception('php', $e); + } } if (!empty($schema[$table_name]['indexes'])) { foreach ($schema[$table_name]['indexes'] as $name => $specifier) { diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index c38d30c..fb77f7d 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -117,8 +117,8 @@ 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'); + if (!$entity_type->get('content_translation_metadata')) { + $entity_type->set('content_translation_metadata', 'Drupal\content_translation\ContentTranslationMetadata'); } $translation = $entity_type->get('translation'); @@ -162,11 +162,11 @@ function content_translation_entity_base_field_info(EntityTypeInterface $entity_ $manager = \Drupal::service('content_translation.manager'); $entity_type_id = $entity_type->id(); if ($manager->isSupported($entity_type_id)) { - $definitions = $manager->getEntityTypeTranslationMetadata($entity_type)->getFieldDefinitions(); + $definitions = $manager->getTranslationHandler($entity_type_id)->getFieldDefinitions(); $installed_storage_definitions = \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($entity_type_id); - // By returning already installed field definitions even when no bundle is - // enabled for translation anymore, translation can be enabled again later - // without translation metadata being lost meanwhile. + // 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. @@ -367,10 +367,12 @@ function content_translation_enabled($entity_type, $bundle = NULL) { * @return \Drupal\content_translation\ContentTranslationHandlerInterface * An instance of the content translation controller interface. * - * @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) { - return \Drupal::entityManager()->getHandler($entity_type_id, 'translation'); + return \Drupal::service('content_translation.manager')->getTranslationHandler($entity_type_id); } /** diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php index dd574bc..cb46dcd 100644 --- a/core/modules/content_translation/src/ContentTranslationHandler.php +++ b/core/modules/content_translation/src/ContentTranslationHandler.php @@ -11,6 +11,7 @@ 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; @@ -70,6 +71,105 @@ public static function createInstance(ContainerInterface $container, EntityTypeI /** * {@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())); + } + + /** + * {@inheritdoc} + */ public function retranslate(EntityInterface $entity, $langcode = NULL) { $updated_langcode = !empty($langcode) ? $langcode : $entity->language()->getId(); foreach ($entity->getTranslationLanguages() as $langcode => $language) { diff --git a/core/modules/content_translation/src/ContentTranslationHandlerInterface.php b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php index e42dbdf..6e53355 100644 --- a/core/modules/content_translation/src/ContentTranslationHandlerInterface.php +++ b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php @@ -20,6 +20,13 @@ 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 * wrapped entity. * diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php index 66db0e4..5f2c93a 100644 --- a/core/modules/content_translation/src/ContentTranslationManager.php +++ b/core/modules/content_translation/src/ContentTranslationManager.php @@ -36,8 +36,8 @@ public function __construct(EntityManagerInterface $manager) { /** * {@inheritdoc} */ - public function getEntityTypeTranslationMetadata(EntityTypeInterface $entity_type) { - return clone $this->entityManager->getHandler($entity_type->id(), 'content_translation_metadata'); + function getTranslationHandler($entity_type_id) { + return $this->entityManager->getHandler($entity_type_id, 'translation'); } /** @@ -45,8 +45,9 @@ public function getEntityTypeTranslationMetadata(EntityTypeInterface $entity_typ */ public function getTranslationMetadata(EntityInterface $translation) { // We need a new instance of the metadata handler wrapping each translation. - $metadata = $this->getEntityTypeTranslationMetadata($translation->getEntityType()); - $metadata->setTranslation($translation); + $entity_type = $translation->getEntityType(); + $class = $entity_type->get('content_translation_metadata'); + $metadata = new $class($translation, $this->getTranslationHandler($entity_type->id())); return $metadata; } diff --git a/core/modules/content_translation/src/ContentTranslationManagerInterface.php b/core/modules/content_translation/src/ContentTranslationManagerInterface.php index a2951ed..a1aa3ac 100644 --- a/core/modules/content_translation/src/ContentTranslationManagerInterface.php +++ b/core/modules/content_translation/src/ContentTranslationManagerInterface.php @@ -35,15 +35,15 @@ public function getSupportedEntityTypes(); public function isSupported($entity_type_id); /** - * Content translation metadata factory. + * Content translation controller factory. * - * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type - * The type of entity whose metadata needs to be retrieved. + * @param string $entity_type_id + * The type of the entity being translated. * - * @return \Drupal\content_translation\ContentTranslationMetadataInterface - * An instance of the content translation metadata handler. + * @return \Drupal\content_translation\ContentTranslationHandlerInterface + * An instance of the content translation controller interface. */ - public function getEntityTypeTranslationMetadata(EntityTypeInterface $entity_type); + public function getTranslationHandler($entity_type_id); /** * Content translation metadata factory. diff --git a/core/modules/content_translation/src/ContentTranslationMetadata.php b/core/modules/content_translation/src/ContentTranslationMetadata.php index 99c9c0c..9b2d148 100644 --- a/core/modules/content_translation/src/ContentTranslationMetadata.php +++ b/core/modules/content_translation/src/ContentTranslationMetadata.php @@ -7,12 +7,7 @@ namespace Drupal\content_translation; -use Drupal\Component\Utility\String; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Field\BaseFieldDefinition; -use Drupal\Core\Language\LanguageInterface; -use Drupal\user\EntityOwnerInterface; use Drupal\user\UserInterface; /** @@ -21,150 +16,53 @@ class ContentTranslationMetadata implements ContentTranslationMetadataInterface { /** - * @var \Drupal\Core\Entity\EntityTypeInterface - */ - protected $entityType; - - /** - * @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\TypedData\TranslatableInterface|\Drupal\Core\TypedData\ComplexDataInterface - */ - protected $translation; - - /** - * Initializes an instance of the content translation metadata handler. - */ - public function __construct(EntityTypeInterface $entity_type) { - $this->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->hasAuthor()) { - $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->hasPublishedStatus()) { - $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->hasCreatedTime()) { - $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->hasChangedTime()) { - $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. + * The wrapped entity translation. * - * @return bool - * TRUE if metadata is natively supported, FALSE otherwise. + * @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\FieldableEntityInterface|\Drupal\Core\TypedData\TranslatableInterface */ - protected function hasAuthor() { - return is_subclass_of($this->entityType->getClass(), '\Drupal\user\EntityOwnerInterface'); - } + protected $translation; /** - * Checks whether the entity type supports published status natively. + * The content translation handler. * - * @return bool - * TRUE if metadata is natively supported, FALSE otherwise. + * @var \Drupal\content_translation\ContentTranslationHandlerInterface */ - protected function hasPublishedStatus() { - return array_key_exists('status', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id())); - } + protected $handler; /** - * Checks whether the entity type supports modification time natively. + * The field definition names for the wrapped entity translation. * - * @return bool - * TRUE if metadata is natively supported, FALSE otherwise. + * @var array */ - protected function hasChangedTime() { - return is_subclass_of($this->entityType->getClass(), '\Drupal\Core\Entity\EntityChangedInterface'); - } + protected $fieldDefinitionNames; /** - * Checks whether the entity type supports creation time natively. + * Initializes an instance of the content translation metadata handler. * - * @return bool - * TRUE if metadata is natively supported, FALSE otherwise. + * @param EntityInterface $translation + * The entity translation to be wrapped. + * @param ContentTranslationHandlerInterface $handler + * The content translation handler. */ - protected function hasCreatedTime() { - 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)); - } + public function __construct(EntityInterface $translation, ContentTranslationHandlerInterface $handler) { + $this->translation = $translation; + $this->handler = $handler; + $names = array_keys($this->translation->getFieldDefinitions()); + $this->fieldDefinitionNames = array_combine($names, $names); } /** * {@inheritdoc} */ public function getSource() { - return $this->translation->get('translation_source')->value; + return $this->translation->get('content_translation_source')->value; } /** * {@inheritdoc} */ public function setSource($source) { - $this->translation->set('translation_source', $source); + $this->translation->set('content_translation_source', $source); return $this; } @@ -172,14 +70,14 @@ public function setSource($source) { * {@inheritdoc} */ public function isOutdated() { - return (bool) $this->translation->get('translation_outdated')->value; + return (bool) $this->translation->get('content_translation_outdated')->value; } /** * {@inheritdoc} */ public function setOutdated($outdated) { - $this->translation->set('translation_outdated', $outdated); + $this->translation->set('content_translation_outdated', $outdated); return $this; } @@ -187,18 +85,18 @@ public function setOutdated($outdated) { * {@inheritdoc} */ public function getAuthor() { - return $this->hasAuthor() ? $this->translation->getOwner() : $this->translation->get('translation_uid')->entity; + return isset($this->fieldDefinitionNames['content_translation_uid']) ? $this->translation->get('content_translation_uid')->entity : $this->translation->getOwner(); } /** * {@inheritdoc} */ public function setAuthor(UserInterface $account) { - if ($this->hasAuthor()) { - $this->translation->setOwner($account); + if (isset($this->fieldDefinitionNames['content_translation_uid'])) { + $this->translation->set('content_translation_uid', $account->id()); } else { - $this->translation->set('translation_uid', $account->id()); + $this->translation->setOwner($account); } return $this; } @@ -207,14 +105,14 @@ public function setAuthor(UserInterface $account) { * {@inheritdoc} */ public function isPublished() { - return (bool) $this->translation->get($this->hasPublishedStatus() ? 'status' : 'translation_status')->value; + return (bool) $this->translation->get(isset($this->fieldDefinitionNames['content_translation_status']) ? 'content_translation_status' : 'status')->value; } /** * {@inheritdoc} */ public function setPublished($published) { - $this->translation->set($this->hasPublishedStatus() ? 'status' : 'translation_status', $published); + $this->translation->set(isset($this->fieldDefinitionNames['content_translation_status']) ? 'content_translation_status' : 'status', $published); return $this; } @@ -222,14 +120,14 @@ public function setPublished($published) { * {@inheritdoc} */ public function getCreatedTime() { - return $this->translation->get($this->hasCreatedTime() ? 'created' : 'translation_created')->value; + return $this->translation->get(isset($this->fieldDefinitionNames['content_translation_created']) ? 'content_translation_created' : 'created')->value; } /** * {@inheritdoc} */ public function setCreatedTime($timestamp) { - $this->translation->set($this->hasCreatedTime() ? 'created' : 'translation_created', $timestamp); + $this->translation->set(isset($this->fieldDefinitionNames['content_translation_created']) ? 'content_translation_created' : 'created', $timestamp); return $this; } @@ -237,14 +135,14 @@ public function setCreatedTime($timestamp) { * {@inheritdoc} */ public function getChangedTime() { - return $this->hasChangedTime() ? $this->translation->getChangedTime() : $this->translation->get('translation_changed')->value; + return isset($this->fieldDefinitionNames['content_translation_changed']) ? $this->translation->get('content_translation_changed')->value : $this->translation->getChangedTime(); } /** * {@inheritdoc} */ public function setChangedTime($timestamp) { - $this->translation->set($this->hasChangedTime() ? 'changed' : 'translation_changed', $timestamp); + $this->translation->set( isset($this->fieldDefinitionNames['content_translation_changed']) ? 'content_translation_changed' : 'changed', $timestamp); return $this; } diff --git a/core/modules/content_translation/src/ContentTranslationMetadataInterface.php b/core/modules/content_translation/src/ContentTranslationMetadataInterface.php index 7a0cc95..550168f 100644 --- a/core/modules/content_translation/src/ContentTranslationMetadataInterface.php +++ b/core/modules/content_translation/src/ContentTranslationMetadataInterface.php @@ -22,23 +22,6 @@ interface ContentTranslationMetadataInterface extends EntityChangedInterface { /** - * Returns a set of field definitions to be used to store metadata items. - * - * @return \Drupal\Core\Field\FieldDefinitionInterface[] - */ - public function getFieldDefinitions(); - - /** - * Sets the wrapped translation object. - * - * @param \Drupal\Core\Entity\EntityInterface $translation - * The translation object to be wrapped. - * - * @return $this - */ - public function setTranslation(EntityInterface $translation); - - /** * Retrieves the source language for this translation. * * @return string diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 904a3c0..2cdabc6 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -35,8 +35,7 @@ * "cancel" = "Drupal\user\Form\UserCancelForm", * "register" = "Drupal\user\RegisterForm" * }, - * "translation" = "Drupal\user\ProfileTranslationHandler", - * "content_translation_metadata" = "Drupal\user\UserTranslationMetadata" + * "translation" = "Drupal\user\ProfileTranslationHandler" * }, * admin_permission = "administer user", * base_table = "users", @@ -52,7 +51,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/user/src/UserTranslationMetadata.php b/core/modules/user/src/UserTranslationMetadata.php deleted file mode 100644 index 69a34d4..0000000 --- a/core/modules/user/src/UserTranslationMetadata.php +++ /dev/null @@ -1,33 +0,0 @@ -