diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 5447e90..dcf358b 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -62,20 +62,6 @@
protected $fields = array();
/**
- * Local cache for the entity language.
- *
- * @var \Drupal\Core\Language\Language
- */
- protected $language;
-
- /**
- * Local cache for the available language objects.
- *
- * @var array
- */
- protected $languages;
-
- /**
* Local cache for field definitions.
*
* @see ContentEntityBase::getPropertyDefinitions()
@@ -92,6 +78,13 @@
protected $uriPlaceholderReplacements;
/**
+ * Local cache for the available language objects.
+ *
+ * @var array
+ */
+ protected $languages;
+
+ /**
* Language code identifying the entity active language.
*
* This is the language field accessors will use to determine which field
@@ -102,6 +95,13 @@
protected $activeLangcode = Language::LANGCODE_DEFAULT;
/**
+ * Local cache for the default language code.
+ *
+ * @var string
+ */
+ protected $defaultLangcode;
+
+ /**
* An array of entity translation metadata.
*
* An associative array keyed by translation language code. Every value is an
@@ -150,14 +150,14 @@ public function __construct(array $values, $entity_type, $bundle = FALSE, $trans
$this->values[$key] = $value;
}
- // Initialize translations. Ensure we have at least an entry for the entity
- // original language.
+ // Initialize translations. Ensure we have at least an entry for the default
+ // language.
$data = array('status' => static::TRANSLATION_EXISTING);
$this->translations[Language::LANGCODE_DEFAULT] = $data;
+ $this->setDefaultLangcode();
if ($translations) {
- $default_langcode = $this->language()->id;
foreach ($translations as $langcode) {
- if ($langcode != $default_langcode && $langcode != Language::LANGCODE_DEFAULT) {
+ if ($langcode != $this->defaultLangcode && $langcode != Language::LANGCODE_DEFAULT) {
$this->translations[$langcode] = $data;
}
}
@@ -385,50 +385,56 @@ public function get($property_name) {
*
* @return \Drupal\Core\Field\FieldItemListInterface
*/
- protected function getTranslatedField($property_name, $langcode) {
+ protected function getTranslatedField($name, $langcode) {
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
$message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
throw new \InvalidArgumentException(format_string($message, array('@langcode' => $this->activeLangcode)));
}
// Populate $this->fields to speed-up further look-ups and to keep track of
// fields objects, possibly holding changes to field values.
- if (!isset($this->fields[$property_name][$langcode])) {
- $definition = $this->getPropertyDefinition($property_name);
+ if (!isset($this->fields[$name][$langcode])) {
+ $definition = $this->getPropertyDefinition($name);
if (!$definition) {
- throw new \InvalidArgumentException('Field ' . check_plain($property_name) . ' is unknown.');
+ throw new \InvalidArgumentException('Field ' . check_plain($name) . ' is unknown.');
}
// Non-translatable fields are always stored with
// Language::LANGCODE_DEFAULT as key.
- if ($langcode != Language::LANGCODE_DEFAULT && empty($definition['translatable'])) {
- if (!isset($this->fields[$property_name][Language::LANGCODE_DEFAULT])) {
- $this->fields[$property_name][Language::LANGCODE_DEFAULT] = $this->getTranslatedField($property_name, Language::LANGCODE_DEFAULT);
+ $default = $langcode == Language::LANGCODE_DEFAULT;
+ if (!$default && empty($definition['translatable'])) {
+ if (!isset($this->fields[$name][Language::LANGCODE_DEFAULT])) {
+ $this->fields[$name][Language::LANGCODE_DEFAULT] = $this->getTranslatedField($name, Language::LANGCODE_DEFAULT);
}
- $this->fields[$property_name][$langcode] = &$this->fields[$property_name][Language::LANGCODE_DEFAULT];
+ $this->fields[$name][$langcode] = &$this->fields[$name][Language::LANGCODE_DEFAULT];
}
else {
$value = NULL;
- if (isset($this->values[$property_name][$langcode])) {
- $value = $this->values[$property_name][$langcode];
+ if (isset($this->values[$name][$langcode])) {
+ $value = $this->values[$name][$langcode];
+ }
+ $field = \Drupal::typedData()->getPropertyInstance($this, $name, $value);
+ if ($default) {
+ // $this->defaultLangcode might not be set if we are initializing the
+ // default language code cache, in which case there is no valid
+ // langcode to assign.
+ $field_langcode = isset($this->defaultLangcode) ? $this->defaultLangcode : Language::LANGCODE_NOT_SPECIFIED;
}
- $field = \Drupal::typedData()->getPropertyInstance($this, $property_name, $value);
- $field->setLangcode($langcode);
- $this->fields[$property_name][$langcode] = $field;
+ else {
+ $field_langcode = $langcode;
+ }
+ $field->setLangcode($field_langcode);
+ $this->fields[$name][$langcode] = $field;
}
}
- return $this->fields[$property_name][$langcode];
+ return $this->fields[$name][$langcode];
}
/**
* {@inheritdoc}
*/
- public function set($property_name, $value, $notify = TRUE) {
- $this->get($property_name)->setValue($value, FALSE);
-
- if ($property_name == 'langcode') {
- // Avoid using unset as this unnecessarily triggers magic methods later
- // on.
- $this->language = NULL;
- }
+ public function set($name, $value, $notify = TRUE) {
+ // If default language changes we need to react to that.
+ $notify = $name == 'langcode';
+ $this->get($name)->setValue($value, $notify);
}
/**
@@ -530,47 +536,63 @@ public function access($operation = 'view', AccountInterface $account = NULL) {
* {@inheritdoc}
*/
public function language() {
+ $language = NULL;
if ($this->activeLangcode != Language::LANGCODE_DEFAULT) {
if (!isset($this->languages[$this->activeLangcode])) {
$this->languages += language_list(Language::STATE_ALL);
}
- return $this->languages[$this->activeLangcode];
+ $language = $this->languages[$this->activeLangcode];
}
else {
- return $this->language ?: $this->getDefaultLanguage();
+ $language = $this->languages[$this->defaultLangcode];
}
+ return $language;
}
/**
- * Returns the entity original language.
+ * Populates the local cache for the default language code.
+ */
+ protected function setDefaultLangcode() {
+ // Get the language code if the property exists.
+ if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
+ $this->defaultLangcode = $item->language->id;
+ }
+ if (empty($this->defaultLangcode)) {
+ // Make sure we return a proper language object.
+ $this->defaultLangcode = Language::LANGCODE_NOT_SPECIFIED;
+ }
+ // This needs to be initialized manually as it is skipped when instantiating
+ // the language field object to avoid infinite recursion.
+ if (!empty($this->fields['langcode'])) {
+ $this->fields['langcode'][Language::LANGCODE_DEFAULT]->setLangcode($this->defaultLangcode);
+ }
+ }
+
+ /**
+ * Updates language for already instantiated fields.
*
* @return \Drupal\Core\Language\Language
* A language object.
*/
- protected function getDefaultLanguage() {
- // Keep a local cache of the language object and clear it if the langcode
- // gets changed, see ContentEntityBase::onChange().
- if (!isset($this->language)) {
- // Get the language code if the property exists.
- if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
- $this->language = $item->language;
- }
- if (empty($this->language)) {
- // Make sure we return a proper language object.
- $this->language = new Language(array('id' => Language::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE));
+ protected function updateFieldLangcodes($langcode) {
+ foreach ($this->fields as $name => $items) {
+ if (!empty($items[Language::LANGCODE_DEFAULT])) {
+ $items[Language::LANGCODE_DEFAULT]->setLangcode($langcode);
}
}
- return $this->language;
}
/**
* {@inheritdoc}
*/
- public function onChange($property_name) {
- if ($property_name == 'langcode') {
- // Avoid using unset as this unnecessarily triggers magic methods later
- // on.
- $this->language = NULL;
+ public function onChange($name) {
+ if ($name == 'langcode') {
+ $this->setDefaultLangcode();
+ if (isset($this->translations[$this->defaultLangcode])) {
+ $message = format_string('A translation already exists for the specified language (@langcode).', array('@langcode' => $this->defaultLangcode));
+ throw new \InvalidArgumentException($message);
+ }
+ $this->updateFieldLangcodes($this->defaultLangcode);
}
}
@@ -582,11 +604,8 @@ public function onChange($property_name) {
public function getTranslation($langcode) {
// Ensure we always use the default language code when dealing with the
// original entity language.
- if ($langcode != Language::LANGCODE_DEFAULT) {
- $default_language = $this->language ?: $this->getDefaultLanguage();
- if ($langcode == $default_language->id) {
- $langcode = Language::LANGCODE_DEFAULT;
- }
+ if ($langcode != Language::LANGCODE_DEFAULT && $langcode == $this->defaultLangcode) {
+ $langcode = Language::LANGCODE_DEFAULT;
}
// Populate entity translation object cache so it will be available for all
@@ -608,12 +627,11 @@ public function getTranslation($langcode) {
else {
// If we were given a valid language and there is no translation for it,
// we return a new one.
- $languages = language_list(Language::STATE_ALL);
- if (isset($languages[$langcode])) {
+ if (isset($this->languages[$langcode])) {
// If the entity or the requested language is not a configured
// language, we fall back to the entity itself, since in this case it
// cannot have translations.
- $translation = empty($this->getDefaultLanguage()->locked) && empty($languages[$langcode]->locked) ? $this->addTranslation($langcode) : $this;
+ $translation = empty($this->languages[$this->defaultLangcode]->locked) && empty($this->languages[$langcode]->locked) ? $this->addTranslation($langcode) : $this;
}
}
}
@@ -674,8 +692,7 @@ protected function initializeTranslation($langcode) {
* {@inheritdoc}
*/
public function hasTranslation($langcode) {
- $default_language = $this->language ?: $this->getDefaultLanguage();
- if ($langcode == $default_language->id) {
+ if ($langcode == $this->defaultLangcode) {
$langcode = Language::LANGCODE_DEFAULT;
}
return !empty($this->translations[$langcode]['status']);
@@ -685,8 +702,7 @@ public function hasTranslation($langcode) {
* {@inheritdoc}
*/
public function addTranslation($langcode, array $values = array()) {
- $languages = language_list(Language::STATE_ALL);
- if (!isset($languages[$langcode]) || $this->hasTranslation($langcode)) {
+ if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode)) {
$message = 'Invalid translation language (@langcode) specified.';
throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode)));
}
@@ -722,7 +738,7 @@ public function addTranslation($langcode, array $values = array()) {
* {@inheritdoc}
*/
public function removeTranslation($langcode) {
- if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->getDefaultLanguage()->id) {
+ if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) {
foreach ($this->getPropertyDefinitions() as $name => $definition) {
if (!empty($definition['translatable'])) {
unset($this->values[$name][$langcode]);
@@ -741,7 +757,7 @@ public function removeTranslation($langcode) {
* {@inheritdoc}
*/
public function initTranslation($langcode) {
- if ($langcode != Language::LANGCODE_DEFAULT && $langcode != $this->getDefaultLanguage()->id) {
+ if ($langcode != Language::LANGCODE_DEFAULT && $langcode != $this->defaultLangcode) {
$this->translations[$langcode]['status'] = static::TRANSLATION_EXISTING;
}
}
@@ -754,12 +770,11 @@ public function getTranslationLanguages($include_default = TRUE) {
unset($translations[Language::LANGCODE_DEFAULT]);
if ($include_default) {
- $langcode = $this->getDefaultLanguage()->id;
- $translations[$langcode] = TRUE;
+ $translations[$this->defaultLangcode] = TRUE;
}
// Now load language objects based upon translation langcodes.
- return array_intersect_key(language_list(Language::STATE_ALL), $translations);
+ return array_intersect_key($this->languages, $translations);
}
/**
@@ -911,14 +926,11 @@ public function __clone() {
}
}
- // Ensure the translations array is actually cloned by removing the
- // original reference and re-creating its values.
+ // Ensure the translations array is actually cloned by overwriting the
+ // original reference with one pointing to a copy of the array.
$this->clearTranslationCache();
$translations = $this->translations;
- unset($this->translations);
- // This will trigger the magic setter as the translations array is
- // undefined now.
- $this->translations = $translations;
+ $this->translations = &$translations;
}
}
diff --git a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
index 5497efb..0c482b0 100644
--- a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
@@ -325,7 +325,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
// Get the revision IDs.
$revision_ids = array();
foreach ($entities as $values) {
- $revision_ids[] = $values[$this->revisionKey];
+ $revision_ids[] = $values[$this->revisionKey][Language::LANGCODE_DEFAULT];
}
$query->condition($this->revisionKey, $revision_ids);
}
@@ -849,14 +849,13 @@ protected function doLoadFieldItems($entities, $age) {
}
// Load field data.
- $all_langcodes = array_keys(language_list());
+ $langcodes = array_keys(language_list(Language::STATE_ALL));
foreach ($fields as $field_name => $field) {
$table = $load_current ? static::_fieldTableName($field) : static::_fieldRevisionTableName($field);
- // If the field is translatable ensure that only values having valid
- // languages are retrieved. Since we are loading values for multiple
- // entities, we cannot limit the query to the available translations.
- $langcodes = $field->isFieldTranslatable() ? $all_langcodes : array(Language::LANGCODE_NOT_SPECIFIED);
+ // Ensure that only values having valid languages are retrieved. Since we
+ // are loading values for multiple entities, we cannot limit the query to
+ // the available translations.
$results = $this->database->select($table, 't')
->fields('t')
->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
@@ -867,23 +866,29 @@ protected function doLoadFieldItems($entities, $age) {
$delta_count = array();
foreach ($results as $row) {
- if (!isset($delta_count[$row->entity_id][$row->langcode])) {
- $delta_count[$row->entity_id][$row->langcode] = 0;
- }
-
- if ($field->getFieldCardinality() == FieldInterface::CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->langcode] < $field->getFieldCardinality()) {
- $item = array();
- // For each column declared by the field, populate the item from the
- // prefixed database column.
- foreach ($field->getColumns() as $column => $attributes) {
- $column_name = static::_fieldColumnName($field, $column);
- // Unserialize the value if specified in the column schema.
- $item[$column] = (!empty($attributes['serialize'])) ? unserialize($row->$column_name) : $row->$column_name;
+ // Ensure that records for non-translatable fields having invalid
+ // languages are skipped.
+ // @todo Remove BC support for 'und' untranslatable fields as soon as
+ // can write a migration. See https://drupal.org/node/2137917.
+ if ($field->isFieldTranslatable() || $row->langcode == Language::LANGCODE_NOT_SPECIFIED || $row->langcode == $entities[$row->entity_id]->getUntranslated()->language()->id) {
+ if (!isset($delta_count[$row->entity_id][$row->langcode])) {
+ $delta_count[$row->entity_id][$row->langcode] = 0;
}
- // Add the item to the field values for the entity.
- $entities[$row->entity_id]->getTranslation($row->langcode)->{$field_name}[$delta_count[$row->entity_id][$row->langcode]] = $item;
- $delta_count[$row->entity_id][$row->langcode]++;
+ if ($field->getFieldCardinality() == FieldInterface::CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->langcode] < $field->getFieldCardinality()) {
+ $item = array();
+ // For each column declared by the field, populate the item from the
+ // prefixed database column.
+ foreach ($field->getColumns() as $column => $attributes) {
+ $column_name = static::_fieldColumnName($field, $column);
+ // Unserialize the value if specified in the column schema.
+ $item[$column] = (!empty($attributes['serialize'])) ? unserialize($row->$column_name) : $row->$column_name;
+ }
+
+ // Add the item to the field values for the entity.
+ $entities[$row->entity_id]->getTranslation($row->langcode)->{$field_name}[$delta_count[$row->entity_id][$row->langcode]] = $item;
+ $delta_count[$row->entity_id][$row->langcode]++;
+ }
}
}
}
@@ -897,6 +902,9 @@ protected function doSaveFieldItems(EntityInterface $entity, $update) {
$id = $entity->id();
$bundle = $entity->bundle();
$entity_type = $entity->entityType();
+ $default_langcode = $entity->getUntranslated()->language()->id;
+ $translation_langcodes = array_keys($entity->getTranslationLanguages());
+
if (!isset($vid)) {
$vid = $id;
}
@@ -930,7 +938,7 @@ protected function doSaveFieldItems(EntityInterface $entity, $update) {
$query = $this->database->insert($table_name)->fields($columns);
$revision_query = $this->database->insert($revision_name)->fields($columns);
- $langcodes = $field->isFieldTranslatable() ? array_keys($entity->getTranslationLanguages()) : array(Language::LANGCODE_NOT_SPECIFIED);
+ $langcodes = $field->isFieldTranslatable() ? $translation_langcodes : array($default_langcode);
foreach ($langcodes as $langcode) {
$delta_count = 0;
$items = $entity->getTranslation($langcode)->get($field_name);
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index 00dcaba..da50ce1 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -39,7 +39,7 @@ class FieldItemList extends ItemList implements FieldItemListInterface {
*
* @var string
*/
- protected $langcode = Language::LANGCODE_DEFAULT;
+ protected $langcode = Language::LANGCODE_NOT_SPECIFIED;
/**
* Overrides TypedData::__construct().
diff --git a/core/lib/Drupal/Core/Language/Language.php b/core/lib/Drupal/Core/Language/Language.php
index b5ea710..e6709b6 100644
--- a/core/lib/Drupal/Core/Language/Language.php
+++ b/core/lib/Drupal/Core/Language/Language.php
@@ -60,10 +60,10 @@ class Language {
/**
* Language code referring to the default language of data, e.g. of an entity.
*
- * @todo: Change value to differ from Language::LANGCODE_NOT_SPECIFIED once
- * field API leverages the property API.
+ * See the BCP 47 syntax for defining private language tags:
+ * http://www.rfc-editor.org/rfc/bcp/bcp47.txt
*/
- const LANGCODE_DEFAULT = 'und';
+ const LANGCODE_DEFAULT = 'x-default';
/**
* The language state when referring to configurable languages.
diff --git a/core/modules/aggregator/aggregator.install b/core/modules/aggregator/aggregator.install
index f891444..b47dc01 100644
--- a/core/modules/aggregator/aggregator.install
+++ b/core/modules/aggregator/aggregator.install
@@ -312,7 +312,7 @@ function aggregator_update_8001() {
'length' => 12,
'not null' => TRUE,
'default' => '',
- 'initial' => Language::LANGCODE_DEFAULT,
+ 'initial' => Language::LANGCODE_NOT_SPECIFIED,
));
db_add_field('aggregator_item', 'langcode', array(
'description' => 'The {language}.langcode of this feed item.',
@@ -320,6 +320,6 @@ function aggregator_update_8001() {
'length' => 12,
'not null' => TRUE,
'default' => '',
- 'initial' => Language::LANGCODE_DEFAULT,
+ 'initial' => Language::LANGCODE_NOT_SPECIFIED,
));
}
diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index 6d02b9d..4c767f0 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -331,9 +331,6 @@ function content_translation_form_language_content_settings_submit(array $form,
* language_save_default_configuration().
* - fields: An associative array with field names as keys and a boolean as
* value, indicating field translatability.
- *
- * @todo Remove this migration entirely once the Field API is converted to the
- * Entity Field API.
*/
function _content_translation_update_field_translatability($settings) {
$fields = array();
@@ -346,224 +343,15 @@ function _content_translation_update_field_translatability($settings) {
foreach ($bundle_settings['fields'] as $field_name => $translatable) {
// If a field is enabled for translation for at least one instance we
// need to mark it as translatable.
- if (FieldService::fieldInfo()->getField($entity_type, $field_name)) {
- $fields[$entity_type][$field_name] = $translatable || !empty($fields[$entity_type][$field_name]);
+ $field = FieldService::fieldInfo()->getField($entity_type, $field_name);
+ if ($field && $field->isFieldTranslatable() !== $translatable) {
+ $field->translatable = $translatable;
+ $field->save();
}
}
}
}
}
- $operations = array();
- foreach ($fields as $entity_type => $entity_type_fields) {
- foreach ($entity_type_fields as $field_name => $translatable) {
- $field = field_info_field($entity_type, $field_name);
- if ($field->isFieldTranslatable() != $translatable) {
- // If a field is untranslatable, it can have no data except under
- // Language::LANGCODE_NOT_SPECIFIED. Thus we need a field to be translatable before
- // we convert data to the entity language. Conversely we need to switch
- // data back to Language::LANGCODE_NOT_SPECIFIED before making a field
- // untranslatable lest we lose information.
- $field_operations = array(
- array('content_translation_translatable_switch', array($translatable, $entity_type, $field_name)),
- );
- if ($field->hasData()) {
- $field_operations[] = array('content_translation_translatable_batch', array($translatable, $field_name));
- $field_operations = $translatable ? $field_operations : array_reverse($field_operations);
- }
- $operations = array_merge($operations, $field_operations);
- }
- }
- }
-
- // As last operation store the submitted settings.
- $operations[] = array('content_translation_save_settings', array($settings));
-
- $batch = array(
- 'title' => t('Updating translatability for the selected fields'),
- 'operations' => $operations,
- 'finished' => 'content_translation_translatable_batch_done',
- 'file' => drupal_get_path('module', 'content_translation') . '/content_translation.admin.inc',
- );
- batch_set($batch);
-}
-
-/**
- * Toggles translatability of the given field.
- *
- * This is called from a batch operation, but should only run once per field.
- *
- * @param bool $translatable
- * Indicator of whether the field should be made translatable (TRUE) or
- * untranslatble (FALSE).
- * @param string $entity_type
- * Field entity type.
- * @param string $field_name
- * Field machine name.
- */
-function content_translation_translatable_switch($translatable, $entity_type, $field_name) {
- $field = field_info_field($entity_type, $field_name);
- if ($field->isFieldTranslatable() !== $translatable) {
- $field->translatable = $translatable;
- $field->save();
- }
+ content_translation_save_settings($settings);
}
-
-/**
- * Batch callback: Converts field data to or from Language::LANGCODE_NOT_SPECIFIED.
- *
- * @param bool $translatable
- * Indicator of whether the field should be made translatable (TRUE) or
- * untranslatble (FALSE).
- * @param string $field_name
- * Field machine name.
- */
-function content_translation_translatable_batch($translatable, $field_name, &$context) {
- // Determine the entity types to act on.
- $entity_types = array();
- foreach (field_info_instances() as $entity_type => $info) {
- foreach ($info as $bundle => $instances) {
- foreach ($instances as $instance_field_name => $instance) {
- if ($instance_field_name == $field_name) {
- $entity_types[] = $entity_type;
- break 2;
- }
- }
- }
- }
-
- if (empty($context['sandbox'])) {
- $context['sandbox']['progress'] = 0;
- $context['sandbox']['max'] = 0;
-
- foreach ($entity_types as $entity_type) {
- $field = field_info_field($entity_type, $field_name);
- $columns = $field->getColumns();
- $column = isset($columns['value']) ? 'value' : key($columns);
- $query_field = "$field_name.$column";
-
- // How many entities will need processing?
- $query = \Drupal::entityQuery($entity_type);
- $count = $query
- ->exists($query_field)
- ->count()
- ->execute();
-
- $context['sandbox']['max'] += $count;
- $context['sandbox']['progress_entity_type'][$entity_type] = 0;
- $context['sandbox']['max_entity_type'][$entity_type] = $count;
- }
-
- if ($context['sandbox']['max'] === 0) {
- // Nothing to do.
- $context['finished'] = 1;
- return;
- }
- }
-
- foreach ($entity_types as $entity_type) {
- if ($context['sandbox']['max_entity_type'][$entity_type] === 0) {
- continue;
- }
-
- $info = entity_get_info($entity_type);
- $offset = $context['sandbox']['progress_entity_type'][$entity_type];
- $query = \Drupal::entityQuery($entity_type);
- $field = field_info_field($entity_type, $field_name);
- $columns = $field->getColumns();
- $column = isset($columns['value']) ? 'value' : key($columns);
- $query_field = "$field_name.$column";
- $result = $query
- ->exists($query_field)
- ->sort($info['entity_keys']['id'])
- ->range($offset, 10)
- ->execute();
-
- foreach (entity_load_multiple($entity_type, $result) as $id => $entity) {
- $context['sandbox']['max_entity_type'][$entity_type] -= count($result);
- $context['sandbox']['progress_entity_type'][$entity_type]++;
- $context['sandbox']['progress']++;
- $langcode = $entity->language()->id;
-
- // Skip process for language neutral entities.
- if ($langcode == Language::LANGCODE_NOT_SPECIFIED) {
- continue;
- }
-
- // We need a two-step approach while updating field translations: given
- // that field-specific update functions might rely on the stored values to
- // perform their processing first we need to store the new translations
- // and only after we can remove the old ones. Otherwise we might have data
- // loss, since the removal of the old translations might occur before the
- // new ones are stored.
- if ($translatable && isset($entity->{$field_name}[Language::LANGCODE_NOT_SPECIFIED])) {
- // If the field is being switched to translatable and has data for
- // Language::LANGCODE_NOT_SPECIFIED then we need to move the data to the right
- // language.
- $entity->{$field_name}[$langcode] = $entity->{$field_name}[Language::LANGCODE_NOT_SPECIFIED];
- // Store the original value.
- _content_translation_update_field($entity_type, $entity, $field_name);
- $entity->{$field_name}[Language::LANGCODE_NOT_SPECIFIED] = array();
- // Remove the language neutral value.
- _content_translation_update_field($entity_type, $entity, $field_name);
- }
- elseif (!$translatable && isset($entity->{$field_name}[$langcode])) {
- // The field has been marked untranslatable and has data in the entity
- // language: we need to move it to Language::LANGCODE_NOT_SPECIFIED and drop the
- // other translations.
- $entity->{$field_name}[Language::LANGCODE_NOT_SPECIFIED] = $entity->{$field_name}[$langcode];
- // Store the original value.
- _content_translation_update_field($entity_type, $entity, $field_name);
- // Remove translations.
- foreach ($entity->{$field_name} as $langcode => $items) {
- if ($langcode != Language::LANGCODE_NOT_SPECIFIED) {
- $entity->{$field_name}[$langcode] = array();
- }
- }
- _content_translation_update_field($entity_type, $entity, $field_name);
- }
- else {
- // No need to save unchanged entities.
- continue;
- }
- }
- }
-
- $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
-}
-
-/**
- * Stores the given field translations.
- */
-function _content_translation_update_field($entity_type, EntityInterface $entity, $field_name) {
- $empty = 0;
- $translations = $entity->getTranslationLanguages();
-
- // Ensure that we are trying to store only valid data.
- foreach (array_keys($translations) as $langcode) {
- $items = $entity->getTranslation($langcode)->get($field_name);
- $items->filterEmptyValues();
- $empty += $items->isEmpty();
- }
-
- // Save the field value only if there is at least one item available,
- // otherwise any stored empty field value would be deleted. If this happens
- // the range queries would be messed up.
- if ($empty < count($translations)) {
- $entity->save();
- }
-}
-
-/**
- * Batch finished callback: Checks the exit status of the batch operation.
- */
-function content_translation_translatable_batch_done($success, $results, $operations) {
- if ($success) {
- drupal_set_message(t("Successfully changed field translation setting."));
- }
- else {
- // @todo: Do something about this case.
- drupal_set_message(t("Something went wrong while processing data. Some nodes may appear to have lost fields."), 'error');
- }
-}
-
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 43127b0..747ce68 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -79,10 +79,6 @@ function content_translation_entity_info_alter(array &$entity_info) {
// Provide defaults for translation info.
foreach ($entity_info as $entity_type => &$info) {
if (!empty($info['translatable'])) {
- // Every fieldable entity type must have a translation controller class,
- // no matter if it is enabled for translation or not. As a matter of fact
- // we might need it to correctly switch field translatability when a field
- // is shared accross different entities.
$info['controllers'] += array('translation' => 'Drupal\content_translation\ContentTranslationController');
if (!isset($info['translation']['content_translation'])) {
@@ -207,12 +203,6 @@ function content_translation_menu() {
}
}
- $items['admin/config/regional/content_translation/translatable/%/%'] = array(
- 'title' => 'Confirm change in translatability.',
- 'description' => 'Confirm page for changing field translatability.',
- 'route_name' => 'content_translation.translatable',
- );
-
return $items;
}
@@ -801,36 +791,12 @@ function content_translation_field_extra_fields() {
* Implements hook_form_FORM_ID_alter() for 'field_ui_field_edit_form'.
*/
function content_translation_form_field_ui_field_edit_form_alter(array &$form, array &$form_state, $form_id) {
- $field = $form['#field'];
- $field_name = $field->getFieldName();
- $translatable = $field->isFieldTranslatable();
- $entity_type = $field->entity_type;
- $label = t('Field translation');
-
- if ($field->hasData()) {
- $form['field']['translatable'] = array(
- '#type' => 'item',
- '#title' => $label,
- '#attributes' => array('class' => 'translatable'),
- 'link' => array(
- '#type' => 'link',
- '#prefix' => t('This field has data in existing content.') . ' ',
- '#title' => !$translatable ? t('Enable translation') : t('Disable translation'),
- '#href' => "admin/config/regional/content_translation/translatable/$entity_type/$field_name",
- '#options' => array('query' => drupal_get_destination()),
- '#access' => user_access('administer content translation'),
- ),
- );
- }
- else {
- $form['field']['translatable'] = array(
- '#type' => 'checkbox',
- '#title' => t('Users may translate this field.'),
- '#default_value' => $translatable,
- );
- }
-
- $form['field']['translatable']['#weight'] = 20;
+ $form['field']['translatable'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Users may translate this field.'),
+ '#default_value' => $form['#field']->isFieldTranslatable(),
+ '#weight' => 20,
+ );
}
/**
diff --git a/core/modules/content_translation/content_translation.routing.yml b/core/modules/content_translation/content_translation.routing.yml
deleted file mode 100644
index 51db4a3..0000000
--- a/core/modules/content_translation/content_translation.routing.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-content_translation.translatable:
- path: '/admin/config/regional/content_translation/translatable/{entity_type}/{field_name}'
- defaults:
- _form: 'Drupal\content_translation\Form\TranslatableForm'
- requirements:
- _permission: 'administer content translation'
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Form/TranslatableForm.php b/core/modules/content_translation/lib/Drupal/content_translation/Form/TranslatableForm.php
deleted file mode 100644
index 363cb1c..0000000
--- a/core/modules/content_translation/lib/Drupal/content_translation/Form/TranslatableForm.php
+++ /dev/null
@@ -1,156 +0,0 @@
-field->isFieldTranslatable()) {
- $question = t('Are you sure you want to disable translation for the %name field?', array('%name' => $this->fieldName));
- }
- else {
- $question = t('Are you sure you want to enable translation for the %name field?', array('%name' => $this->fieldName));
- }
- return $question;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getDescription() {
- $description = t('By submitting this form these changes will apply to the %name field everywhere it is used.',
- array('%name' => $this->fieldName)
- );
- $description .= $this->field->isFieldTranslatable() ? "
" . t("All the existing translations of this field will be deleted.
This action cannot be undone.") : '';
- return $description;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getCancelRoute() {
- return array(
- 'route_name' => '',
- );
- }
-
- /**
- * {@inheritdoc}
- * @param string $entity_type
- * The entity type.
- * @param string $field_name
- * The field name.
- */
- public function buildForm(array $form, array &$form_state, $entity_type = NULL, $field_name = NULL) {
- $this->fieldName = $field_name;
- $this->fieldInfo = FieldInfo::fieldInfo()->getField($entity_type, $field_name);
-
- return parent::buildForm($form, $form_state);
- }
-
- /**
- * Form submission handler.
- *
- * This submit handler maintains consistency between the translatability of an
- * entity and the language under which the field data is stored. When a field
- * is marked as translatable, all the data in
- * $entity->{field_name}[Language::LANGCODE_NOT_SPECIFIED] is moved to
- * $entity->{field_name}[$entity_language]. When a field is marked as
- * untranslatable the opposite process occurs. Note that marking a field as
- * untranslatable will cause all of its translations to be permanently
- * removed, with the exception of the one corresponding to the entity
- * language.
- *
- * @param array $form
- * An associative array containing the structure of the form.
- * @param array $form_state
- * An associative array containing the current state of the form.
- */
- public function submitForm(array &$form, array &$form_state) {
- // This is the current state that we want to reverse.
- $translatable = $form_state['values']['translatable'];
- if ($this->field->translatable !== $translatable) {
- // Field translatability has changed since form creation, abort.
- $t_args = array('%field_name');
- $msg = $translatable ?
- t('The field %field_name is already translatable. No change was performed.', $t_args):
- t('The field %field_name is already untranslatable. No change was performed.', $t_args);
- drupal_set_message($msg, 'warning');
- return;
- }
-
- // If a field is untranslatable, it can have no data except under
- // Language::LANGCODE_NOT_SPECIFIED. Thus we need a field to be translatable
- // before we convert data to the entity language. Conversely we need to
- // switch data back to Language::LANGCODE_NOT_SPECIFIED before making a
- // field untranslatable lest we lose information.
- $operations = array(
- array(
- 'content_translation_translatable_batch', array(
- !$translatable,
- $this->fieldName,
- ),
- ),
- array(
- 'content_translation_translatable_switch', array(
- !$translatable,
- $this->field['entity_type'],
- $this->fieldName,
- ),
- ),
- );
- $operations = $translatable ? $operations : array_reverse($operations);
-
- $t_args = array('%field' => $this->fieldName);
- $title = !$translatable ? t('Enabling translation for the %field field', $t_args) : t('Disabling translation for the %field field', $t_args);
-
- $batch = array(
- 'title' => $title,
- 'operations' => $operations,
- 'finished' => 'content_translation_translatable_batch_done',
- 'file' => drupal_get_path('module', 'content_translation') . '/content_translation.admin.inc',
- );
-
- batch_set($batch);
-
- }
-
-}
diff --git a/core/modules/field/field.multilingual.inc b/core/modules/field/field.multilingual.inc
index 68493c7..2ea2bf1 100644
--- a/core/modules/field/field.multilingual.inc
+++ b/core/modules/field/field.multilingual.inc
@@ -21,11 +21,11 @@
* @endcode
* Every field can hold a single or multiple value for each language code
* belonging to the available language codes set:
- * - For untranslatable fields this set only contains Language::LANGCODE_NOT_SPECIFIED.
+ * - For untranslatable fields this set is only Language::LANGCODE_DEFAULT.
* - For translatable fields this set can contain any language code. By default
* it is the list returned by field_content_languages(), which contains all
- * installed languages with the addition of Language::LANGCODE_NOT_SPECIFIED. This
- * default can be altered by modules implementing
+ * installed languages with the addition of Language::LANGCODE_NOT_SPECIFIED.
+ * This default can be altered by modules implementing
* hook_field_available_languages_alter().
*
* The available language codes for a particular field are returned by
@@ -69,10 +69,10 @@
* Collects the available language codes for the given entity type and field.
*
* If the given field has language support enabled, an array of available
- * language codes will be returned, otherwise only Language::LANGCODE_NOT_SPECIFIED will
- * be returned. Since the default value for a 'translatable' entity property is
- * FALSE, we ensure that only entities that are able to handle translations
- * actually get translatable fields.
+ * language codes will be returned, otherwise only Language::LANGCODE_DEFAULT
+ * will be returned. Since the default value for a 'translatable' entity
+ * property is FALSE, we ensure that only entities that are able to handle
+ * translations actually get translatable fields.
*
* @param $entity_type
* The type of the entity the field is attached to, e.g. 'node' or 'user'.
@@ -92,7 +92,7 @@ function field_available_languages($entity_type, FieldInterface $field) {
if (!isset($field_langcodes[$entity_type][$field_name])) {
// If the field has language support enabled we retrieve an (alterable) list
- // of enabled languages, otherwise we return just Language::LANGCODE_NOT_SPECIFIED.
+ // of enabled languages, otherwise we return Language::LANGCODE_DEFAULT.
if (field_is_translatable($entity_type, $field)) {
$langcodes = field_content_languages();
// Let other modules alter the available languages.
@@ -101,7 +101,7 @@ function field_available_languages($entity_type, FieldInterface $field) {
$field_langcodes[$entity_type][$field_name] = $langcodes;
}
else {
- $field_langcodes[$entity_type][$field_name] = array(Language::LANGCODE_NOT_SPECIFIED);
+ $field_langcodes[$entity_type][$field_name] = array(Language::LANGCODE_DEFAULT);
}
}
diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
index de2dd94..0c02b4f 100644
--- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php
@@ -142,7 +142,7 @@ function testFieldAvailableLanguages() {
$this->field->translatable = FALSE;
$this->field->save();
$available_langcodes = field_available_languages($this->entity_type, $this->field);
- $this->assertTrue(count($available_langcodes) == 1 && $available_langcodes[0] === Language::LANGCODE_NOT_SPECIFIED, 'For untranslatable fields only Language::LANGCODE_NOT_SPECIFIED is available.');
+ $this->assertTrue(count($available_langcodes) == 1 && $available_langcodes[0] === Language::LANGCODE_DEFAULT, 'For untranslatable fields only Language::LANGCODE_DEFAULT is available.');
}
/**
diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php
index bd3472f..7fe938a 100644
--- a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php
+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php
@@ -44,9 +44,9 @@ public function normalize($field, $format = NULL, array $context = array()) {
// in to the field item normalizer in the context. The langcode is appended
// to the field item values.
else {
- foreach ($entity->getTranslationLanguages() as $lang) {
- $context['langcode'] = $lang->id == 'und' ? Language::LANGCODE_DEFAULT : $lang->id;
- $translation = $entity->getTranslation($lang->id);
+ foreach ($entity->getTranslationLanguages() as $language) {
+ $context['langcode'] = $language->id;
+ $translation = $entity->getTranslation($language->id);
$translated_field = $translation->get($field_name);
$normalized_field_items = array_merge($normalized_field_items, $this->normalizeFieldItems($translated_field, $format, $context));
}
diff --git a/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
index 910c14f..32bc279 100644
--- a/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
+++ b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
@@ -206,7 +206,10 @@ public function testPatchDenormailzation() {
// Assert that all fields are NULL and not set to default values. Example:
// the UUID field is NULL and not initialized as usual.
foreach ($denormalized as $field_name => $field) {
- $this->assertFalse(isset($denormalized->$field_name), "$field_name is not set.");
+ // The 'langcode' field always has a value.
+ if ($field_name != 'langcode') {
+ $this->assertFalse(isset($denormalized->$field_name), "$field_name is not set.");
+ }
}
}
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityLanguageTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityLanguageTestBase.php
new file mode 100644
index 0000000..cf38de6
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityLanguageTestBase.php
@@ -0,0 +1,134 @@
+installSchema('system', 'variable');
+ $this->installSchema('entity_test', array(
+ 'entity_test_mul',
+ 'entity_test_mul_property_data',
+ 'entity_test_rev',
+ 'entity_test_rev_revision',
+ 'entity_test_mulrev',
+ 'entity_test_mulrev_revision',
+ 'entity_test_mulrev_property_data',
+ 'entity_test_mulrev_property_revision',
+ ));
+ $this->installConfig(array('language'));
+
+ // Create the test field.
+ entity_test_install();
+
+ // Enable translations for the test entity type.
+ $this->state->set('entity_test.translation', TRUE);
+
+ // Create a translatable test field.
+ $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
+
+ // Create an untranslatable test field.
+ $this->untranslatable_field_name = drupal_strtolower($this->randomName() . '_field_name');
+
+ // Create field instances in all entity variations.
+ foreach (entity_test_entity_types() as $entity_type) {
+ entity_create('field_entity', array(
+ 'name' => $this->field_name,
+ 'entity_type' => $entity_type,
+ 'type' => 'text',
+ 'cardinality' => 4,
+ 'translatable' => TRUE,
+ ))->save();
+ entity_create('field_instance', array(
+ 'field_name' => $this->field_name,
+ 'entity_type' => $entity_type,
+ 'bundle' => $entity_type,
+ ))->save();
+ $this->instance[$entity_type] = field_read_instance($entity_type, $this->field_name, $entity_type);
+
+ entity_create('field_entity', array(
+ 'name' => $this->untranslatable_field_name,
+ 'entity_type' => $entity_type,
+ 'type' => 'text',
+ 'cardinality' => 4,
+ 'translatable' => FALSE,
+ ))->save();
+ entity_create('field_instance', array(
+ 'field_name' => $this->untranslatable_field_name,
+ 'entity_type' => $entity_type,
+ 'bundle' => $entity_type,
+ ))->save();
+ }
+
+ // Create the default languages.
+ $default_language = language_save(language_default());
+ $languages = language_default_locked_languages($default_language->weight);
+ foreach ($languages as $language) {
+ language_save($language);
+ }
+
+ // Create test languages.
+ $this->langcodes = array();
+ for ($i = 0; $i < 3; ++$i) {
+ $language = new Language(array(
+ 'id' => 'l' . $i,
+ 'name' => $this->randomString(),
+ 'weight' => $i,
+ ));
+ $this->langcodes[$i] = $language->id;
+ language_save($language);
+ }
+ }
+
+ /**
+ * Toggles field translatability.
+ *
+ * @param string $entity_type
+ * The type of the entity fields are attached to.
+ */
+ protected function toggleFieldTranslatability($entity_type) {
+ $fields = array($this->field_name, $this->untranslatable_field_name);
+ foreach ($fields as $field_name) {
+ $field = FieldService::fieldInfo()->getField($entity_type, $field_name);
+ $translatable = !$field->isFieldTranslatable();
+ $field->set('translatable', $translatable);
+ $field->save();
+ FieldService::fieldInfo()->flush();
+ $field = FieldService::fieldInfo()->getField($entity_type, $field_name);
+ $this->assertEqual($field->isFieldTranslatable(), $translatable, 'Field translatability changed.');
+ }
+ \Drupal::cache('field')->deleteAll();
+ }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
index bc0e195..cd0718e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
@@ -7,19 +7,14 @@
namespace Drupal\system\Tests\Entity;
+use Drupal\Component\Utility\MapArray;
use Drupal\Core\Language\Language;
-use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\entity_test\Entity\EntityTestMulRev;
-use Drupal\Component\Utility\MapArray;
/**
* Tests entity translation.
*/
-class EntityTranslationTest extends EntityUnitTestBase {
-
- protected $langcodes;
-
- public static $modules = array('language', 'entity_test');
+class EntityTranslationTest extends EntityLanguageTestBase {
public static function getInfo() {
return array(
@@ -29,68 +24,6 @@ public static function getInfo() {
);
}
- function setUp() {
- parent::setUp();
-
- $this->installSchema('system', 'variable');
- $this->installSchema('entity_test', array(
- 'entity_test_mul',
- 'entity_test_mul_property_data',
- 'entity_test_rev',
- 'entity_test_rev_revision',
- 'entity_test_mulrev',
- 'entity_test_mulrev_revision',
- 'entity_test_mulrev_property_data',
- 'entity_test_mulrev_property_revision',
- ));
- $this->installConfig(array('language'));
-
- // Create the test field.
- entity_test_install();
-
- // Enable translations for the test entity type.
- $this->state->set('entity_test.translation', TRUE);
-
- // Create a translatable test field.
- $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
-
- // Create instance in all entity variations.
- foreach (entity_test_entity_types() as $entity_type) {
- entity_create('field_entity', array(
- 'name' => $this->field_name,
- 'entity_type' => $entity_type,
- 'type' => 'text',
- 'cardinality' => 4,
- 'translatable' => TRUE,
- ))->save();
- entity_create('field_instance', array(
- 'field_name' => $this->field_name,
- 'entity_type' => $entity_type,
- 'bundle' => $entity_type,
- ))->save();
- $this->instance[$entity_type] = field_read_instance($entity_type, $this->field_name, $entity_type);
- }
-
- // Create the default languages.
- $default_language = language_save(language_default());
- $languages = language_default_locked_languages($default_language->weight);
- foreach ($languages as $language) {
- language_save($language);
- }
-
- // Create test languages.
- $this->langcodes = array();
- for ($i = 0; $i < 3; ++$i) {
- $language = new Language(array(
- 'id' => 'l' . $i,
- 'name' => $this->randomString(),
- 'weight' => $i,
- ));
- $this->langcodes[$i] = $language->id;
- language_save($language);
- }
- }
-
/**
* Tests language related methods of the Entity class.
*/
@@ -120,7 +53,7 @@ protected function _testEntityLanguageMethods($entity_type) {
// Get the value.
$field = $entity->getTranslation(Language::LANGCODE_DEFAULT)->get($this->field_name);
$this->assertEqual($field->value, 'default value', format_string('%entity_type: Untranslated value retrieved.', array('%entity_type' => $entity_type)));
- $this->assertEqual($field->getLangcode(), Language::LANGCODE_DEFAULT, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($field->getLangcode(), Language::LANGCODE_NOT_SPECIFIED, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
// Set the value in a certain language. As the entity is not
// language-specific it should use the default language and so ignore the
@@ -133,11 +66,12 @@ protected function _testEntityLanguageMethods($entity_type) {
// language-specific entity.
$field = $entity->getTranslation($this->langcodes[1])->get($this->field_name);
$this->assertEqual($field->value, 'default value2', format_string('%entity_type: Untranslated value retrieved.', array('%entity_type' => $entity_type)));
- $this->assertEqual($field->getLangcode(), Language::LANGCODE_DEFAULT, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($field->getLangcode(), Language::LANGCODE_NOT_SPECIFIED, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
// Now, make the entity language-specific by assigning a language and test
// translating it.
- $entity->langcode->value = $this->langcodes[0];
+ $default_langcode = $this->langcodes[0];
+ $entity->langcode->value = $default_langcode;
$entity->{$this->field_name} = array();
$this->assertEqual($entity->language(), language_load($this->langcodes[0]), format_string('%entity_type: Entity language retrieved.', array('%entity_type' => $entity_type)));
$this->assertFalse($entity->getTranslationLanguages(FALSE), format_string('%entity_type: No translations are available', array('%entity_type' => $entity_type)));
@@ -147,7 +81,7 @@ protected function _testEntityLanguageMethods($entity_type) {
// Get the value.
$field = $entity->get($this->field_name);
$this->assertEqual($field->value, 'default value', format_string('%entity_type: Untranslated value retrieved.', array('%entity_type' => $entity_type)));
- $this->assertEqual($field->getLangcode(), Language::LANGCODE_DEFAULT, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($field->getLangcode(), $default_langcode, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
// Set a translation.
$entity->getTranslation($this->langcodes[1])->set($this->field_name, array(0 => array('value' => 'translation 1')));
@@ -158,7 +92,7 @@ protected function _testEntityLanguageMethods($entity_type) {
// Make sure the untranslated value stays.
$field = $entity->get($this->field_name);
$this->assertEqual($field->value, 'default value', 'Untranslated value stays.');
- $this->assertEqual($field->getLangcode(), Language::LANGCODE_DEFAULT, 'Untranslated value has the expected langcode.');
+ $this->assertEqual($field->getLangcode(), $default_langcode, 'Untranslated value has the expected langcode.');
$translations[$this->langcodes[1]] = language_load($this->langcodes[1]);
$this->assertEqual($entity->getTranslationLanguages(FALSE), $translations, 'Translations retrieved.');
@@ -190,7 +124,7 @@ protected function _testEntityLanguageMethods($entity_type) {
// Get the value.
$field = $entity->get($field_name);
$this->assertEqual($field->value, 'default value2', format_string('%entity_type: Untranslated value set into a translation in non-strict mode.', array('%entity_type' => $entity_type)));
- $this->assertEqual($field->getLangcode(), Language::LANGCODE_DEFAULT, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($field->getLangcode(), $default_langcode, format_string('%entity_type: Field object has the expected langcode.', array('%entity_type' => $entity_type)));
}
/**
@@ -219,20 +153,19 @@ protected function _testMultilingualProperties($entity_type) {
$entity = entity_create($entity_type, array('name' => $name, 'user_id' => $uid));
$entity->save();
$entity = entity_load($entity_type, $entity->id());
- $this->assertEqual($entity->language()->id, Language::LANGCODE_NOT_SPECIFIED, format_string('%entity_type: Entity created as language neutral.', array('%entity_type' => $entity_type)));
+ $default_langcode = $entity->language()->id;
+ $this->assertEqual($default_langcode, Language::LANGCODE_NOT_SPECIFIED, format_string('%entity_type: Entity created as language neutral.', array('%entity_type' => $entity_type)));
$field = $entity->getTranslation(Language::LANGCODE_DEFAULT)->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name has been correctly stored as language neutral.', array('%entity_type' => $entity_type)));
- $this->assertEqual(Language::LANGCODE_DEFAULT, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->getTranslation(Language::LANGCODE_DEFAULT)->get('user_id')->target_id, format_string('%entity_type: The entity author has been correctly stored as language neutral.', array('%entity_type' => $entity_type)));
- // As fields, translatable properties should ignore the given langcode and
- // use neutral language if the entity is not translatable.
$field = $entity->getTranslation($langcode)->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name defaults to neutral language.', array('%entity_type' => $entity_type)));
- $this->assertEqual(Language::LANGCODE_DEFAULT, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->getTranslation($langcode)->get('user_id')->target_id, format_string('%entity_type: The entity author defaults to neutral language.', array('%entity_type' => $entity_type)));
$field = $entity->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name can be retrieved without specifying a language.', array('%entity_type' => $entity_type)));
- $this->assertEqual(Language::LANGCODE_DEFAULT, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->get('user_id')->target_id, format_string('%entity_type: The entity author can be retrieved without specifying a language.', array('%entity_type' => $entity_type)));
// Create a language-aware entity and check that properties are stored
@@ -240,20 +173,21 @@ protected function _testMultilingualProperties($entity_type) {
$entity = entity_create($entity_type, array('name' => $name, 'user_id' => $uid, 'langcode' => $langcode));
$entity->save();
$entity = entity_load($entity_type, $entity->id());
- $this->assertEqual($entity->language()->id, $langcode, format_string('%entity_type: Entity created as language specific.', array('%entity_type' => $entity_type)));
+ $default_langcode = $entity->language()->id;
+ $this->assertEqual($default_langcode, $langcode, format_string('%entity_type: Entity created as language specific.', array('%entity_type' => $entity_type)));
$field = $entity->getTranslation($langcode)->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name has been correctly stored as a language-aware property.', array('%entity_type' => $entity_type)));
- $this->assertEqual(Language::LANGCODE_NOT_SPECIFIED, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->getTranslation($langcode)->get('user_id')->target_id, format_string('%entity_type: The entity author has been correctly stored as a language-aware property.', array('%entity_type' => $entity_type)));
// Translatable properties on a translatable entity should use default
// language if Language::LANGCODE_NOT_SPECIFIED is passed.
$field = $entity->getTranslation(Language::LANGCODE_NOT_SPECIFIED)->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name defaults to the default language.', array('%entity_type' => $entity_type)));
- $this->assertEqual(Language::LANGCODE_NOT_SPECIFIED, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->getTranslation(Language::LANGCODE_NOT_SPECIFIED)->get('user_id')->target_id, format_string('%entity_type: The entity author defaults to the default language.', array('%entity_type' => $entity_type)));
$field = $entity->get('name');
$this->assertEqual($name, $field->value, format_string('%entity_type: The entity name can be retrieved without specifying a language.', array('%entity_type' => $entity_type)));
- $this->assertEqual(Language::LANGCODE_NOT_SPECIFIED, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
+ $this->assertEqual($default_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expect langcode.', array('%entity_type' => $entity_type)));
$this->assertEqual($uid, $entity->get('user_id')->target_id, format_string('%entity_type: The entity author can be retrieved without specifying a language.', array('%entity_type' => $entity_type)));
// Create property translations.
@@ -285,8 +219,7 @@ protected function _testMultilingualProperties($entity_type) {
);
$field = $entity->getTranslation($langcode)->get('name');
$this->assertEqual($properties[$langcode]['name'][0], $field->value, format_string('%entity_type: The entity name has been correctly stored for language %langcode.', $args));
- // Fields for the default entity langcode are seen as language neutral.
- $field_langcode = ($langcode == $entity->language()->id) ? Language::LANGCODE_NOT_SPECIFIED : $langcode;
+ $field_langcode = ($langcode == $entity->language()->id) ? $default_langcode : $langcode;
$this->assertEqual($field_langcode, $field->getLangcode(), format_string('%entity_type: The field object has the expected langcode %langcode.', $args));
$this->assertEqual($properties[$langcode]['user_id'][0], $entity->getTranslation($langcode)->get('user_id')->target_id, format_string('%entity_type: The entity author has been correctly stored for language %langcode.', $args));
}
@@ -482,6 +415,9 @@ function testEntityTranslationAPI() {
$cloned = clone $entity;
$translation = $cloned->getTranslation($langcode);
$this->assertNotIdentical($entity, $translation->getUntranslated(), 'A cloned entity object has no reference to the original one.');
+ $entity->removeTranslation($langcode);
+ $this->assertFalse($entity->hasTranslation($langcode));
+ $this->assertTrue($cloned->hasTranslation($langcode));
// Check that per-language defaults are properly populated.
$entity = $this->reloadEntity($entity);
@@ -596,4 +532,53 @@ function testFieldDefinitions() {
}
}
+ /**
+ * Tests that changing entity language does not break field language.
+ */
+ public function testLanguageChange() {
+ $entity_type = 'entity_test_mul';
+ $controller = $this->entityManager->getStorageController($entity_type);
+ $langcode = $this->langcodes[0];
+
+ // check that field languages match entity language regardless of field
+ // translatability.
+ $values = array(
+ 'langcode' => $langcode,
+ $this->field_name => $this->randomName(),
+ $this->untranslatable_field_name => $this->randomName(),
+ );
+ $entity = $controller->create($values);
+ foreach (array($this->field_name, $this->untranslatable_field_name) as $field_name) {
+ $this->assertEqual($entity->get($field_name)->getLangcode(), $langcode, 'Field language works as expected.');
+ }
+
+ // Check that field languages keep matching entity language even after
+ // changing it.
+ $langcode = $this->langcodes[1];
+ $entity->langcode->value = $langcode;
+ foreach (array($this->field_name, $this->untranslatable_field_name) as $field_name) {
+ $this->assertEqual($entity->get($field_name)->getLangcode(), $langcode, 'Field language works as expected after changing entity language.');
+ }
+
+ // Check that entity translation does not affect the language of original
+ // field values and untranslatable ones.
+ $langcode = $this->langcodes[0];
+ $entity->addTranslation($this->langcodes[2], array($this->field_name => $this->randomName()));
+ $entity->langcode->value = $langcode;
+ foreach (array($this->field_name, $this->untranslatable_field_name) as $field_name) {
+ $this->assertEqual($entity->get($field_name)->getLangcode(), $langcode, 'Field language works as expected after translating the entity and changing language.');
+ }
+
+ // Check that setting the default language to an existing translation
+ // language causes an exception to be thrown.
+ $message = 'An exception is thrown when setting the default language to an existing translation language';
+ try {
+ $entity->langcode->value = $this->langcodes[2];
+ $this->fail($message);
+ }
+ catch (\InvalidArgumentException $e) {
+ $this->pass($message);
+ }
+ }
+
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/FieldTranslationSqlStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldTranslationSqlStorageTest.php
new file mode 100644
index 0000000..b6790aa
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/FieldTranslationSqlStorageTest.php
@@ -0,0 +1,119 @@
+ 'Field translation SQL storage tests',
+ 'description' => "Test Field translation SQL Storage.",
+ 'group' => 'Entity API'
+ );
+ }
+
+ /**
+ * Tests field SQL storage.
+ */
+ public function testFieldSqlStorage() {
+ $entity_type = 'entity_test_mul';
+
+ $controller = $this->entityManager->getStorageController($entity_type);
+ $values = array(
+ $this->field_name => $this->randomName(),
+ $this->untranslatable_field_name => $this->randomName(),
+ );
+ $entity = $controller->create($values);
+ $entity->save();
+
+ // Tests that when changing language field language codes are still correct.
+ $langcode = $this->langcodes[0];
+ $entity->langcode->value = $langcode;
+ $entity->save();
+ $this->assertFieldStorageLangcode($entity, 'Field language successfully changed from language neutral.');
+ $langcode = $this->langcodes[1];
+ $entity->langcode->value = $langcode;
+ $entity->save();
+ $this->assertFieldStorageLangcode($entity, 'Field language successfully changed.');
+ $langcode = Language::LANGCODE_NOT_SPECIFIED;
+ $entity->langcode->value = $langcode;
+ $entity->save();
+ $this->assertFieldStorageLangcode($entity, 'Field language successfully changed to language neutral.');
+
+ // Test that after switching field translatability things keep working as
+ // before.
+ $this->toggleFieldTranslatability($entity_type);
+ $entity = $this->reloadEntity($entity);
+ foreach (array($this->field_name, $this->untranslatable_field_name) as $field_name) {
+ $this->assertEqual($entity->get($field_name)->value, $values[$field_name], 'Field language works as expected after switching translatability.');
+ }
+
+ // Test that after disabling field translatability translated values are not
+ // loaded.
+ $this->toggleFieldTranslatability($entity_type);
+ $entity = $this->reloadEntity($entity);
+ $entity->langcode->value = $this->langcodes[0];
+ $translation = $entity->addTranslation($this->langcodes[1]);
+ $translated_value = $this->randomName();
+ $translation->get($this->field_name)->value = $translated_value;
+ $translation->save();
+ $this->toggleFieldTranslatability($entity_type);
+ $entity = $this->reloadEntity($entity);
+ $this->assertEqual($entity->getTranslation($this->langcodes[1])->get($this->field_name)->value, $values[$this->field_name], 'Existing field translations are not loaded for untranslatable fields.');
+ }
+
+ /**
+ * Checks whether field languages are correctly stored for the given entity.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity fields are attached to.
+ * @param string $message
+ * (optional) A message to display with the assertion.
+ */
+ protected function assertFieldStorageLangcode(ContentEntityInterface $entity, $message = '') {
+ $status = TRUE;
+ $entity_type = $entity->entityType();
+ $id = $entity->id();
+ $langcode = $entity->getUntranslated()->language()->id;
+ $fields = array($this->field_name, $this->untranslatable_field_name);
+
+ foreach ($fields as $field_name) {
+ $field = FieldService::fieldInfo()->getField($entity_type, $field_name);
+ $tables = array(
+ FieldableDatabaseStorageController::_fieldTableName($field),
+ FieldableDatabaseStorageController::_fieldRevisionTableName($field),
+ );
+
+ foreach ($tables as $table) {
+ $record = \Drupal::database()
+ ->select($table, 'f')
+ ->fields('f')
+ ->condition('f.entity_id', $id)
+ ->condition('f.revision_id', $id)
+ ->execute()
+ ->fetchObject();
+
+ if ($record->langcode != $langcode) {
+ $status = FALSE;
+ break;
+ }
+ }
+ }
+
+ return $this->assertTrue($status, $message);
+ }
+
+}
diff --git a/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php b/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php
index 0b47055..49d9f78 100644
--- a/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php
+++ b/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php
@@ -128,7 +128,7 @@ function testProcessedCache() {
$entity = entity_load($entity_type, $entity->id());
$cache = cache('field')->get("field:$entity_type:" . $entity->id());
$this->assertEqual($cache->data, array(
- Language::LANGCODE_DEFAULT => array(
+ Language::LANGCODE_NOT_SPECIFIED => array(
'summary_field' => array(
0 => array(
'value' => $value,
@@ -144,7 +144,7 @@ function testProcessedCache() {
// Inject fake processed values into the cache to make sure that these are
// used as-is and not re-calculated when the entity is loaded.
$data = array(
- Language::LANGCODE_DEFAULT => array(
+ Language::LANGCODE_NOT_SPECIFIED => array(
'summary_field' => array(
0 => array(
'value' => $value,