diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php index 3d2a56a..f4dfadf 100644 --- a/core/includes/entity.api.php +++ b/core/includes/entity.api.php @@ -241,6 +241,37 @@ function hook_entity_update(Drupal\Core\Entity\EntityInterface $entity) { } /** + * Acts after storing a new entity translation. + * + * @param \Drupal\Core\Entity\EntityInterface $translation + * The entity object of the translation just stored. + */ +function hook_entity_translation_insert(\Drupal\Core\Entity\EntityInterface $translation) { + $variables = array( + '@language' => $translation->language()->name, + '@label' => $translation->getOriginal()->label(), + ); + watchdog('example', 'The @language translation of @label has just been stored.', $variables); +} + +/** + * Acts after deleting an entity translation from the storage. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The original entity object. + * @param string $langcode + * The language code identifying the translation just removed. + */ +function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $entity, $langcode) { + $languages = language_list(); + $variables = array( + '@language' => $languages[$langcode]->name, + '@label' => $entity->label(), + ); + watchdog('example', 'The @language translation of @label has just been deleted.', $variables); +} + +/** * Act before entity deletion. * * This hook runs after the entity type-specific predelete hook. diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php index 91ec62d..c8349cb 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -14,7 +14,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\DatabaseStorageController; use Drupal\Core\Entity\EntityStorageException; -use Drupal\Core\TypedData\ComplexDataInterface; +use Drupal\Core\TypedData\TranslatableInterface; use Drupal\Component\Uuid\Uuid; use Drupal\Core\Database\Connection; @@ -302,6 +302,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) { $data = $query->execute(); $field_definition = $this->getFieldDefinitions(array()); + $translations = array(); if ($this->revisionTable) { $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_table']), drupal_schema_fields_sql($this->entityInfo['base_table']))); } @@ -315,6 +316,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) { // Field values in default language are stored with // Language::LANGCODE_DEFAULT as key. $langcode = empty($values['default_langcode']) ? $values['langcode'] : Language::LANGCODE_DEFAULT; + $translations[$id][$langcode] = TRUE; foreach ($field_definition as $name => $definition) { // Set only translatable properties, unless we are dealing with a @@ -330,7 +332,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) { foreach ($entities as $id => $values) { $bundle = $this->bundleKey ? $values[$this->bundleKey][Language::LANGCODE_DEFAULT] : FALSE; // Turn the record into an entity class. - $entities[$id] = new $this->entityClass($values, $this->entityType, $bundle); + $entities[$id] = new $this->entityClass($values, $this->entityType, $bundle, array_keys($translations[$id])); } } } @@ -378,6 +380,9 @@ public function save(EntityInterface $entity) { $this->resetCache(array($entity->id())); $this->postSave($entity, TRUE); $this->invokeHook('update', $entity); + if ($this->dataTable) { + $this->notifyTranslationChanges($entity); + } } else { $return = drupal_write_record($this->entityInfo['base_table'], $record); @@ -422,7 +427,7 @@ public function save(EntityInterface $entity) { */ protected function saveRevision(EntityInterface $entity) { $return = $entity->id(); - $default_langcode = $entity->language()->langcode; + $default_langcode = $entity->getOriginal()->language()->langcode; if (!$entity->isNewRevision()) { // Delete to handle removed values. @@ -432,9 +437,9 @@ protected function saveRevision(EntityInterface $entity) { ->execute(); } - $languages = $this->dataTable ? $entity->getTranslationLanguages(TRUE) : array($default_langcode => $entity->language()); + $languages = $this->dataTable ? $entity->getTranslationLanguages() : array($default_langcode => $entity->language()); foreach ($languages as $langcode => $language) { - $translation = $entity->getTranslation($langcode, FALSE); + $translation = $entity->getTranslation($langcode); $record = $this->mapToRevisionStorageRecord($translation); $record->langcode = $langcode; $record->default_langcode = $langcode == $default_langcode; @@ -496,6 +501,28 @@ protected function savePropertyData(EntityInterface $entity) { } /** + * Checks translation statuses and invoke the related hooks if needed. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + */ + function notifyTranslationChanges(EntityInterface $entity) { + // Notify modules of translation creation/removal. + foreach ($entity->getTranslationLanguages(FALSE, TRUE) as $langcode => $language) { + $translation = $entity->getTranslation($langcode); + + switch ($translation->getTranslationStatus()) { + case TranslatableInterface::TRANSLATION_CREATED: + $this->invokeHook('translation_insert', $translation); + break; + + case TranslatableInterface::TRANSLATION_REMOVED: + $this->invokeHook('translation_delete', $entity, $langcode); + break; + } + } + } + + /** * Overrides DatabaseStorageController::invokeHook(). * * Invokes field API attachers with a BC entity. @@ -543,7 +570,7 @@ protected function mapToStorageRecord(EntityInterface $entity) { * @return \stdClass * The record to store. */ - protected function mapToRevisionStorageRecord(ComplexDataInterface $entity) { + protected function mapToRevisionStorageRecord(EntityInterface $entity) { $record = new \stdClass(); $definitions = $entity->getPropertyDefinitions(); foreach (drupal_schema_fields_sql($this->entityInfo['revision_table']) as $name) { @@ -566,10 +593,10 @@ protected function mapToRevisionStorageRecord(ComplexDataInterface $entity) { * The record to store. */ protected function mapToDataStorageRecord(EntityInterface $entity, $langcode) { - $default_langcode = $entity->language()->langcode; + $default_langcode = $entity->getOriginal()->language()->langcode; // Don't use strict mode, this way there's no need to do checks here, as // non-translatable properties are replicated for each language. - $translation = $entity->getTranslation($langcode, FALSE); + $translation = $entity->getTranslation($langcode); $definitions = $translation->getPropertyDefinitions(); $schema = drupal_get_schema($this->entityInfo['data_table']); diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 9798515..7105b26 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -9,6 +9,7 @@ use Drupal\Component\Uuid\Uuid; use Drupal\Core\Language\Language; +use Drupal\Core\TypedData\TranslatableInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\user\UserInterface; use IteratorAggregate; @@ -280,10 +281,13 @@ public function language() { /** * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation(). + * + * @return \Drupal\Core\Entity\EntityInterface */ public function getTranslation($langcode, $strict = TRUE) { // @todo: Replace by EntityNG implementation once all entity types have been // converted to use the entity field API. + return $this; } /** @@ -300,7 +304,7 @@ public function translations() { /** * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages(). */ - public function getTranslationLanguages($include_default = TRUE) { + public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE) { // @todo: Replace by EntityNG implementation once all entity types have been // converted to use the entity field API. $default_language = $this->language(); @@ -517,4 +521,41 @@ public function isTranslatable() { return !empty($bundles[$this->bundle()]['translatable']); } + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::getOriginal(). + */ + public function getOriginal() { + return $this->getTranslation(Language::LANGCODE_DEFAULT); + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::hasTranslation(). + */ + public function hasTranslation($langcode) { + $translations = $this->getTranslationLanguages(); + return isset($translations[$langcode]); + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::addTranslation(). + */ + public function addTranslation($langcode, array $values = array()) {} + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::removeTranslation(). + */ + public function removeTranslation($langcode) {} + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationStatus(). + */ + public function getTranslationStatus() { + return TranslatableInterface::TRANSLATION_EXISTING; + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::initTranslation(). + */ + public function initTranslation($langcode) {} + } diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php index b27dae6..71e88f8 100644 --- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php +++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php @@ -411,8 +411,8 @@ public function language() { /** * Forwards the call to the decorated entity. */ - public function getTranslationLanguages($include_default = TRUE) { - return $this->decorated->getTranslationLanguages($include_default); + public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE) { + return $this->decorated->getTranslationLanguages($include_default, $include_removed); } /** @@ -526,4 +526,47 @@ public function onChange($property_name) { public function isTranslatable() { return $this->decorated->isTranslatable(); } + + /** + * Forwards the call to the decorated entity. + */ + public function getOriginal() { + return $this->decorated->getOriginal(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function hasTranslation($langcode) { + return $this->decorated->hasTranslation($langcode); + } + + /** + * Forwards the call to the decorated entity. + */ + public function addTranslation($langcode, array $values = array()) { + return $this->decorated->addTranslation($langcode, $values); + } + + /** + * Forwards the call to the decorated entity. + */ + public function removeTranslation($langcode) { + $this->decorated->removeTranslation($langcode); + } + + /** + * Forwards the call to the decorated entity. + */ + public function getTranslationStatus() { + return $this->decorated->getTranslationStatus(); + } + + /** + * Forwards the call to the decorated entity. + */ + public function initTranslation($langcode) { + $this->decorated->initTranslation($langcode); + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php index 7c5f7ed..9e0f046 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormController.php +++ b/core/lib/Drupal/Core/Entity/EntityFormController.php @@ -120,6 +120,13 @@ protected function init(array &$form_state) { // Add the controller to the form state so it can be easily accessed by // module-provided form handlers there. $form_state['controller'] = $this; + + // Ensure we act on the translation object corresponding to the current form + // language. + $translation = $this->entity->getTranslation($this->getFormLangcode($form_state)); + $this->entity = $this->entity instanceof EntityBCDecorator ? $translation->getBCEntity() : $translation; + + // Prepare the entity to be presented in the entity form. $this->prepareEntity(); // @todo Allow the usage of different form modes by exposing a hook and the @@ -164,7 +171,7 @@ public function form(array $form, array &$form_state) { // new entities. $form['langcode'] = array( '#type' => 'value', - '#value' => !$entity->isNew() ? $entity->langcode : language_default()->langcode, + '#value' => !$entity->isNew() ? $entity->getOriginal()->language()->langcode : language_default()->langcode, ); } return $form; @@ -309,7 +316,6 @@ public function delete(array $form, array &$form_state) { */ public function getFormLangcode(array $form_state) { $entity = $this->entity; - $translations = $entity->getTranslationLanguages(); if (!empty($form_state['langcode'])) { $langcode = $form_state['langcode']; @@ -318,6 +324,7 @@ public function getFormLangcode(array $form_state) { // If no form langcode was provided we default to the current content // language and inspect existing translations to find a valid fallback, // if any. + $translations = $entity->getTranslationLanguages(); $langcode = language(Language::TYPE_CONTENT)->langcode; $fallback = language_multilingual() ? language_fallback_get_candidates() : array(); while (!empty($langcode) && !isset($translations[$langcode])) { @@ -327,14 +334,14 @@ public function getFormLangcode(array $form_state) { // If the site is not multilingual or no translation for the given form // language is available, fall back to the entity language. - return !empty($langcode) ? $langcode : $entity->language()->langcode; + return !empty($langcode) ? $langcode : $entity->getOriginal()->language()->langcode; } /** * Implements \Drupal\Core\Entity\EntityFormControllerInterface::isDefaultFormLangcode(). */ public function isDefaultFormLangcode(array $form_state) { - return $this->getFormLangcode($form_state) == $this->entity->language()->langcode; + return $this->getFormLangcode($form_state) == $this->entity->getOriginal()->language()->langcode; } /** diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php index 0c9dabd..192beff 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php +++ b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php @@ -84,11 +84,10 @@ public function buildEntity(array $form, array &$form_state) { // edited by this form. Values of fields handled by field API are copied // by field_attach_extract_form_values() below. $values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values']; - $translation = $entity->getTranslation($this->getFormLangcode($form_state), FALSE); - $definitions = $translation->getPropertyDefinitions(); + $definitions = $entity->getPropertyDefinitions(); foreach ($values_excluding_fields as $key => $value) { if (isset($definitions[$key])) { - $translation->$key = $value; + $entity->$key = $value; } } diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index 078094c..2c2313e 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -12,6 +12,7 @@ use Drupal\Component\Uuid\Uuid; use ArrayIterator; use InvalidArgumentException; +use Drupal\Core\TypedData\TranslatableInterface; /** * Implements Entity Field API specific enhancements to the Entity class. @@ -74,11 +75,40 @@ class EntityNG extends Entity { protected $fieldDefinitions; /** + * Language code identifying the entity active language. + * + * This is the language field accessors will use to determine which field + * values manipulate. + * + * @var string + */ + protected $activeLangcode = Language::LANGCODE_DEFAULT; + + /** + * An array of entity translation metadata. + * + * An associative array keyed by translation language code. Every value is an + * array containg the translation status and the translation object, if it has + * already been instantiated. + * + * @var array + */ + protected $translations; + + /** + * A flag indicating whether a translation object is being initialized. + * + * @var boolean + */ + protected $translationInit = FALSE; + + /** * Overrides Entity::__construct(). */ - public function __construct(array $values, $entity_type, $bundle = FALSE) { + public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) { $this->entityType = $entity_type; $this->bundle = $bundle ? $bundle : $this->entityType; + foreach ($values as $key => $value) { // If the key matches an existing property set the value to the property // to ensure non converted properties have the correct value. @@ -87,6 +117,20 @@ public function __construct(array $values, $entity_type, $bundle = FALSE) { } $this->values[$key] = $value; } + + // Initialize translations. Ensure we have at least an entry for the entity + // original language. + $data = array('status' => TranslatableInterface::TRANSLATION_EXISTING); + $this->translations = array(Language::LANGCODE_DEFAULT => $data); + if ($translations) { + $default_langcode = $this->language()->langcode; + foreach ($translations as $langcode) { + if ($langcode != $default_langcode && $langcode != Language::LANGCODE_DEFAULT) { + $this->translations[$langcode] = $data; + } + } + } + $this->init(); } @@ -108,10 +152,20 @@ protected function init() { } /** + * Clear entity translation object cache to ensure we do not have stale references. + */ + protected function clearTranslationCache() { + foreach ($this->translations as &$translation) { + unset($translation['entity']); + } + } + + /** * Magic __wakeup() implementation. */ public function __wakeup() { $this->init(); + $this->clearTranslationCache(); } /** @@ -139,12 +193,11 @@ public function uuid() { * Implements \Drupal\Core\TypedData\ComplexDataInterface::get(). */ public function get($property_name) { - // Values in default language are always stored using the - // Language::LANGCODE_DEFAULT constant. - if (!isset($this->fields[$property_name][Language::LANGCODE_DEFAULT])) { - return $this->getTranslatedField($property_name, Language::LANGCODE_DEFAULT); + $this->checkTranslationStatus(); + if (!isset($this->fields[$property_name][$this->activeLangcode])) { + return $this->getTranslatedField($property_name, $this->activeLangcode); } - return $this->fields[$property_name][Language::LANGCODE_DEFAULT]; + return $this->fields[$property_name][$this->activeLangcode]; } /** @@ -270,9 +323,26 @@ public function isEmpty() { * Implements \Drupal\Core\TypedData\TranslatableInterface::language(). */ public function language() { + if ($this->activeLangcode != Language::LANGCODE_DEFAULT) { + $languages = language_list(Language::STATE_ALL); + if (isset($languages[$this->activeLangcode])) { + return $languages[$this->activeLangcode]; + } + } + return $this->getOriginalLanguage(); + } + + /** + * Returns the entity original language. + * + * @return \Drupal\Core\Language\Language + * The entity language object. + */ + protected function getOriginalLanguage() { + $language = NULL; // Get the language code if the property exists. - if ($this->getPropertyDefinition('langcode')) { - $language = $this->get('langcode')->language; + if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) { + $language = $item->language; } if (empty($language)) { // Make sure we return a proper language object. @@ -284,75 +354,158 @@ public function language() { /** * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation(). * - * @return \Drupal\Core\Entity\Field\Type\EntityTranslation + * @return \Drupal\Core\Entity\EntityInterface */ public function getTranslation($langcode, $strict = TRUE) { - // If the default language is Language::LANGCODE_NOT_SPECIFIED, the entity is not - // translatable, so we use Language::LANGCODE_DEFAULT. - if ($langcode == Language::LANGCODE_DEFAULT || in_array($this->language()->langcode, array(Language::LANGCODE_NOT_SPECIFIED, $langcode))) { - // No translation needed, return the entity. - return $this; - } - // Check whether the language code is valid, thus is of an available - // language. + // Ensure we always use the default language code when dealing with the + // original entity language. + if ($langcode != Language::LANGCODE_DEFAULT && $langcode == $this->getOriginalLanguage()->langcode) { + $langcode = Language::LANGCODE_DEFAULT; + } + + // Populate entity translation object cache so it will be available for all + // translation objects. + if ($langcode == $this->activeLangcode) { + $this->translations[$langcode]['entity'] = $this; + } + + // If we already have a translation object for the specified language we can + // just return it. + if (isset($this->translations[$langcode]['entity'])) { + return $this->translations[$langcode]['entity']; + } + + // If the requested translation is valid, we instantiate a new translation + // object being a clone of the current one but with the specified language + // as active language. Before cloning we specify we are initializing a + // translation object to perform a shallow clone, in fact all the field data + // structures need to be shared among the translation objects to ensure all + // of them deal with fresh data. + if (isset($this->translations[$langcode])) { + $this->translationInit = TRUE; + $translation = clone $this; + $translation->activeLangcode = $langcode; + // Ensure that changes to fields, values and translations are propagated + // to all the translation objects. + // @todo Consider converting these to ArrayObject. + $translation->values = &$this->values; + $translation->fields = &$this->fields; + $translation->translations = &$this->translations; + $translation->translationInit = FALSE; + $this->translations[$langcode]['entity'] = $translation; + $this->translationInit = FALSE; + return $translation; + } + + // TODO Do we want to return $this instead? + throw new InvalidArgumentException("The '$langcode' translation does not exist."); + } + + /** + * {@inheritdoc} + */ + public function hasTranslation($langcode) { + return !empty($this->translations[$langcode]['status']); + } + + /** + * {@inheritdoc} + */ + public function addTranslation($langcode, array $values = array()) { $languages = language_list(Language::STATE_ALL); - if (!isset($languages[$langcode])) { - throw new InvalidArgumentException("Unable to get translation for the invalid language '$langcode'."); + if (!isset($languages[$langcode]) || $this->hasTranslation($langcode)) { + throw new InvalidArgumentException("Invalid translation language '$langcode'"); + } + + // Instantiate a new empty entity so default values will be populated in the + // specified language. + $info = $this->entityInfo(); + // @todo Use the actual translation language once the BC decorator is gone. + $default_values = array($info['entity_keys']['bundle'] => $this->bundle, 'langcode' => Language::LANGCODE_NOT_SPECIFIED); + $entity = entity_create($this->entityType(), $default_values); + foreach ($entity as $name => $field) { + if (!isset($values[$name]) && !$field->isEmpty()) { + $values[$name] = $field->value; + } } - $fields = array(); - foreach ($this->getPropertyDefinitions() as $name => $definition) { - // Load only translatable properties in strict mode. - if (!empty($definition['translatable']) || !$strict) { - $fields[$name] = $this->getTranslatedField($name, $langcode); + + $this->translations[$langcode]['status'] = self::TRANSLATION_CREATED; + $translation = $this->getTranslation($langcode); + $definitions = $translation->getPropertyDefinitions(); + + foreach ($values as $name => $value) { + if (isset($definitions[$name]) && !empty($definitions[$name]['translatable'])) { + $translation->$name = $value; } } - // @todo: Add a way to get the definition of a translation to the - // TranslatableInterface and leverage TypeDataManager::getPropertyInstance - // also. - $translation_definition = array( - 'type' => 'entity_translation', - 'constraints' => array( - 'entity type' => $this->entityType(), - 'bundle' => $this->bundle(), - ), - ); - $translation = \Drupal::typedData()->create($translation_definition, $fields); - $translation->setStrictMode($strict); - $translation->setContext('@' . $langcode, $this); + return $translation; } /** - * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages(). + * {@inheritdoc} */ - public function getTranslationLanguages($include_default = TRUE) { - $translations = array(); - $definitions = $this->getPropertyDefinitions(); - // Build an array with the translation langcodes set as keys. Empty - // translations should not be included and must be skipped. - foreach ($this->getProperties() as $name => $property) { - foreach ($this->fields[$name] as $langcode => $field) { - if (!$field->isEmpty()) { - $translations[$langcode] = TRUE; - } - if (isset($this->values[$name])) { - foreach ($this->values[$name] as $langcode => $values) { - // If a value is there but the field object is empty, it has been - // unset, so we need to skip the field also. - if ($values && !empty($definitions[$name]['translatable']) && !(isset($this->fields[$name][$langcode]) && $this->fields[$name][$langcode]->isEmpty())) { - $translations[$langcode] = TRUE; - } - } - } + public function removeTranslation($langcode) { + $translation = $this->getTranslation($langcode); + foreach ($translation->getPropertyDefinitions() as $name => $definition) { + if (!empty($definition['translatable'])) { + $translation->$name = array(); } } - // We include the default language code instead of the - // Language::LANGCODE_DEFAULT constant. + $this->translations[$langcode]['status'] = self::TRANSLATION_REMOVED; + } + + /** + * {@inheritdoc} + */ + public function getTranslationStatus() { + return $this->translations[$this->activeLangcode]['status']; + } + + /** + * {@inheritdoc} + */ + public function initTranslation($langcode) { + $this->translations[$langcode]['status'] = self::TRANSLATION_EXISTING; + } + + /** + * Checks wether the current translation object has a valid status. + * + * To avoid manipulating stale data, we invalidate a translation object as + * soon as the related translation has been removed. Any attempt to access + * invalid data causes an exception to be thrown. + * + * @return boolean + * TRUE if the translation object has a valid status. + */ + protected function checkTranslationStatus() { + if ($this->translations[$this->activeLangcode]['status'] == self::TRANSLATION_REMOVED) { + // TODO Use a more specific exception. + throw new \Exception('The entity object refers to a removed translation and cannot be manipulated.'); + } + return TRUE; + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages(). + */ + public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE) { + if (!$include_removed) { + $translations = array_filter($this->translations, function($translation) { return $translation['status']; }); + } + else { + $translations = $this->translations; + } + + $translations = array_flip(array_keys($translations)); unset($translations[Language::LANGCODE_DEFAULT]); if ($include_default) { - $translations[$this->language()->langcode] = TRUE; + $langcode = $this->getOriginalLanguage()->langcode; + $translations[$langcode] = TRUE; } + // Now load language objects based upon translation langcodes. return array_intersect_key(language_list(Language::STATE_ALL), $translations); } @@ -402,17 +555,18 @@ public function updateOriginalValues() { * For compatibility mode to work this must return a reference. */ public function &__get($name) { + $this->checkTranslationStatus(); // If this is an entity field, handle it accordingly. We first check whether // a field object has been already created. If not, we create one. - if (isset($this->fields[$name][Language::LANGCODE_DEFAULT])) { - return $this->fields[$name][Language::LANGCODE_DEFAULT]; + if (isset($this->fields[$name][$this->activeLangcode])) { + return $this->fields[$name][$this->activeLangcode]; } // Inline getPropertyDefinition() to speed up things. if (!isset($this->fieldDefinitions)) { $this->getPropertyDefinitions(); } if (isset($this->fieldDefinitions[$name])) { - $return = $this->getTranslatedField($name, Language::LANGCODE_DEFAULT); + $return = $this->getTranslatedField($name, $this->activeLangcode); return $return; } // Allow the EntityBCDecorator to directly access the values and fields. @@ -434,17 +588,18 @@ public function &__get($name) { * Uses default language always. */ public function __set($name, $value) { + $this->checkTranslationStatus(); // Support setting values via property objects. if ($value instanceof TypedDataInterface && !$value instanceof EntityInterface) { $value = $value->getValue(); } // If this is an entity field, handle it accordingly. We first check whether // a field object has been already created. If not, we create one. - if (isset($this->fields[$name][Language::LANGCODE_DEFAULT])) { - $this->fields[$name][Language::LANGCODE_DEFAULT]->setValue($value); + if (isset($this->fields[$name][$this->activeLangcode])) { + $this->fields[$name][$this->activeLangcode]->setValue($value); } elseif ($this->getPropertyDefinition($name)) { - $this->getTranslatedField($name, Language::LANGCODE_DEFAULT)->setValue($value); + $this->getTranslatedField($name, $this->activeLangcode)->setValue($value); } // Else directly read/write plain values. That way, fields not yet converted // to the entity field API can always be directly accessed. @@ -481,6 +636,7 @@ public function __unset($name) { * Overrides Entity::createDuplicate(). */ public function createDuplicate() { + $this->checkTranslationStatus(); $duplicate = clone $this; $entity_info = $this->entityInfo(); $duplicate->{$entity_info['entity_keys']['id']}->value = NULL; @@ -503,13 +659,19 @@ public function createDuplicate() { * Magic method: Implements a deep clone. */ public function __clone() { - $this->bcEntity = NULL; - - foreach ($this->fields as $name => $properties) { - foreach ($properties as $langcode => $property) { - $this->fields[$name][$langcode] = clone $property; - $this->fields[$name][$langcode]->setContext($name, $this); + // Avoid deep-cloning when we are initializing a translation object, since + // it will represent the same entity, only with a different active language. + if (!$this->translationInit) { + $this->bcEntity = NULL; + + foreach ($this->fields as $name => $properties) { + foreach ($properties as $langcode => $property) { + $this->fields[$name][$langcode] = clone $property; + $this->fields[$name][$langcode]->setContext($name, $this); + } } + + $this->clearTranslationCache(); } } @@ -519,6 +681,9 @@ public function __clone() { public function label($langcode = NULL) { $label = NULL; $entity_info = $this->entityInfo(); + if (!isset($langcode)) { + $langcode = $this->activeLangcode; + } if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) { $label = $entity_info['label_callback']($this->entityType, $this, $langcode); } @@ -535,4 +700,5 @@ public function validate() { // @todo: Add the typed data manager as proper dependency. return \Drupal::typedData()->getValidator()->validate($this); } + } diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php deleted file mode 100644 index 2cc58f4..0000000 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php +++ /dev/null @@ -1,224 +0,0 @@ -strict; - } - - /** - * Sets whether the entity translation acts in strict mode. - * - * @param boolean $strict - * Whether the entity translation acts in strict mode. - * - * @see \Drupal\Core\TypedData\TranslatableInterface::getTranslation() - */ - public function setStrictMode($strict = TRUE) { - $this->strict = $strict; - } - - /** - * Overrides \Drupal\Core\TypedData\TypedData::getValue(). - */ - public function getValue() { - // The plain value of the translation is the array of translated field - // objects. - return $this->fields; - } - - /** - * Overrides \Drupal\Core\TypedData\TypedData::setValue(). - */ - public function setValue($values, $notify = TRUE) { - // Notify the parent of any changes to be made. - if ($notify && isset($this->parent)) { - $this->parent->onChange($this->name); - } - $this->fields = $values; - } - - /** - * Overrides \Drupal\Core\TypedData\TypedData::getString(). - */ - public function getString() { - $strings = array(); - foreach ($this->getProperties() as $property) { - $strings[] = $property->getString(); - } - return implode(', ', array_filter($strings)); - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::get(). - */ - public function get($property_name) { - $definitions = $this->getPropertyDefinitions(); - if (!isset($definitions[$property_name])) { - throw new InvalidArgumentException(format_string('Field @name is unknown or not translatable.', array('@name' => $property_name))); - } - return $this->fields[$property_name]; - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::set(). - */ - public function set($property_name, $value, $notify = TRUE) { - $this->get($property_name)->setValue($value, FALSE); - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::getProperties(). - */ - public function getProperties($include_computed = FALSE) { - $properties = array(); - foreach ($this->getPropertyDefinitions() as $name => $definition) { - if ($include_computed || empty($definition['computed'])) { - $properties[$name] = $this->get($name); - } - } - return $properties; - } - - /** - * Magic method: Gets a translated field. - */ - public function __get($name) { - return $this->get($name); - } - - /** - * Magic method: Sets a translated field. - */ - public function __set($name, $value) { - $this->get($name)->setValue($value); - } - - /** - * Implements \IteratorAggregate::getIterator(). - */ - public function getIterator() { - return new ArrayIterator($this->getProperties()); - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition(). - */ - public function getPropertyDefinition($name) { - $definitions = $this->getPropertyDefinitions(); - if (isset($definitions[$name])) { - return $definitions[$name]; - } - else { - return FALSE; - } - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions(). - */ - public function getPropertyDefinitions() { - $definitions = array(); - foreach ($this->parent->getPropertyDefinitions() as $name => $definition) { - if (!empty($definition['translatable']) || !$this->strict) { - $definitions[$name] = $definition; - } - } - return $definitions; - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues(). - */ - public function getPropertyValues() { - return $this->getValue(); - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues(). - */ - public function setPropertyValues($values) { - foreach ($values as $name => $value) { - $this->get($name)->setValue($value); - } - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty(). - */ - public function isEmpty() { - foreach ($this->getProperties() as $property) { - if ($property->getValue() !== NULL) { - return FALSE; - } - } - return TRUE; - } - - /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange(). - */ - public function onChange($property_name) { - // Notify the parent of changes. - if (isset($this->parent)) { - $this->parent->onChange($this->name); - } - } - - /** - * Implements \Drupal\Core\TypedData\AccessibleInterface::access(). - */ - public function access($operation = 'view', UserInterface $account = NULL) { - // Determine the language code of this translation by cutting of the - // leading "@" from the property name to get the langcode. - // @todo Add a way to set and get the langcode so that's more obvious what - // we're doing here. - $langcode = substr($this->getName(), 1); - return \Drupal::entityManager() - ->getAccessController($this->parent->entityType()) - ->access($this->parent, $operation, $langcode, $account); - } -} diff --git a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php index f86068a..63a264b 100644 --- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php +++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php @@ -13,9 +13,24 @@ interface TranslatableInterface { /** + * Status code indentifying a removed translation. + */ + const TRANSLATION_REMOVED = 0; + + /** + * Status code indentifying an existing translation. + */ + const TRANSLATION_EXISTING = 1; + + /** + * Status code indentifying a newly created translation. + */ + const TRANSLATION_CREATED = 2; + + /** * Returns the default language. * - * @return + * @return \Drupal\Core\Language\Language * The language object. */ public function language(); @@ -24,12 +39,16 @@ public function language(); * Returns the languages the data is translated to. * * @param bool $include_default - * Whether the default language should be included. + * (optional) Whether the default language should be included. Defaults to + * TRUE. + * @param bool $include_removed + * Whether languages referring to removed translations should be included. + * Defaults to FALSE. * * @return * An array of language objects, keyed by language codes. */ - public function getTranslationLanguages($include_default = TRUE); + public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE); /** * Gets a translation of the data. @@ -53,4 +72,59 @@ public function getTranslationLanguages($include_default = TRUE); */ public function getTranslation($langcode, $strict = TRUE); + + /** + * Returns the entity object referring to the original language. + * + * @return \Drupal\Core\TypedData\TranslatableInterface + */ + public function getOriginal(); + + /** + * Returns TRUE if the entity has a translation for the given language code. + * + * @param string $langcode + * The language code identifiying the translation. + * + * @return bool + */ + public function hasTranslation($langcode); + + /** + * Adds a new translation to the entity object. + * + * @param string $langcode + * The language code identifying the translation. + * @param array $values + * (optional) An array of initial values to be assigned to the translatable + * field. Defaults to none. + * + * @return \Drupal\Core\TypedData\TranslatableInterface + */ + public function addTranslation($langcode, array $values = array()); + + /** + * Removes the translation identified by the given language code. + * + * @param string $langcode + */ + public function removeTranslation($langcode); + + /** + * Returns the current status of the entity translation object. + * + * @return integer + * A translation status code as defined in TranslatableInterface. + */ + public function getTranslationStatus(); + + /** + * Marks the translation identified by the given language code as existing. + * + * @todo Remove this as soon as all translatable entities have a data table. + * + * @param string $langcode + */ + public function initTranslation($langcode); + } diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php index dd4e13d..c06a0de 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -158,9 +158,10 @@ public function form(array $form, array &$form_state) { } // Add internal comment properties. + $original = $comment->getOriginal(); foreach (array('cid', 'pid', 'nid', 'uid', 'node_type', 'langcode') as $key) { $key_name = key($comment->$key->offsetGet(0)->getPropertyDefinitions()); - $form[$key] = array('#type' => 'value', '#value' => $comment->$key->{$key_name}); + $form[$key] = array('#type' => 'value', '#value' => $original->$key->{$key_name}); } return parent::form($form, $form_state, $comment); diff --git a/core/modules/node/lib/Drupal/node/NodeTranslationController.php b/core/modules/node/lib/Drupal/node/NodeTranslationController.php index 4531a8e..6ed7182 100644 --- a/core/modules/node/lib/Drupal/node/NodeTranslationController.php +++ b/core/modules/node/lib/Drupal/node/NodeTranslationController.php @@ -8,12 +8,12 @@ namespace Drupal\node; use Drupal\Core\Entity\EntityInterface; -use Drupal\translation_entity\EntityTranslationController; +use Drupal\translation_entity\EntityTranslationControllerNG; /** * Defines the translation controller class for nodes. */ -class NodeTranslationController extends EntityTranslationController { +class NodeTranslationController extends EntityTranslationControllerNG { /** * Overrides EntityTranslationController::getAccess(). diff --git a/core/modules/system/system.module b/core/modules/system/system.module index ce326d2..be32ca3 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -2246,11 +2246,6 @@ function system_data_type_info() { 'description' => t('All kind of entities, e.g. nodes, comments or users.'), 'class' => '\Drupal\Core\Entity\Field\Type\EntityWrapper', ), - 'entity_translation' => array( - 'label' => t('Entity translation'), - 'description' => t('A translation of an entity'), - 'class' => '\Drupal\Core\Entity\Field\Type\EntityTranslation', - ), 'boolean_field' => array( 'label' => t('Boolean field item'), 'description' => t('An entity field containing a boolean value.'), diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php index 185fdab..838a52f 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php @@ -47,7 +47,7 @@ public function form(array $form, array &$form_state) { $form['langcode'] = array( '#title' => t('Language'), '#type' => 'language_select', - '#default_value' => $entity->language()->langcode, + '#default_value' => $entity->getOriginal()->language()->langcode, '#languages' => Language::STATE_ALL, ); diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php index 832a0cf..8e98c4f 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php @@ -47,7 +47,7 @@ public function form(array $form, array &$form_state) { '#type' => 'language_select', '#title' => t('Language'), '#languages' => Language::STATE_ALL, - '#default_value' => $term->langcode->value, + '#default_value' => $term->getOriginal()->language()->langcode, '#access' => !is_null($language_configuration['language_show']) && $language_configuration['language_show'], ); diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php index 906248a..973902e 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php @@ -126,7 +126,7 @@ public function getSourceLangcode(array $form_state) { public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity) { $form_controller = translation_entity_form_controller($form_state); $form_langcode = $form_controller->getFormLangcode($form_state); - $entity_langcode = $entity->language()->langcode; + $entity_langcode = $entity->getOriginal()->language()->langcode; $source_langcode = $this->getSourceLangcode($form_state); $new_translation = !empty($source_langcode); @@ -144,7 +144,7 @@ public function entityFormAlter(array &$form, array &$form_state, EntityInterfac if (isset($languages[$form_langcode]) && ($has_translations || $new_translation)) { $title = $this->entityFormTitle($entity); // When editing the original values display just the entity label. - if ($form_langcode != $entity->language()->langcode) { + if ($form_langcode != $entity_langcode) { $t_args = array('%language' => $languages[$form_langcode]->name, '%title' => $entity->label()); $title = empty($source_langcode) ? $title . ' [' . t('%language translation', $t_args) . ']' : t('Create %language translation of %title', $t_args); } @@ -433,10 +433,9 @@ public function entityFormEntityBuild($entity_type, EntityInterface $entity, arr } // Set contextual information that can be reused during the storage phase. - // @todo Remove this once we have an EntityLanguageDecorator to deal with - // the active language. - $attributes = drupal_container()->get('request')->attributes; - $attributes->set('working_langcode', $form_langcode); + // @todo Remove this once translation metadata is converted to regular + // fields. + $attributes = \Drupal::request()->attributes; $attributes->set('source_langcode', $source_langcode); } diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php index fd007cd..2243466 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php @@ -25,10 +25,7 @@ public function getAccess(EntityInterface $entity, $op) { * Overrides \Drupal\translation_entity\EntityTranslationControllerInterface::removeTranslation(). */ public function removeTranslation(EntityInterface $entity, $langcode) { - $translation = $entity->getTranslation($langcode); - foreach ($translation->getPropertyDefinitions() as $property_name => $langcode) { - $translation->$property_name = array(); - } + $entity->removeTranslation($langcode); } } diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php index 261dfe1..94e688f 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php @@ -48,7 +48,7 @@ public function synchronizeFields(EntityInterface $entity, $sync_langcode, $orig // If the entity language is being changed there is nothing to synchronize. $entity_type = $entity->entityType(); $entity_unchanged = isset($entity->original) ? $entity->original : $this->entityManager->getStorageController($entity_type)->loadUnchanged($entity->id()); - if ($entity->language()->langcode != $entity_unchanged->language()->langcode) { + if ($entity->getOriginal()->language()->langcode != $entity_unchanged->getOriginal()->language()->langcode) { return; } diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php index 50e8bbd..e18c454 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php @@ -10,7 +10,6 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityNG; use Drupal\Core\Language\Language; -use Drupal\Core\TypedData\ComplexDataInterface; /** * Tests the Entity Translation UI. @@ -267,7 +266,7 @@ protected function getTranslation(EntityInterface $entity, $langcode) { /** * Returns the value for the specified property in the given language. * - * @param \Drupal\Core\TypedData\TranslatableInterface $translation + * @param \Drupal\Core\Entity\EntityInterface $translation * The translation object the property value should be retrieved from. * @param string $property * The property name. @@ -277,7 +276,7 @@ protected function getTranslation(EntityInterface $entity, $langcode) { * @return * The property value. */ - protected function getValue(ComplexDataInterface $translation, $property, $langcode) { + protected function getValue(EntityInterface $translation, $property, $langcode) { $key = $property == 'user_id' ? 'target_id' : 'value'; // @todo remove EntityBCDecorator condition once EntityBCDecorator is gone. if (($translation instanceof EntityInterface) && !($translation instanceof EntityNG) && !($translation instanceof EntityBCDecorator)) { diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module index bbe6605..695e57d 100644 --- a/core/modules/translation_entity/translation_entity.module +++ b/core/modules/translation_entity/translation_entity.module @@ -278,7 +278,7 @@ function _translation_entity_menu_strip_loaders($path) { */ function translation_entity_translate_access(EntityInterface $entity) { $entity_type = $entity->entityType(); - return empty($entity->language()->locked) && language_multilingual() && $entity->isTranslatable() && + return empty($entity->getOriginal()->language()->locked) && language_multilingual() && $entity->isTranslatable() && (user_access('create entity translations') || user_access('update entity translations') || user_access('delete entity translations')); } @@ -331,7 +331,7 @@ function translation_entity_edit_access(EntityInterface $entity, Language $langu $language = !empty($language) ? $language : language(Language::TYPE_CONTENT); $translations = $entity->getTranslationLanguages(); $languages = language_list(); - return isset($languages[$language->langcode]) && $language->langcode != $entity->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'update'); + return isset($languages[$language->langcode]) && $language->langcode != $entity->getOriginal()->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'update'); } /** @@ -347,7 +347,7 @@ function translation_entity_delete_access(EntityInterface $entity, Language $lan $language = !empty($language) ? $language : language(Language::TYPE_CONTENT); $translations = $entity->getTranslationLanguages(); $languages = language_list(); - return isset($languages[$language->langcode]) && $language->langcode != $entity->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'delete'); + return isset($languages[$language->langcode]) && $language->langcode != $entity->getOriginal()->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'delete'); } /** @@ -644,7 +644,7 @@ function translation_entity_field_language_alter(&$display_language, $context) { $instances = field_info_instances($entity_type, $entity->bundle()); // Avoid altering the real entity. $entity = clone($entity); - $entity_langcode = $entity->language()->langcode; + $entity_langcode = $entity->getOriginal()->language()->langcode; foreach ($entity->translation as $langcode => $translation) { if ($langcode == $context['langcode'] || !translation_entity_view_access($entity, $langcode)) { @@ -704,7 +704,11 @@ function translation_entity_load_translation_metadata(array $entities, $entity_t // @todo Declare these as entity (translation?) properties. foreach ($record as $field_name => $value) { if (!in_array($field_name, $exclude)) { - $entity->translation[$record->langcode][$field_name] = $value; + $langcode = $record->langcode; + $entity->translation[$langcode][$field_name] = $value; + if (!$entity->hasTranslation($langcode)) { + $entity->initTranslation($langcode); + } } } } @@ -865,8 +869,10 @@ function translation_entity_field_info_alter(&$info) { */ function translation_entity_field_attach_presave(EntityInterface $entity) { if ($entity->isTranslatable()) { - $attributes = drupal_container()->get('request')->attributes; - Drupal::service('translation_entity.synchronizer')->synchronizeFields($entity, $attributes->get('working_langcode'), $attributes->get('source_langcode')); + // @todo Avoid using request attributes once translation metadata become + // regular fields. + $attributes = Drupal::request()->attributes; + Drupal::service('translation_entity.synchronizer')->synchronizeFields($entity, $entity->language()->langcode, $attributes->get('source_langcode')); } } diff --git a/core/modules/translation_entity/translation_entity.pages.inc b/core/modules/translation_entity/translation_entity.pages.inc index c7de9e2..d1b6c44 100644 --- a/core/modules/translation_entity/translation_entity.pages.inc +++ b/core/modules/translation_entity/translation_entity.pages.inc @@ -19,7 +19,7 @@ function translation_entity_overview(EntityInterface $entity) { $controller = translation_entity_controller($entity->entityType()); $entity_manager = Drupal::entityManager(); $languages = language_list(); - $original = $entity->language()->langcode; + $original = $entity->getOriginal()->language()->langcode; $translations = $entity->getTranslationLanguages(); $field_ui = module_exists('field_ui') && user_access('administer ' . $entity->entityType() . ' fields'); @@ -238,19 +238,13 @@ function translation_entity_edit_page(EntityInterface $entity, Language $languag */ function translation_entity_prepare_translation(EntityInterface $entity, Language $source, Language $target) { // @todo Unify field and property handling. - $instances = field_info_instances($entity->entityType(), $entity->bundle()); $entity = $entity->getNGEntity(); if ($entity instanceof EntityNG) { $source_translation = $entity->getTranslation($source->langcode); - $target_translation = $entity->getTranslation($target->langcode); - foreach ($target_translation->getPropertyDefinitions() as $property_name => $definition) { - // @todo The "key" part should not be needed. Remove it as soon as things - // do not break. - $key = key($entity->{$property_name}[0]->getProperties()); - $target_translation->$property_name->{$key} = $source_translation->$property_name->{$key}; - } + $entity->addTranslation($target->langcode, $source_translation->getPropertyValues()); } else { + $instances = field_info_instances($entity->entityType(), $entity->bundle()); foreach ($instances as $field_name => $instance) { $field = field_info_field($field_name); if (!empty($field['translatable'])) { diff --git a/core/modules/user/lib/Drupal/user/ProfileTranslationController.php b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php index c1b2414..59a34f9 100644 --- a/core/modules/user/lib/Drupal/user/ProfileTranslationController.php +++ b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php @@ -8,12 +8,12 @@ namespace Drupal\user; use Drupal\Core\Entity\EntityInterface; -use Drupal\translation_entity\EntityTranslationController; +use Drupal\translation_entity\EntityTranslationControllerNG; /** * Defines the translation controller class for terms. */ -class ProfileTranslationController extends EntityTranslationController { +class ProfileTranslationController extends EntityTranslationControllerNG { /** * Overrides EntityTranslationController::entityFormAlter(). diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php index abe1e45..0103583 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php @@ -923,14 +923,15 @@ public function getExportProperties() { * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation(). */ public function getTranslation($langcode, $strict = TRUE) { - return $this->storage->getTranslation($langcode, $strict); + // @todo Revisit this once config entities are converted to NG. + return $this; } /** * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages(). */ - public function getTranslationLanguages($include_default = TRUE) { - return $this->storage->getTranslationLanguages($include_default); + public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE) { + return $this->storage->getTranslationLanguages($include_default, $include_removed); } /** @@ -1046,6 +1047,48 @@ public function isTranslatable() { } /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::getOriginal(). + */ + public function getOriginal() { + return $this->storage->getOriginal(); + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::hasTranslation(). + */ + public function hasTranslation($langcode) { + return $this->storage->hasTranslation($langcode); + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::addTranslation(). + */ + public function addTranslation($langcode, array $values = array()) { + return $this->storage->addTranslation($langcode, $values); + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::removeTranslation(). + */ + public function removeTranslation($langcode) { + $this->storage->removeTranslation($langcode); + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationStatus(). + */ + public function getTranslationStatus() { + return $this->storage->getTranslationStatus(); + } + + /** + * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationStatus(). + */ + public function initTranslation($langcode) { + $this->storage->initTranslation($langcode); + } + + /** * Implements \Drupal\Core\TypedData\TypedDataInterface::getType(). */ public function getType() {