diff --git a/core/modules/entity/entity.class.inc b/core/modules/entity/entity.class.inc index 2311af54019..7af3e0ac65f 100644 --- a/core/modules/entity/entity.class.inc +++ b/core/modules/entity/entity.class.inc @@ -92,6 +92,60 @@ interface EntityInterface { */ public function uri(); + /** + * Returns the default language of a language-specific entity. + * + * @return + * The language object of the entity's default language, or FALSE if the + * entity is not language-specific. + * + * @see EntityInterface::translations() + */ + public function language(); + + /** + * Returns the languages the entity is translated to. + * + * @return + * An array of language objects, keyed by language codes. + * + * @see EntityInterface::language() + */ + public function translations(); + + /** + * Returns the value of an entity property. + * + * @param $property_name + * The name of the property to return; e.g., 'title'. + * @param $langcode + * (optional) If the property is translatable, the language code of the + * language that should be used for getting the property. If set to NULL, + * the entity's default language is being used. + * + * @return + * The property value, or NULL if it is not defined. + * + * @see EntityInterface::language() + */ + public function get($property_name, $langcode = NULL); + + /** + * Sets the value of an entity property. + * + * @param $property_name + * The name of the property to set; e.g., 'title'. + * @param $value + * The value to set, or NULL to unset the property. + * @param $langcode + * (optional) If the property is translatable, the language code of the + * language that should be used for getting the property. If set to + * NULL, the entity's default language is being used. + * + * @see EntityInterface::language() + */ + public function set($property_name, $value, $langcode = NULL); + /** * Saves an entity permanently. * @@ -138,6 +192,13 @@ interface EntityInterface { */ class Entity implements EntityInterface { + /** + * The language code of the entity's default language. + * + * @var string + */ + public $langcode = LANGUAGE_NOT_SPECIFIED; + /** * The entity type. * @@ -246,6 +307,89 @@ class Entity implements EntityInterface { } } + /** + * Implements EntityInterface::language(). + */ + public function language() { + // @todo: Check for language.module instead, once Field API language + // handling depends upon it too. + return module_exists('locale') ? language_load($this->langcode) : FALSE; + } + + /** + * Implements EntityInterface::translations(). + */ + public function translations() { + $languages = array(); + if ($this->entityInfo['fieldable'] && ($default_language = $this->language())) { + // Go through translatable properties and determine all languages for + // which translated values are available. + foreach (field_info_instances($this->entityType, $this->bundle()) as $field_name => $instance) { + $field = field_info_field($field_name); + if (field_is_translatable($this->entityType, $field) && isset($this->$field_name)) { + foreach ($this->$field_name as $langcode => $value) { + $languages[$langcode] = TRUE; + } + } + } + // Remove the default language from the translations. + unset($languages[$default_language->langcode]); + $languages = array_intersect_key(language_list(), $languages); + } + return $languages; + } + + /** + * Implements EntityInterface::get(). + */ + public function get($property_name, $langcode = NULL) { + // Handle fields. + if ($this->entityInfo['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) { + $field = field_info_field($property_name); + $langcode = $this->getFieldLangcode($field, $langcode); + return isset($this->{$property_name}[$langcode]) ? $this->{$property_name}[$langcode] : NULL; + } + else { + // Handle properties being not fields. + // @todo: Add support for translatable properties being not fields. + return isset($this->{$property_name}) ? $this->{$property_name} : NULL; + } + } + + /** + * Implements EntityInterface::set(). + */ + public function set($property_name, $value, $langcode = NULL) { + // Handle fields. + if ($this->entityInfo['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) { + $field = field_info_field($property_name); + $langcode = $this->getFieldLangcode($field, $langcode); + $this->{$property_name}[$langcode] = $value; + } + else { + // Handle properties being not fields. + // @todo: Add support for translatable properties being not fields. + $this->{$property_name} = $value; + } + } + + /** + * Determines the language code to use for accessing a field value in a certain language. + */ + protected function getFieldLangcode($field, $langcode = NULL) { + // Only apply the given langcode if the entity is language-specific. + // Otherwise translatable fields are handled as non-translatable fields. + if (field_is_translatable($this->entityType, $field) && ($default_language = $this->language())) { + // For translatable fields the values in default language are stored using + // the language code of the default language. + return isset($langcode) ? $langcode : $default_language->langcode; + } + else { + // Non-translatable fields always use LANGUAGE_NOT_SPECIFIED. + return LANGUAGE_NOT_SPECIFIED; + } + } + /** * Implements EntityInterface::save(). */ diff --git a/core/modules/entity/tests/entity.test b/core/modules/entity/tests/entity.test index 35b6ab1c262..57600a35f65 100644 --- a/core/modules/entity/tests/entity.test +++ b/core/modules/entity/tests/entity.test @@ -65,6 +65,138 @@ class EntityAPITestCase extends DrupalWebTestCase { $all = entity_test_load_multiple(FALSE); $this->assertTrue(empty($all), 'Deleted all entities.'); } + + /** + * Tests Entity getters/setters. + */ + function testEntityGettersSetters() { + $entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL)); + $this->assertNull($entity->get('uid'), 'Property is not set.'); + + $entity->set('uid', $GLOBALS['user']->uid); + $this->assertEqual($entity->uid, $GLOBALS['user']->uid, 'Property has been set.'); + + $value = $entity->get('uid'); + $this->assertEqual($value, $entity->uid, 'Property has been retrieved.'); + + // Make sure setting/getting translations boils down to setting/getting the + // regular value as the entity and property are not translatable. + $entity->set('uid', NULL, 'en'); + $this->assertNull($entity->uid, 'Language neutral property has been set.'); + + $value = $entity->get('uid', 'en'); + $this->assertNull($value, 'Language neutral property has been retrieved.'); + } +} + +/** + * Tests entity translation. + */ +class EntityTranslationTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Entity Translation', + 'description' => 'Tests entity translation functionality.', + 'group' => 'Entity API', + ); + } + + function setUp() { + // Enable translations for the test entity type. We cannot use + // variable_set() here as variables are cleared by parent::setUp(); + $GLOBALS['entity_test_translation'] = TRUE; + parent::setUp('entity_test', 'language', 'locale'); + + // Create a translatable test field. + $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); + $field = array( + 'field_name' => $this->field_name, + 'type' => 'text', + 'cardinality' => 4, + 'translatable' => TRUE, + ); + field_create_field($field); + $this->field = field_read_field($this->field_name); + + $instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'entity_test', + 'bundle' => 'entity_test', + ); + field_create_instance($instance); + $this->instance = field_read_instance('entity_test', $this->field_name, 'entity_test'); + + // Create test languages. + $this->langcodes = array(); + for ($i = 0; $i < 3; ++$i) { + $language = (object) array( + 'langcode' => 'l' . $i, + 'name' => $this->randomString(), + ); + $this->langcodes[$i] = $language->langcode; + language_save($language); + } + } + + /** + * Tests language related methods of the Entity class. + */ + function testEntityLanguageMethods() { + $entity = entity_create('entity_test', array( + 'name' => 'test', + 'uid' => $GLOBALS['user']->uid, + )); + $this->assertFalse($entity->language(), 'No entity language has been specified.'); + $this->assertFalse($entity->translations(), 'No translations are available'); + + // Set the value in default language. + $entity->set($this->field_name, array(0 => array('value' => 'default value'))); + // Get the value. + $value = $entity->get($this->field_name); + $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value retrieved.'); + + // Set the value in a certain language. As the entity is not + // language-specific it should use the default language and so ignore the + // specified language. + $entity->set($this->field_name, array(0 => array('value' => 'default value2')), $this->langcodes[1]); + $value = $entity->get($this->field_name); + $this->assertEqual($value, array(0 => array('value' => 'default value2')), 'Untranslated value updated.'); + $this->assertFalse($entity->translations(), 'No translations are available'); + + // Test getting a field value using the default language for a not + // language-specific entity. + $value = $entity->get($this->field_name, $this->langcodes[1]); + $this->assertEqual($value, array(0 => array('value' => 'default value2')), 'Untranslated value retrieved.'); + + // Now, make the entity language-specific by assigning a language and test + // translating it. + $entity->langcode = $this->langcodes[0]; + $entity->{$this->field_name} = array(); + $this->assertEqual($entity->language(), language_load($this->langcodes[0]), 'Entity language retrieved.'); + $this->assertFalse($entity->translations(), 'No translations are available'); + + // Set the value in default language. + $entity->set($this->field_name, array(0 => array('value' => 'default value'))); + // Get the value. + $value = $entity->get($this->field_name); + $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value retrieved.'); + + // Set a translation. + $entity->set($this->field_name, array(0 => array('value' => 'translation 1')), $this->langcodes[1]); + $value = $entity->get($this->field_name, $this->langcodes[1]); + $this->assertEqual($value, array(0 => array('value' => 'translation 1')), 'Translated value set.'); + // Make sure the untranslated value stays. + $value = $entity->get($this->field_name); + $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value stays.'); + + $translations[$this->langcodes[1]] = language_load($this->langcodes[1]); + $this->assertEqual($entity->translations(), $translations, 'Translations retrieved.'); + + // Try to get a not available translation. + $value = $entity->get($this->field_name, $this->langcodes[2]); + $this->assertNull($value, 'A translation that is not available is NULL.'); + } } /** diff --git a/core/modules/entity/tests/entity_test.install b/core/modules/entity/tests/entity_test.install index ec2e5bd8245..c0c77039103 100644 --- a/core/modules/entity/tests/entity_test.install +++ b/core/modules/entity/tests/entity_test.install @@ -57,6 +57,13 @@ function entity_test_schema() { 'default' => NULL, 'description' => "The {users}.uid of the associated user.", ), + 'langcode' => array( + 'description' => 'The {language}.langcode of the test entity.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), ), 'indexes' => array( 'uid' => array('uid'), diff --git a/core/modules/entity/tests/entity_test.module b/core/modules/entity/tests/entity_test.module index 6034b06ece4..f7dffa0ac3c 100644 --- a/core/modules/entity/tests/entity_test.module +++ b/core/modules/entity/tests/entity_test.module @@ -9,19 +9,21 @@ * Implements hook_entity_info(). */ function entity_test_entity_info() { - $return = array( - 'entity_test' => array( - 'label' => t('Test entity'), - 'entity class' => 'Entity', - 'controller class' => 'EntityDatabaseStorageController', - 'base table' => 'entity_test', - 'fieldable' => TRUE, - 'entity keys' => array( - 'id' => 'id', - ), + $items['entity_test'] = array( + 'label' => t('Test entity'), + 'entity class' => 'Entity', + 'controller class' => 'EntityDatabaseStorageController', + 'base table' => 'entity_test', + 'fieldable' => TRUE, + 'entity keys' => array( + 'id' => 'id', ), ); - return $return; + // Optionally specify a translation handler for testing translations. + if (!empty($GLOBALS['entity_test_translation'])) { + $items['entity_test']['translation']['entity_test'] = TRUE; + } + return $items; } /**