diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityApiTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityApiTest.php index 5eb245f..a7ac1a9 100644 --- a/core/modules/entity/lib/Drupal/entity/Tests/EntityApiTest.php +++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityApiTest.php @@ -42,8 +42,8 @@ class EntityApiTest extends WebTestBase { $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test'))); - $this->assertEqual($entities[0]->name, 'test', 'Created and loaded entity.'); - $this->assertEqual($entities[1]->name, 'test', 'Created and loaded entity.'); + $this->assertEqual($entities[0]->get('name'), 'test', 'Created and loaded entity.'); + $this->assertEqual($entities[1]->get('name'), 'test', 'Created and loaded entity.'); // Test loading a single entity. $loaded_entity = entity_test_load($entity->id); @@ -57,10 +57,10 @@ class EntityApiTest extends WebTestBase { // Test updating an entity. $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test'))); - $entities[0]->name = 'test3'; + $entities[0]->set('name', 'test3'); $entities[0]->save(); $entity = entity_test_load($entities[0]->id); - $this->assertEqual($entity->name, 'test3', 'Entity updated.'); + $this->assertEqual($entity->get('name'), 'test3', 'Entity updated.'); // Try deleting multiple test entities by deleting all. $ids = array_keys(entity_test_load_multiple(FALSE)); @@ -78,10 +78,10 @@ class EntityApiTest extends WebTestBase { $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.'); + $this->assertEqual($entity->get('uid'), $GLOBALS['user']->uid, 'Property has been set.'); $value = $entity->get('uid'); - $this->assertEqual($value, $entity->uid, 'Property has been retrieved.'); + $this->assertEqual($value, $entity->get('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. diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.php index 99842e5..6f9ec7a 100644 --- a/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.php +++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.php @@ -7,6 +7,9 @@ namespace Drupal\entity\Tests; +use Exception; +use InvalidArgumentException; + use Drupal\simpletest\WebTestBase; /** @@ -14,6 +17,8 @@ use Drupal\simpletest\WebTestBase; */ class EntityTranslationTest extends WebTestBase { + protected $langcodes; + public static function getInfo() { return array( 'name' => 'Entity Translation', @@ -90,7 +95,7 @@ class EntityTranslationTest extends WebTestBase { // Now, make the entity language-specific by assigning a language and test // translating it. - $entity->langcode = $this->langcodes[0]; + $entity->setLangcode($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'); @@ -115,5 +120,108 @@ class EntityTranslationTest extends WebTestBase { // 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.'); + + // Try to get a value using an invalid language code. + $value = $entity->get($this->field_name, 'invalid'); + $this->assertNull($value, 'A translation for an invalid language is NULL.'); + + // Try to set a value using an invalid language code. + $message = "An exception is thrown when trying to set an invalid translation."; + try { + $entity->set($this->field_name, NULL, 'invalid'); + // This line is not expected to be executed unless something goes wrong. + $this->fail($message); + } + catch (Exception $e) { + $this->assertTrue($e instanceof InvalidArgumentException, $message); + } + } + + /** + * Tests multilingual properties. + */ + function testMultilingualProperties() { + $name = $this->randomName(); + $uid = mt_rand(0, 127); + $langcode = $this->langcodes[0]; + + // Create a language neutral entity and check that properties are stored + // as language neutral. + $entity = entity_create('entity_test', array('name' => $name, 'uid' => $uid)); + $entity->save(); + $entity = entity_test_load($entity->id()); + $this->assertEqual($entity->language()->langcode, LANGUAGE_NOT_SPECIFIED, 'Entity created as language neutral.'); + $this->assertEqual($name, $entity->get('name', LANGUAGE_NOT_SPECIFIED), 'The entity name has been correctly stored as language neutral.'); + $this->assertEqual($uid, $entity->get('uid', LANGUAGE_NOT_SPECIFIED), 'The entity author has been correctly stored as language neutral.'); + $this->assertNull($entity->get('name', $langcode), 'The entity name is not available as a language-aware property.'); + $this->assertNull($entity->get('uid', $langcode), 'The entity author is not available as a language-aware property.'); + $this->assertEqual($name, $entity->get('name'), 'The entity name can be retrieved without specifying a language.'); + $this->assertEqual($uid, $entity->get('uid'), 'The entity author can be retrieved without specifying a language.'); + + // Create a language-aware entity and check that properties are stored + // as language-aware. + $entity = entity_create('entity_test', array('name' => $name, 'uid' => $uid, 'langcode' => $langcode)); + $entity->save(); + $entity = entity_test_load($entity->id()); + $this->assertEqual($entity->language()->langcode, $langcode, 'Entity created as language specific.'); + $this->assertEqual($name, $entity->get('name', $langcode), 'The entity name has been correctly stored as a language-aware property.'); + $this->assertEqual($uid, $entity->get('uid', $langcode), 'The entity author has been correctly stored as a language-aware property.'); + $this->assertNull($entity->get('name', LANGUAGE_NOT_SPECIFIED), 'The entity name is not available as a language neutral property.'); + $this->assertNull($entity->get('uid', LANGUAGE_NOT_SPECIFIED), 'The entity author is not available as a language neutral property.'); + $this->assertEqual($name, $entity->get('name'), 'The entity name can be retrieved without specifying a language.'); + $this->assertEqual($uid, $entity->get('uid'), 'The entity author can be retrieved without specifying a language.'); + + // Create property translations. + $properties = array(); + $default_langcode = $langcode; + foreach ($this->langcodes as $langcode) { + if ($langcode != $default_langcode) { + $properties[$langcode] = array( + 'name' => $this->randomName(), + 'uid' => mt_rand(0, 127), + ); + } + else { + $properties[$langcode] = array( + 'name' => $name, + 'uid' => $uid, + ); + } + $entity->setProperties($properties[$langcode], $langcode); + } + $entity->save(); + + // Check that property translation were correctly stored. + $entity = entity_test_load($entity->id()); + foreach ($this->langcodes as $langcode) { + $args = array('%langcode' => $langcode); + $this->assertEqual($properties[$langcode]['name'], $entity->get('name', $langcode), format_string('The entity name has been correctly stored for language %langcode.', $args)); + $this->assertEqual($properties[$langcode]['uid'], $entity->get('uid', $langcode), format_string('The entity author has been correctly stored for language %langcode.', $args)); + } + + // Test query conditions (cache is reset at each call). + $translated_id = $entity->id(); + // Create an additional entity with only the uid set. The uid for the + // original language is the same of one used for a translation. + $langcode = $this->langcodes[1]; + entity_create('entity_test', array('uid' => $properties[$langcode]['uid']))->save(); + $entities = entity_test_load_multiple(FALSE, array(), TRUE); + $this->assertEqual(count($entities), 3, 'Three entities were created.'); + $entities = entity_test_load_multiple(array($translated_id), array(), TRUE); + $this->assertEqual(count($entities), 1, 'One entity correctly loaded by id.'); + $entities = entity_test_load_multiple(array(), array('name' => $name), TRUE); + $this->assertEqual(count($entities), 2, 'Two entities correctly loaded by name.'); + // @todo The default language condition should go away in favor of an + // explicit parameter. + $entities = entity_test_load_multiple(array(), array('name' => $properties[$langcode]['name'], 'default_langcode' => 0), TRUE); + $this->assertEqual(count($entities), 1, 'One entity correctly loaded by name translation.'); + $entities = entity_test_load_multiple(array(), array('langcode' => $default_langcode, 'name' => $name), TRUE); + $this->assertEqual(count($entities), 1, 'One entity correctly loaded by name and language.'); + $entities = entity_test_load_multiple(array(), array('langcode' => $langcode, 'name' => $properties[$langcode]['name']), TRUE); + $this->assertEqual(count($entities), 0, 'No entity loaded by name translation specifying the translation language.'); + $entities = entity_test_load_multiple(array(), array('langcode' => $langcode, 'name' => $properties[$langcode]['name'], 'default_langcode' => 0), TRUE); + $this->assertEqual(count($entities), 1, 'One entity loaded by name translation and language specifying to look for translations.'); + $entities = entity_test_load_multiple(array(), array('uid' => $properties[$langcode]['uid'], 'default_langcode' => NULL), TRUE); + $this->assertEqual(count($entities), 2, 'Two entities loaded by uid without caring about property translatability.'); } } diff --git a/core/modules/entity/tests/modules/entity_test/entity_test.install b/core/modules/entity/tests/modules/entity_test/entity_test.install index 1bef390..b42dbdb 100644 --- a/core/modules/entity/tests/modules/entity_test/entity_test.install +++ b/core/modules/entity/tests/modules/entity_test/entity_test.install @@ -49,6 +49,38 @@ function entity_test_schema() { 'length' => 128, 'not null' => FALSE, ), + 'langcode' => array( + 'description' => 'The {language}.langcode of the original variant of this test entity.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'primary key' => array('id'), + ); + $schema['entity_test_property_data'] = array( + 'description' => 'Stores entity_test item properties.', + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The {entity_test}.id of the test entity.', + ), + 'langcode' => array( + 'description' => 'The {language}.langcode of this variant of this test entity.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + 'default_langcode' => array( + 'description' => 'Boolean indicating whether the current variant is in the original entity language.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), 'name' => array( 'description' => 'The name of the test entity.', 'type' => 'varchar', @@ -63,24 +95,15 @@ 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'), ), 'foreign keys' => array( 'uid' => array('users' => 'uid'), + 'id' => array('entity_test' => 'id'), ), - 'primary key' => array('id'), - 'unique keys' => array( - 'uuid' => array('uuid'), - ), + 'primary key' => array('id', 'langcode'), ); return $schema; } diff --git a/core/modules/entity/tests/modules/entity_test/entity_test.module b/core/modules/entity/tests/modules/entity_test/entity_test.module index 67c3085..088706d 100644 --- a/core/modules/entity/tests/modules/entity_test/entity_test.module +++ b/core/modules/entity/tests/modules/entity_test/entity_test.module @@ -11,8 +11,8 @@ function entity_test_entity_info() { $items['entity_test'] = array( 'label' => t('Test entity'), - 'entity class' => 'Drupal\entity\Entity', - 'controller class' => 'Drupal\entity\DatabaseStorageController', + 'entity class' => 'Drupal\entity_test\EntityTest', + 'controller class' => 'Drupal\entity_test\EntityTestStorageController', 'base table' => 'entity_test', 'fieldable' => TRUE, 'entity keys' => array( @@ -35,7 +35,7 @@ function entity_test_entity_info() { * @param bool $reset * A boolean indicating that the internal cache should be reset. * - * @return Drupal\entity\Entity + * @return Drupal\entity_test\EntityTest * The loaded entity object, or FALSE if the entity cannot be loaded. */ function entity_test_load($id, $reset = FALSE) { diff --git a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php new file mode 100644 index 0000000..d39c4b8 --- /dev/null +++ b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php @@ -0,0 +1,138 @@ +langcode = !empty($values['langcode']) ? $values['langcode'] : LANGUAGE_NOT_SPECIFIED; + + // Set initial values ensuring that only real properties are stored. + // @todo For now we have no way to mark a property as multlingual hence we + // just assume that all of them are. + unset($values['id'], $values['default_langcode']); + $this->setProperties($values, $this->langcode); + } + + /** + * Sets the entity original langcode. + * + * @param $langcode + */ + public function setLangcode($langcode) { + // If the original language is changed the related properties must change + // their language accordingly. + $prev_langcode = $this->langcode; + if (isset($this->properties[$prev_langcode])) { + $this->properties[$langcode] = $this->properties[$prev_langcode]; + unset($this->properties[$prev_langcode]); + } + $this->langcode = $langcode; + } + + /** + * Overrides EntityInterface::get(). + */ + public function get($property_name, $langcode = NULL) { + $langcode = !empty($langcode) ? $langcode : $this->langcode; + $entity_info = $this->entityInfo(); + if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) { + return parent::get($property_name, $langcode); + } + else { + return isset($this->properties[$langcode][$property_name]) ? $this->properties[$langcode][$property_name] : NULL; + } + } + + /** + * Overrides EntityInterface::set(). + */ + public function set($property_name, $value, $langcode = NULL) { + $langcode = !empty($langcode) ? $langcode : $this->langcode; + if (!isset(self::$langcodes[$langcode])) { + throw new InvalidArgumentException("Detected an invalid language '$langcode' while setting '$property_name' to '$value'."); + } + $entity_info = $this->entityInfo(); + if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) { + parent::set($property_name, $value, $langcode); + } + else { + $this->properties[$langcode][$property_name] = $value; + } + } + + /** + * Overrides EntityInterface::translations(). + */ + public function translations() { + $translations = !empty($this->properties) ? $this->properties : array(); + $languages = array_intersect_key(self::$langcodes, $translations); + unset($languages[$this->langcode]); + return $languages + parent::translations(); + } + + /** + * Returns the property array for the given language. + * + * @param string $langcode + * (optional) The language code to be used to retrieve the properties. + */ + public function getProperties($langcode = NULL) { + $langcode = !empty($langcode) ? $langcode : $this->langcode; + return isset($this->properties[$langcode]) ? $this->properties[$langcode] : array(); + } + + /** + * Sets the property array for the given language. + * + * @param array $properties + * A keyed array of properties to be set with their 'langcode' as one of the + * keys. If no language is provided the entity language is used. + * @param string $langcode + * (optional) The language code to be used to set the properties. + */ + public function setProperties(array $properties, $langcode = NULL) { + $langcode = !empty($langcode) ? $langcode : $this->langcode; + $this->properties[$langcode] = $properties; + } +} diff --git a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php new file mode 100644 index 0000000..5c35d91 --- /dev/null +++ b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php @@ -0,0 +1,116 @@ +distinct(TRUE) + ->fields('data', array('id')) + ->addTag($this->entityType . '_load_multiple'); + + if ($ids) { + $query->condition("data.id", $ids, 'IN'); + } + + if ($conditions) { + // @todo We should not be using a condition to specify whether conditions + // apply to the default language or not. We need to move this to a + // separate parameter during the following API refactoring. + // Default to the original entity language if not explicitly specified + // otherwise. + if (!array_key_exists('default_langcode', $conditions)) { + $conditions['default_langcode'] = 1; + } + // If the 'default_langcode' flag is esplicitly not set, we do not care + // whether the queried values are in the original entity language or not. + elseif ($conditions['default_langcode'] === NULL) { + unset($conditions['default_langcode']); + } + + foreach ($conditions as $field => $value) { + $query->condition('data.' . $field, $value); + } + } + + return $query; + } + + /** + * Overrides Drupal\entity\DatabaseStorageController::attachLoad(). + */ + protected function attachLoad(&$queried_entities, $revision_id = FALSE) { + $data = db_select('entity_test_property_data', 'data', array('fetch' => PDO::FETCH_ASSOC)) + ->fields('data') + ->condition('id', array_keys($queried_entities)) + ->orderBy('data.id') + ->execute(); + + foreach ($data as $values) { + $entity = $queried_entities[$values['id']]; + $langcode = $values['langcode']; + if (!empty($values['default_langcode'])) { + $entity->setLangcode($langcode); + } + // Make sure only real properties are stored. + unset($values['id'], $values['default_langcode']); + $entity->setProperties($values, $langcode); + } + + parent::attachLoad($queried_entities, $revision_id); + } + + /** + * Overrides Drupal\entity\DatabaseStorageController::postSave(). + */ + protected function postSave(EntityInterface $entity, $update) { + $default_langcode = ($language = $entity->language()) ? $language->langcode : LANGUAGE_NOT_SPECIFIED; + $langcodes = array_keys($entity->translations()); + $langcodes[] = $default_langcode; + + foreach ($langcodes as $langcode) { + $properties = $entity->getProperties($langcode); + + $values = array( + 'id' => $entity->id(), + 'langcode' => $langcode, + 'default_langcode' => intval($default_langcode == $langcode), + ) + $properties; + + db_merge('entity_test_property_data') + ->fields($values) + ->condition('id', $values['id']) + ->condition('langcode', $values['langcode']) + ->execute(); + } + } + + /** + * Overrides Drupal\entity\DatabaseStorageController::postDelete(). + */ + protected function postDelete($entities) { + db_delete('entity_test_property_data') + ->condition('id', array_keys($entities)) + ->execute(); + } +}