Commit dc3d8a01 authored by alexpott's avatar alexpott

Issue #2431329 by plach: Make (content) translation language available as a field definition

parent 1483ef10
......@@ -81,6 +81,13 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
*/
protected $langcodeKey;
/**
* The default langcode entity key.
*
* @var string
*/
protected $defaultLangcodeKey;
/**
* Language code identifying the entity active language.
*
......@@ -144,6 +151,7 @@ public function __construct(array $values, $entity_type, $bundle = FALSE, $trans
$this->entityTypeId = $entity_type;
$this->entityKeys['bundle'] = $bundle ? $bundle : $this->entityTypeId;
$this->langcodeKey = $this->getEntityType()->getKey('langcode');
$this->defaultLangcodeKey = $this->getEntityType()->getKey('default_langcode');
foreach ($values as $key => $value) {
// If the key matches an existing property set the value to the property
......@@ -242,6 +250,13 @@ public function isDefaultRevision($new_value = NULL) {
return $return;
}
/**
* {@inheritdoc}
*/
public function isDefaultTranslation() {
return $this->activeLangcode === LanguageInterface::LANGCODE_DEFAULT;
}
/**
* {@inheritdoc}
*/
......@@ -543,8 +558,10 @@ public function onChange($name) {
}
}
switch ($name) {
case $this->langcodeKey:
if ($this->isDefaultTranslation()) {
// Update the default internal language cache.
if ($name == $this->langcodeKey) {
$this->setDefaultLangcode();
if (isset($this->translations[$this->defaultLangcode])) {
$message = String::format('A translation already exists for the specified language (@langcode).', array('@langcode' => $this->defaultLangcode));
......@@ -552,6 +569,28 @@ public function onChange($name) {
}
$this->updateFieldLangcodes($this->defaultLangcode);
}
else {
// @todo Allow the translation language to be changed. See
// https://www.drupal.org/node/2443989.
$items = $this->get($this->langcodeKey);
if ($items->value != $this->activeLangcode) {
$items->setValue($this->activeLangcode, FALSE);
$message = String::format('The translation language cannot be changed (@langcode).', array('@langcode' => $this->activeLangcode));
throw new \LogicException($message);
}
}
break;
case $this->defaultLangcodeKey:
// @todo Use a standard method to make the default_langcode field
// read-only. See https://www.drupal.org/node/2443991.
if (isset($this->values[$this->defaultLangcodeKey])) {
$this->get($this->defaultLangcodeKey)->setValue($this->isDefaultTranslation(), FALSE);
$message = String::format('The default translation flag cannot be changed (@langcode).', array('@langcode' => $this->activeLangcode));
throw new \LogicException($message);
}
break;
}
}
/**
......@@ -684,6 +723,8 @@ public function addTranslation($langcode, array $values = array()) {
$values[$name] = $field->getValue();
}
}
$values[$this->langcodeKey] = $langcode;
$values[$this->defaultLangcodeKey] = FALSE;
$this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
$translation = $this->getTranslation($langcode);
......@@ -691,7 +732,7 @@ public function addTranslation($langcode, array $values = array()) {
foreach ($values as $name => $value) {
if (isset($definitions[$name]) && $definitions[$name]->isTranslatable()) {
$translation->$name = $value;
$translation->values[$name][$langcode] = $value;
}
}
......
......@@ -401,14 +401,35 @@ public function getBaseFieldDefinitions($entity_type_id) {
protected function buildBaseFieldDefinitions($entity_type_id) {
$entity_type = $this->getDefinition($entity_type_id);
$class = $entity_type->getClass();
$keys = array_filter($entity_type->getKeys());
// Fail with an exception for non-fieldable entity types.
if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
throw new \LogicException(String::format('Getting the base fields is not supported for entity type @type.', array('@type' => $entity_type->getLabel())));
}
// Retrieve base field definitions and assign them the entity type provider.
// Retrieve base field definitions.
/** @var FieldStorageDefinitionInterface[] $base_field_definitions */
$base_field_definitions = $class::baseFieldDefinitions($entity_type);
// Make sure translatable entity types are correctly defined.
if ($entity_type->isTranslatable()) {
// The langcode field should always be translatable if the entity type is.
if (isset($keys['langcode']) && isset($base_field_definitions[$keys['langcode']])) {
$base_field_definitions[$keys['langcode']]->setTranslatable(TRUE);
}
// A default_langcode field should always be defined.
if (!isset($base_field_definitions[$keys['default_langcode']])) {
$base_field_definitions[$keys['default_langcode']] = BaseFieldDefinition::create('boolean')
->setLabel($this->t('Default translation'))
->setDescription($this->t('A flag indicating whether this is the default translation.'))
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setDefaultValue(TRUE);
}
}
// Assign base field definitions the entity type provider.
$provider = $entity_type->getProvider();
foreach ($base_field_definitions as $definition) {
// @todo Remove this check once FieldDefinitionInterface exposes a proper
......@@ -450,15 +471,32 @@ protected function buildBaseFieldDefinitions($entity_type_id) {
// Ensure defined entity keys are there and have proper revisionable and
// translatable values.
$keys = array_filter($entity_type->getKeys());
foreach ($keys as $key => $field_name) {
if (isset($base_field_definitions[$field_name]) && in_array($key, array('id', 'revision', 'uuid', 'bundle')) && $base_field_definitions[$field_name]->isRevisionable()) {
throw new \LogicException(String::format('The @field field cannot be revisionable as it is used as @key entity key.', array('@field' => $base_field_definitions[$field_name]->getLabel(), '@key' => $key)));
foreach (array_intersect_key($keys, array_flip(['id', 'revision', 'uuid', 'bundle'])) as $key => $field_name) {
if (!isset($base_field_definitions[$field_name])) {
throw new \LogicException(String::format('The @field field definition does not exist and it is used as @key entity key.', array(
'@field' => $base_field_definitions[$field_name]->getLabel(),
'@key' => $key,
)));
}
if ($base_field_definitions[$field_name]->isRevisionable()) {
throw new \LogicException(String::format('The @field field cannot be revisionable as it is used as @key entity key.', array(
'@field' => $base_field_definitions[$field_name]->getLabel(),
'@key' => $key,
)));
}
if (isset($base_field_definitions[$field_name]) && in_array($key, array('id', 'revision', 'uuid', 'bundle', 'langcode')) && $base_field_definitions[$field_name]->isTranslatable()) {
throw new \LogicException(String::format('The @field field cannot be translatable as it is used as @key entity key.', array('@field' => $base_field_definitions[$field_name]->getLabel(), '@key' => $key)));
if ($base_field_definitions[$field_name]->isTranslatable()) {
throw new \LogicException(String::format('The @field field cannot be translatable as it is used as @key entity key.', array(
'@field' => $base_field_definitions[$field_name]->getLabel(),
'@key' => $key,
)));
}
}
// Make sure translatable entity types define the "langcode" field properly.
if ($entity_type->isTranslatable() && (!isset($keys['langcode']) || !isset($base_field_definitions[$keys['langcode']]) || !$base_field_definitions[$keys['langcode']]->isTranslatable())) {
throw new \LogicException(String::format('The @entity_type entity type cannot be translatable as it does not define a translatable "langcode" field.', array('@entity_type' => $entity_type->getLabel())));
}
return $base_field_definitions;
}
......
......@@ -238,6 +238,7 @@ public function __construct($definition) {
'revision' => '',
'bundle' => '',
'langcode' => '',
'default_langcode' => 'default_langcode',
);
$this->handlers += array(
'access' => 'Drupal\Core\Entity\EntityAccessControlHandler',
......
......@@ -192,6 +192,14 @@ public function getFields($include_computed = TRUE);
*
* @param string $field_name
* The name of the field which is changed.
*
* @throws \InvalidArgumentException
* When trying to assign a value to the language field that matches an
* existing translation.
* @throws \LogicException
* When trying to change:
* - The language of a translation.
* - The value of the flag identifying the default translation object.
*/
public function onChange($field_name);
......
......@@ -182,8 +182,8 @@ public function addField($field, $type, $langcode) {
$table = $this->ensureEntityTable($index_prefix, $sql_column, $type, $langcode, $base_table, $entity_id_field, $entity_tables);
// If there is a field storage (some specifiers are not, like
// default_langcode), check for case sensitivity.
// If there is a field storage (some specifiers are not), check for case
// sensitivity.
if ($field_storage) {
$column = $field_storage->getMainPropertyName();
$base_field_property_definitions = $field_storage->getPropertyDefinitions();
......
......@@ -66,6 +66,13 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
*/
protected $langcodeKey = FALSE;
/**
* The default language entity key.
*
* @var string
*/
protected $defaultLangcodeKey = FALSE;
/**
* The base table of the entity.
*
......@@ -200,7 +207,7 @@ protected function initTableLayout() {
if ($translatable) {
$this->dataTable = $this->entityType->getDataTable() ?: $this->entityTypeId . '_field_data';
$this->langcodeKey = $this->entityType->getKey('langcode');
$this->defaultLangcodeKey = $this->entityType->getKey('default_langcode') ?: 'default_langcode';
$this->defaultLangcodeKey = $this->entityType->getKey('default_langcode');
}
if ($revisionable && $translatable) {
$this->revisionDataTable = $this->entityType->getRevisionDataTable() ?: $this->entityTypeId . '_field_revision';
......@@ -342,11 +349,7 @@ public function getTableMapping(array $storage_definitions = NULL) {
// the data table.
$table_mapping
->setFieldNames($this->baseTable, $key_fields)
->setFieldNames($this->dataTable, array_values(array_diff($all_fields, array($this->uuidKey))))
// Add the denormalized 'default_langcode' field to the mapping. Its
// value is identical to the query expression
// "base_table.langcode = data_table.langcode"
->setExtraColumns($this->dataTable, array('default_langcode'));
->setFieldNames($this->dataTable, array_values(array_diff($all_fields, array($this->uuidKey))));
}
elseif ($revisionable && $translatable) {
// The revisionable multilingual layout stores key field values in the
......@@ -356,31 +359,20 @@ public function getTableMapping(array $storage_definitions = NULL) {
// holds the data field values for all non-revisionable fields. The data
// field values of revisionable fields are denormalized in the data
// table, as well.
$table_mapping->setFieldNames($this->baseTable, array_values(array_diff($key_fields, array($this->langcodeKey))));
$table_mapping->setFieldNames($this->baseTable, array_values($key_fields));
// Like in the multilingual, non-revisionable case the UUID is not
// in the data table. Additionally, do not store revision metadata
// fields in the data table.
$data_fields = array_values(array_diff($all_fields, array($this->uuidKey), $revision_metadata_fields));
$table_mapping
->setFieldNames($this->dataTable, $data_fields)
// Add the denormalized 'default_langcode' field to the mapping. Its
// value is identical to the query expression
// "base_langcode = data_table.langcode" where "base_langcode" is
// the language code of the default revision.
->setExtraColumns($this->dataTable, array('default_langcode'));
$table_mapping->setFieldNames($this->dataTable, $data_fields);
$revision_base_fields = array_merge(array($this->idKey, $this->revisionKey, $this->langcodeKey), $revision_metadata_fields);
$table_mapping->setFieldNames($this->revisionTable, $revision_base_fields);
$revision_data_key_fields = array($this->idKey, $this->revisionKey, $this->langcodeKey);
$revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, array($this->langcodeKey));
$table_mapping
->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields))
// Add the denormalized 'default_langcode' field to the mapping. Its
// value is identical to the query expression
// "revision_table.langcode = data_table.langcode".
->setExtraColumns($this->revisionDataTable, array('default_langcode'));
$table_mapping->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields));
}
// Add dedicated tables.
......@@ -673,12 +665,14 @@ protected function attachPropertyData(array &$entities) {
$table_mapping = $this->getTableMapping();
if ($this->revisionDataTable) {
// Find revisioned fields that are not entity keys.
$fields = array_diff($table_mapping->getFieldNames($this->revisionDataTable), $table_mapping->getFieldNames($this->baseTable));
// Find revisioned fields that are not entity keys. Exclude the langcode
// key as the base table holds only the default language.
$base_fields = array_diff($table_mapping->getFieldNames($this->baseTable), array($this->langcodeKey));
$fields = array_diff($table_mapping->getFieldNames($this->revisionDataTable), $base_fields);
// Find fields that are not revisioned or entity keys. Data fields have
// the same value regardless of entity revision.
$data_fields = array_diff($table_mapping->getFieldNames($this->dataTable), $fields, $table_mapping->getFieldNames($this->baseTable));
$data_fields = array_diff($table_mapping->getFieldNames($this->dataTable), $fields, $base_fields);
if ($data_fields) {
$fields = array_merge($fields, $data_fields);
$query->leftJoin($this->dataTable, 'data', "(revision.$this->idKey = data.$this->idKey)");
......@@ -703,10 +697,9 @@ protected function attachPropertyData(array &$entities) {
// Field values in default language are stored with
// LanguageInterface::LANGCODE_DEFAULT as key.
$langcode = empty($values['default_langcode']) ? $values[$this->langcodeKey] : LanguageInterface::LANGCODE_DEFAULT;
$langcode = empty($values[$this->defaultLangcodeKey]) ? $values[$this->langcodeKey] : LanguageInterface::LANGCODE_DEFAULT;
$translations[$id][$langcode] = TRUE;
foreach ($fields as $field_name) {
$columns = $table_mapping->getColumnNames($field_name);
// Do not key single-column fields by property name.
......@@ -776,13 +769,13 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
// apply to the default language. See http://drupal.org/node/1866330.
// Default to the original entity language if not explicitly specified
// otherwise.
if (!array_key_exists('default_langcode', $values)) {
$values['default_langcode'] = 1;
if (!array_key_exists($this->defaultLangcodeKey, $values)) {
$values[$this->defaultLangcodeKey] = 1;
}
// If the 'default_langcode' flag is explicitly not set, we do not care
// whether the queried values are in the original entity language or not.
elseif ($values['default_langcode'] === NULL) {
unset($values['default_langcode']);
elseif ($values[$this->defaultLangcodeKey] === NULL) {
unset($values[$this->defaultLangcodeKey]);
}
}
......@@ -1156,8 +1149,6 @@ protected function mapToDataStorageRecord(EntityInterface $entity, $table_name =
$table_name = $this->dataTable;
}
$record = $this->mapToStorageRecord($entity, $table_name);
$record->{$this->langcodeKey} = $entity->language()->getId();
$record->default_langcode = intval($record->{$this->langcodeKey} == $entity->getUntranslated()->language()->getId());
return $record;
}
......@@ -1171,7 +1162,7 @@ protected function mapToDataStorageRecord(EntityInterface $entity, $table_name =
* The revision id.
*/
protected function saveRevision(EntityInterface $entity) {
$record = $this->mapToStorageRecord($entity, $this->revisionTable);
$record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable);
$entity->preSaveRevision($this, $record);
......
......@@ -9,6 +9,7 @@
use Drupal\Component\Utility\String;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\DatabaseException;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageException;
......@@ -163,7 +164,6 @@ public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterfac
$storage_definition->hasCustomStorage() != $original->hasCustomStorage() ||
$storage_definition->getSchema() != $original->getSchema() ||
$storage_definition->isRevisionable() != $original->isRevisionable() ||
$storage_definition->isTranslatable() != $original->isTranslatable() ||
$table_mapping->allowsSharedTableStorage($storage_definition) != $table_mapping->allowsSharedTableStorage($original) ||
$table_mapping->requiresDedicatedTableStorage($storage_definition) != $table_mapping->requiresDedicatedTableStorage($original)
) {
......@@ -386,8 +386,17 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
// Only configurable fields currently support purging, so prevent deletion
// of ones we can't purge if they have existing data.
// @todo Add purging to all fields: https://www.drupal.org/node/2282119.
try {
if (!($storage_definition instanceof FieldStorageConfigInterface) && $this->storage->countFieldData($storage_definition, TRUE)) {
throw new FieldStorageDefinitionUpdateForbiddenException('Unable to delete a field with data that can\'t be purged.');
throw new FieldStorageDefinitionUpdateForbiddenException('Unable to delete a field with data that cannot be purged.');
}
}
catch (DatabaseException $e) {
// This may happen when changing field storage schema, since we are not
// able to use a table mapping matching the passed storage definition.
// @todo Revisit this once we are able to instantiate the table mapping
// properly. See https://www.drupal.org/node/2274017.
return;
}
// Retrieve a table mapping which contains the deleted field still.
......@@ -506,13 +515,6 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
$schema[$table_name] = array_merge_recursive($schema[$table_name], $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names));
}
}
// Add the schema for extra fields.
foreach ($table_mapping->getExtraColumns($table_name) as $column_name) {
if ($column_name == 'default_langcode') {
$this->addDefaultLangcodeSchema($schema[$table_name]);
}
}
}
// Process tables after having gathered field information.
......@@ -726,25 +728,6 @@ protected function getFieldForeignKeys($field_name, array $field_schema, array $
return $foreign_keys;
}
/**
* Returns the schema for the 'default_langcode' metadata field.
*
* @param array $schema
* The table schema to add the field schema to, passed by reference.
*
* @return array
* A schema field array for the 'default_langcode' metadata field.
*/
protected function addDefaultLangcodeSchema(&$schema) {
$schema['fields']['default_langcode'] = array(
'description' => 'Boolean indicating whether field values are in the default entity language.',
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 1,
);
}
/**
* Loads stored schema data for the given entity type definition.
*
......
......@@ -13,13 +13,21 @@
interface TranslatableInterface {
/**
* Returns the default language.
* Returns the translation language.
*
* @return \Drupal\Core\Language\LanguageInterface
* The language object.
*/
public function language();
/**
* Checks whether the translation is the default one.
*
* @return bool
* TRUE if the translation is the default one, FALSE otherwise.
*/
public function isDefaultTranslation();
/**
* Returns the languages the data is translated to.
*
......
......@@ -167,6 +167,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The custom block language code.'))
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
......
......@@ -224,6 +224,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The comment language code.'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
))
......
......@@ -102,7 +102,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
foreach ($fields as $field_name => $definition) {
// Allow to configure only fields supporting multilingual storage.
// We skip our own fields as they are always translatable.
if (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable() && $storage_definitions[$field_name]->getProvider() != 'content_translation') {
if (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable() && $storage_definitions[$field_name]->getProvider() != 'content_translation' && $field_name != $entity_type->getKey('langcode') && $field_name != $entity_type->getKey('default_langcode')) {
$form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = array(
'#label' => $definition->getLabel(),
'#type' => 'checkbox',
......
......@@ -360,6 +360,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The menu link language code.'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
))
......
......@@ -353,6 +353,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The node language code.'))
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
......
......@@ -95,21 +95,24 @@ public function testNormalize() {
'type' => array(
array('value' => 'entity_test_mulrev'),
),
'created' => array(
array('value' => $this->entity->created->value),
),
'user_id' => array(
array('target_id' => $this->values['user_id']),
),
'revision_id' => array(
array('value' => 1),
),
'default_langcode' => array(
array('value' => TRUE),
),
'field_test_text' => array(
array(
'value' => $this->values['field_test_text']['value'],
'format' => $this->values['field_test_text']['format'],
),
),
'created' => array(
array('value' => $this->entity->created->value),
),
);
$normalized = $this->serializer->normalize($this->entity);
......@@ -175,10 +178,11 @@ public function testSerialize() {
'langcode' => '<langcode><value>en</value></langcode>',
'name' => '<name><value>' . $this->values['name'] . '</value></name>',
'type' => '<type><value>entity_test_mulrev</value></type>',
'created' => '<created><value>' . $this->entity->created->value . '</value></created>',
'user_id' => '<user_id><target_id>' . $this->values['user_id'] . '</target_id></user_id>',
'revision_id' => '<revision_id><value>' . $this->entity->getRevisionId() . '</value></revision_id>',
'default_langcode' => '<default_langcode><value>1</value></default_langcode>',
'field_test_text' => '<field_test_text><value>' . $this->values['field_test_text']['value'] . '</value><format>' . $this->values['field_test_text']['format'] . '</format></field_test_text>',
'created' => '<created><value>' . $this->entity->created->value . '</value></created>',
);
// Sort it in the same order as normalised.
$expected = array_merge($normalized, $expected);
......
......@@ -163,6 +163,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The language code of the shortcut.'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
))
......
......@@ -8,6 +8,7 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\language\Entity\ConfigurableLanguage;
......@@ -147,6 +148,7 @@ public function testMultilingualProperties() {
*/
protected function doTestMultilingualProperties($entity_type) {
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$default_langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('default_langcode');
$name = $this->randomMachineName();
$uid = mt_rand(0, 127);
$langcode = $this->langcodes[0];
......@@ -249,16 +251,16 @@ protected function doTestMultilingualProperties($entity_type) {
$this->assertEqual(count($entities), 2, format_string('%entity_type: Two entities correctly loaded by name.', array('%entity_type' => $entity_type)));
// @todo The default language condition should go away in favor of an
// explicit parameter.
$entities = entity_load_multiple_by_properties($entity_type, array('name' => $properties[$langcode]['name'][0], 'default_langcode' => 0));
$entities = entity_load_multiple_by_properties($entity_type, array('name' => $properties[$langcode]['name'][0], $default_langcode_key => 0));
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity correctly loaded by name translation.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $default_langcode, 'name' => $name));
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity correctly loaded by name and language.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0]));
$this->assertEqual(count($entities), 0, format_string('%entity_type: No entity loaded by name translation specifying the translation language.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0], 'default_langcode' => 0));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0], $default_langcode_key => 0));
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity loaded by name translation and language specifying to look for translations.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array('user_id' => $properties[$langcode]['user_id'][0], 'default_langcode' => NULL));
$entities = entity_load_multiple_by_properties($entity_type, array('user_id' => $properties[$langcode]['user_id'][0], $default_langcode_key => NULL));
$this->assertEqual(count($entities), 2, format_string('%entity_type: Two entities loaded by uid without caring about property translatability.', array('%entity_type' => $entity_type)));
// Test property conditions and orders with multiple languages in the same
......@@ -313,6 +315,9 @@ protected function doTestEntityTranslationAPI($entity_type) {
$default_langcode = $this->langcodes[0];
$langcode = $this->langcodes[1];
$langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('langcode');
$default_langcode_key = $this->entityManager->getDefinition($entity_type)->getKey('default_langcode');
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->entityManager
->getStorage($entity_type)
->create(array('name' => $this->randomMachineName(), $langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED));
......@@ -324,12 +329,13 @@ protected function doTestEntityTranslationAPI($entity_type) {
// Verify that we obtain the entity object itself when we attempt to
// retrieve a translation referring to it.
$translation = $entity->getTranslation($langcode);
$this->assertEqual($entity, $translation, 'The translation object corresponding to a non-default language is the entity object itself when the entity is language-neutral.');
$this->assertIdentical($entity, $translation, 'The translation object corresponding to a non-default language is the entity object itself when the entity is language-neutral.');
$entity->{$langcode_key}->value = $default_langcode;
$translation = $entity->getTranslation($default_langcode);
$this->assertEqual($entity, $translation, 'The translation object corresponding to the default language (explicit) is the entity object itself.');
$this->assertIdentical($entity, $translation, 'The translation object corresponding to the default language (explicit) is the entity object itself.');
$translation = $entity->getTranslation(LanguageInterface::LANGCODE_DEFAULT);
$this->assertEqual($entity, $translation, 'The translation object corresponding to the default language (implicit) is the entity object itself.');
$this->assertIdentical($entity, $translation, 'The translation object corresponding to the default language (implicit) is the entity object itself.');
$this->assertTrue($entity->{$default_langcode_key}->value, 'The translation object is the default one.');
// Create a translation and verify that the translation object and the
// original object behave independently.
......@@ -340,18 +346,57 @@ protected function doTestEntityTranslationAPI($entity_type) {
$this->assertNotIdentical($entity, $translation, 'The entity and the translation object differ from one another.');
$this->assertTrue($entity->hasTranslation($langcode), 'The new translation exists.');
$this->assertEqual($translation->language()->getId(), $langcode, 'The translation language matches the specified one.');
$this->assertEqual($translation->{$langcode_key}->value, $langcode, 'The translation field language value matches the specified one.');
$this->assertFalse($translation->{$default_langcode_key}->value, 'The translation object is not the default one.');
$this->assertEqual($translation->getUntranslated()->language()->getId(), $default_langcode, 'The original language can still be retrieved.');
$translation->name->value = $name_translated;
$this->assertEqual($entity->name->value, $name, 'The original name is retained after setting a translated value.');
$entity->name->value = $name;
$this->assertEqual($translation->name->value, $name_translated, 'The translated name is retained after setting the original value.');
// Save the translation and check that the expecte hooks are fired.
// Save the translation and check that the expected hooks are fired.
$translation->save();
$hooks = $this->getHooksInfo();
$this->assertEqual($hooks['entity_translation_insert'], $langcode, 'The generic entity translation insertion hook has fired.');
$this->assertEqual($hooks[$entity_type . '_translation_insert'], $langcode, 'The entity-type-specific entity translation insertion hook has fired.');
// Verify that changing translation language causes an exception to be
// thrown.
$message = 'The translation language cannot be changed.';
try {
$translation->{$langcode_key}->value = $this->langcodes[2];
$this->fail($message);
}
catch (\LogicException $e) {
$this->pass($message);
}
// Verify that reassigning the same translation language is allowed.
$message = 'The translation language can be reassigned the same value.';
try {
$translation->{$langcode_key}->value = $langcode;
$this->pass($message);
}
catch (\LogicException $e) {
$this->fail($message);
}
// Verify that changing the default translation flag causes an exception to
// be thrown.
$message = 'The default translation flag cannot be changed.';
foreach ($entity->getTranslationLanguages() as $t_langcode => $language) {
$translation = $entity->getTranslation($t_langcode);
$default = $translation->isDefaultTranslation();
try {
$translation->{$default_langcode_key}->value = !$default;
$this->fail($message);
}
catch (\LogicException $e) {
$this->pass($message);
}
$this->assertEqual($translation->{$default_langcode_key}->value, $default);
}
// Check that after loading an entity the language is the default one.
$entity = $this->reloadEntity($entity);
$this->assertEqual($entity->language()->getId(), $default_langcode, 'The loaded entity is the original one.');
......@@ -588,9 +633,16 @@ function testFieldDefinitions() {
$this->assertTrue(!isset($base_field_definitions['id']->translatable), 'Translatability for the <em>id</em> field is not defined.');
$this->assertFalse($definitions['id']->isTranslatable(), 'Field translatability is disabled by default.');
// Check that entity ids and langcode fields cannot be translatable.
foreach (array('id', 'uuid', 'revision_id', 'type', 'langcode') as $name) {
$this->state->set('entity_test.field_definitions.translatable', array($name => TRUE));
// Check that entity id keys have the expect translatability.
$translatable_fields = array(
'id' => TRUE,
'uuid' => TRUE,
'revision_id' => TRUE,
'type' => TRUE,
'langcode' => FALSE,
);
foreach ($translatable_fields as $name => $translatable) {
$this->state->set('entity_test.field_definitions.translatable', array($name => $translatable));
$this->entityManager->clearCachedFieldDefinitions();
$message = format_string('Field %field cannot be translatable.', array('%field' => $name));
......
......@@ -77,7 +77,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language code'))