diff --git a/core/lib/Drupal/Core/File/File.php b/core/lib/Drupal/Core/File/File.php index b35f5ee..2e5e67d 100644 --- a/core/lib/Drupal/Core/File/File.php +++ b/core/lib/Drupal/Core/File/File.php @@ -22,6 +22,13 @@ class File extends Entity { public $fid; /** + * The file UUID. + * + * @var string + */ + public $uuid; + + /** * The file language code. * * @var string diff --git a/core/modules/comment/comment.install b/core/modules/comment/comment.install index 4b3c9fc..0a147b5 100644 --- a/core/modules/comment/comment.install +++ b/core/modules/comment/comment.install @@ -91,6 +91,12 @@ function comment_schema() { 'not null' => TRUE, 'description' => 'Primary Key: Unique comment ID.', ), + 'uuid' => array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), 'pid' => array( 'type' => 'int', 'not null' => TRUE, @@ -183,6 +189,9 @@ function comment_schema() { 'comment_created' => array('created'), ), 'primary key' => array('cid'), + 'unique keys' => array( + 'uuid' => array('uuid'), + ), 'foreign keys' => array( 'comment_node' => array( 'table' => 'node', @@ -282,6 +291,32 @@ function comment_update_8000() { } /** + * Create a UUID column for comments. + * + * @todo UUID upgrade path: http://drupal.org/node/1642526 + */ +function comment_update_8001() { + $spec = array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ); + $keys = array( + 'unique keys' => array( + 'uuid' => array('uuid'), + ), + ); + // Account for sites having the contributed UUID module installed. + if (db_field_exists('comment', 'uuid')) { + db_change_field('comment', 'uuid', 'uuid', $spec, $keys); + } + else { + db_add_field('comment', 'uuid', $spec, $keys); + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index a6230f2..815fce3 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -109,6 +109,7 @@ function comment_entity_info() { 'id' => 'cid', 'bundle' => 'node_type', 'label' => 'subject', + 'uuid' => 'uuid', ), 'bundles' => array(), 'view modes' => array( diff --git a/core/modules/comment/lib/Drupal/comment/Comment.php b/core/modules/comment/lib/Drupal/comment/Comment.php index 101a679..a7bacd4 100644 --- a/core/modules/comment/lib/Drupal/comment/Comment.php +++ b/core/modules/comment/lib/Drupal/comment/Comment.php @@ -22,6 +22,13 @@ class Comment extends Entity { public $cid; /** + * The comment UUID. + * + * @var string + */ + public $uuid; + + /** * The parent comment ID if this is a reply to a comment. * * @var integer diff --git a/core/modules/entity/entity.api.php b/core/modules/entity/entity.api.php index 02d8754..5359376 100644 --- a/core/modules/entity/entity.api.php +++ b/core/modules/entity/entity.api.php @@ -76,6 +76,9 @@ * 'subject' should be specified here. If complex logic is required to * build the label, a 'label callback' should be defined instead (see * the 'label callback' section above for details). + * - uuid (optional): The name of the property that contains the universally + * unique identifier of the entity, which is used to distinctly identify + * an entity across different systems. * - bundle keys: An array describing how the Field API can extract the * information it needs from the bundle objects for this type (e.g * $vocabulary objects for terms; not applicable for nodes). This entry can @@ -147,6 +150,7 @@ function hook_entity_info() { 'id' => 'nid', 'revision' => 'vid', 'bundle' => 'type', + 'uuid' => 'uuid', ), 'bundle keys' => array( 'bundle' => 'type', diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module index a66b1e8..7514458 100644 --- a/core/modules/entity/entity.module +++ b/core/modules/entity/entity.module @@ -5,7 +5,9 @@ * Entity API for handling entities like nodes or users. */ +use Drupal\entity\EntityFieldQuery; use Drupal\entity\EntityMalformedException; +use Drupal\entity\EntityStorageException; use Drupal\entity\EntityInterface; /** @@ -217,6 +219,64 @@ function entity_load($entity_type, $id, $reset = FALSE) { } /** + * Loads an entity by UUID. + * + * Note that some entity types may not support UUIDs. + * + * @see hook_entity_info() + * + * @param string $entity_type + * The entity type to load; e.g., 'node' or 'user'. + * @param string $uuid + * The UUID of the entity to load. + * @param bool $reset + * Whether to reset the internal cache for the requested entity type. + * + * @return EntityInterface|FALSE + * The entity object, or FALSE if there is no entity with the given UUID. + * + * @todo Provide an entity CRUD controller API to execute proper property-based + * lookups. This uses an EntityFieldQuery, since the $conditions parameter of + * EntityStorageControllerInterface::load() is deprecated, although it would + * be the most simple solution for entity-level properties that are expected + * to universally exist for all entity types. At the same time, the entire + * logic of this function absolutely belongs into the entity storage + * controller, which has all required information readily at hand. However, we + * do not want to pollute EntityStorageControllerInterface with property + * specific ::loadByUUID() methods; a generic ::loadByProperty() method would + * be more appropriate, but potentially breaks the separation of concerns. + * Overall, this is way too much code to retrieve a single entity and the API + * definitely has to be cleaned up for Drupal 8. + */ +function entity_load_by_uuid($entity_type, $uuid, $reset = FALSE) { + $entity_info = entity_get_info($entity_type); + if (empty($entity_info['entity keys']['uuid'])) { + throw new EntityStorageException("Entity type $entity_type does not support UUIDs."); + } + $uuid_key = $entity_info['entity keys']['uuid']; + + // Look up the entity ID for the given UUID. + $entity_query = new EntityFieldQuery(); + $result = $entity_query + ->entityCondition('entity_type', $entity_type) + ->propertyCondition($uuid_key, $uuid) + ->range(0, 1) + ->execute(); + + if (empty($result[$entity_type])) { + return FALSE; + } + + $controller = entity_get_controller($entity_type); + if ($reset) { + $controller->resetCache(); + } + $id = key($result[$entity_type]); + $entities = $controller->load(array($id)); + return isset($entities[$id]) ? $entities[$id] : FALSE; +} + +/** * Loads multiple entities from the database. * * This function should be used whenever you need to load more than one entity diff --git a/core/modules/entity/lib/Drupal/entity/DatabaseStorageController.php b/core/modules/entity/lib/Drupal/entity/DatabaseStorageController.php index 8d89341..8c4f3c8 100644 --- a/core/modules/entity/lib/Drupal/entity/DatabaseStorageController.php +++ b/core/modules/entity/lib/Drupal/entity/DatabaseStorageController.php @@ -8,6 +8,8 @@ namespace Drupal\entity; use PDO; +use Drupal\Component\Uuid\Uuid; + /** * Defines a base entity controller class. @@ -59,6 +61,15 @@ class DatabaseStorageController implements EntityStorageControllerInterface { protected $idKey; /** + * Name of entity's UUID database table field, if it supports UUIDs. + * + * Has the value FALSE if this entity does not use UUIDs. + * + * @var string + */ + protected $uuidKey; + + /** * Name of entity's revision database table field, if it supports revisions. * * Has the value FALSE if this entity does not use revisions. @@ -95,6 +106,14 @@ class DatabaseStorageController implements EntityStorageControllerInterface { $this->hookLoadArguments = array(); $this->idKey = $this->entityInfo['entity keys']['id']; + // Check if the entity type supports UUIDs. + if (!empty($this->entityInfo['entity keys']['uuid'])) { + $this->uuidKey = $this->entityInfo['entity keys']['uuid']; + } + else { + $this->uuidKey = FALSE; + } + // Check if the entity type supports revisions. if (!empty($this->entityInfo['entity keys']['revision'])) { $this->revisionKey = $this->entityInfo['entity keys']['revision']; @@ -366,7 +385,16 @@ class DatabaseStorageController implements EntityStorageControllerInterface { */ public function create(array $values) { $class = isset($this->entityInfo['entity class']) ? $this->entityInfo['entity class'] : 'Drupal\entity\Entity'; - return new $class($values, $this->entityType); + + $entity = new $class($values, $this->entityType); + + // Assign a new UUID if there is none yet. + if ($this->uuidKey && !isset($entity->{$this->uuidKey})) { + $uuid = new Uuid(); + $entity->{$this->uuidKey} = $uuid->generate(); + } + + return $entity; } /** diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityUUIDTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityUUIDTest.php new file mode 100644 index 0000000..c2f42c5 --- /dev/null +++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityUUIDTest.php @@ -0,0 +1,65 @@ + 'Entity UUIDs', + 'description' => 'Tests creation, saving, and loading of entity UUIDs.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp(array('entity_test')); + } + + /** + * Tests UUID generation in entity CRUD operations. + */ + function testCRUD() { + // Verify that no UUID is auto-generated when passing one for creation. + $uuid_service = new Uuid(); + $uuid = $uuid_service->generate(); + $custom_entity = entity_create('entity_test', array( + 'name' => $this->randomName(), + 'uuid' => $uuid, + )); + $this->assertIdentical($custom_entity->get('uuid'), $uuid); + // Save this entity, so we have more than one later. + $custom_entity->save(); + + // Verify that a new UUID is generated upon creating an entity. + $entity = entity_create('entity_test', array('name' => $this->randomName())); + $uuid = $entity->get('uuid'); + $this->assertTrue($uuid); + + // Verify that the new UUID is different. + $this->assertNotEqual($custom_entity->get('uuid'), $uuid); + + // Verify that the UUID is retained upon saving. + $entity->save(); + $this->assertIdentical($entity->get('uuid'), $uuid); + + // Verify that the UUID is retained upon loading. + $entity_loaded = entity_test_load($entity->id(), TRUE); + $this->assertIdentical($entity_loaded->get('uuid'), $uuid); + + // Verify that entity_load_by_uuid() loads the same entity. + $entity_loaded_by_uuid = entity_load_by_uuid('entity_test', $uuid, TRUE); + $this->assertIdentical($entity_loaded_by_uuid->get('uuid'), $uuid); + $this->assertEqual($entity_loaded_by_uuid, $entity_loaded); + } +} 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 c0c7703..1bef390 100644 --- a/core/modules/entity/tests/modules/entity_test/entity_test.install +++ b/core/modules/entity/tests/modules/entity_test/entity_test.install @@ -43,6 +43,12 @@ function entity_test_schema() { 'not null' => TRUE, 'description' => 'Primary Key: Unique entity-test item ID.', ), + 'uuid' => array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), 'name' => array( 'description' => 'The name of the test entity.', 'type' => 'varchar', @@ -72,6 +78,9 @@ function entity_test_schema() { 'uid' => array('users' => 'uid'), ), 'primary key' => array('id'), + 'unique keys' => array( + 'uuid' => array('uuid'), + ), ); 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 f97d2e9..67c3085 100644 --- a/core/modules/entity/tests/modules/entity_test/entity_test.module +++ b/core/modules/entity/tests/modules/entity_test/entity_test.module @@ -17,6 +17,7 @@ function entity_test_entity_info() { 'fieldable' => TRUE, 'entity keys' => array( 'id' => 'id', + 'uuid' => 'uuid', ), ); // Optionally specify a translation handler for testing translations. diff --git a/core/modules/node/lib/Drupal/node/Node.php b/core/modules/node/lib/Drupal/node/Node.php index cee6967..fad5c4f 100644 --- a/core/modules/node/lib/Drupal/node/Node.php +++ b/core/modules/node/lib/Drupal/node/Node.php @@ -29,6 +29,13 @@ class Node extends Entity { public $vid; /** + * The node UUID. + * + * @var string + */ + public $uuid; + + /** * The node content type (bundle). * * @var string diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 7382320..054991d 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -18,6 +18,12 @@ function node_schema() { 'unsigned' => TRUE, 'not null' => TRUE, ), + 'uuid' => array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), // Defaults to NULL in order to avoid a brief period of potential // deadlocks on the index. 'vid' => array( @@ -117,6 +123,7 @@ function node_schema() { ), 'unique keys' => array( 'vid' => array('vid'), + 'uuid' => array('uuid'), ), 'foreign keys' => array( 'node_revision' => array( @@ -568,6 +575,32 @@ function node_update_8003() { } /** + * Create a UUID column for nodes. + * + * @todo UUID upgrade path: http://drupal.org/node/1642526 + */ +function node_update_8004() { + $spec = array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ); + $keys = array( + 'unique keys' => array( + 'uuid' => array('uuid'), + ), + ); + // Account for sites having the contributed UUID module installed. + if (db_field_exists('node', 'uuid')) { + db_change_field('node', 'uuid', 'uuid', $spec, $keys); + } + else { + db_add_field('node', 'uuid', $spec, $keys); + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x" * The next series of updates should start at 9000. */ diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 319f422..6a0e990 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -204,6 +204,7 @@ function node_entity_info() { 'revision' => 'vid', 'bundle' => 'type', 'label' => 'title', + 'uuid' => 'uuid', ), 'bundle keys' => array( 'bundle' => 'type', diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 16bac0f..08d97c8 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -866,6 +866,12 @@ function system_schema() { 'unsigned' => TRUE, 'not null' => TRUE, ), + 'uuid' => array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), 'uid' => array( 'description' => 'The {users}.uid of the user who is associated with the file.', 'type' => 'int', @@ -931,6 +937,7 @@ function system_schema() { 'timestamp' => array('timestamp'), ), 'unique keys' => array( + 'uuid' => array('uuid'), 'uri' => array('uri'), ), 'primary key' => array('fid'), @@ -1938,6 +1945,32 @@ function system_update_8011() { } /** + * Create a UUID column for managed files. + * + * @todo UUID upgrade path: http://drupal.org/node/1642526 + */ +function system_update_8012() { + $spec = array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ); + $keys = array( + 'unique keys' => array( + 'uuid' => array('uuid'), + ), + ); + // Account for sites having the contributed UUID module installed. + if (db_field_exists('file_managed', 'uuid')) { + db_change_field('file_managed', 'uuid', 'uuid', $spec, $keys); + } + else { + db_add_field('file_managed', 'uuid', $spec, $keys); + } +} + +/** * @} End of "defgroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 75b1fa2..4fee147 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -280,6 +280,7 @@ function system_entity_info() { 'entity keys' => array( 'id' => 'fid', 'label' => 'filename', + 'uuid' => 'uuid', ), 'static cache' => FALSE, ), diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Term.php index f036f18..929422c 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Term.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Term.php @@ -22,6 +22,13 @@ class Term extends Entity { public $tid; /** + * The term UUID. + * + * @var string + */ + public $uuid; + + /** * The taxonomy vocabulary ID this term belongs to. * * @var integer diff --git a/core/modules/taxonomy/taxonomy.install b/core/modules/taxonomy/taxonomy.install index 14b6956..361dc6a 100644 --- a/core/modules/taxonomy/taxonomy.install +++ b/core/modules/taxonomy/taxonomy.install @@ -32,6 +32,12 @@ function taxonomy_schema() { 'not null' => TRUE, 'description' => 'Primary Key: Unique term ID.', ), + 'uuid' => array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), 'vid' => array( 'type' => 'int', 'unsigned' => TRUE, @@ -75,6 +81,9 @@ function taxonomy_schema() { ), ), 'primary key' => array('tid'), + 'unique keys' => array( + 'uuid' => array('uuid'), + ), 'foreign keys' => array( 'vocabulary' => array( 'table' => 'taxonomy_vocabulary', @@ -300,3 +309,29 @@ function taxonomy_update_8001() { } } } + +/** + * Create a UUID column for taxonomy terms. + * + * @todo UUID upgrade path: http://drupal.org/node/1642526 + */ +function taxonomy_update_8002() { + $spec = array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ); + $keys = array( + 'unique keys' => array( + 'uuid' => array('uuid'), + ), + ); + // Account for sites having the contributed UUID module installed. + if (db_field_exists('taxonomy_term_data', 'uuid')) { + db_change_field('taxonomy_term_data', 'uuid', 'uuid', $spec, $keys); + } + else { + db_add_field('taxonomy_term_data', 'uuid', $spec, $keys); + } +} diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 6697a0a..b31bab9 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -120,6 +120,7 @@ function taxonomy_entity_info() { 'id' => 'tid', 'bundle' => 'vocabulary_machine_name', 'label' => 'name', + 'uuid' => 'uuid', ), 'bundle keys' => array( 'bundle' => 'machine_name', diff --git a/core/modules/user/lib/Drupal/user/User.php b/core/modules/user/lib/Drupal/user/User.php index 992923c..25e27f1 100644 --- a/core/modules/user/lib/Drupal/user/User.php +++ b/core/modules/user/lib/Drupal/user/User.php @@ -22,6 +22,13 @@ class User extends Entity { public $uid; /** + * The user UUID. + * + * @var string + */ + public $uuid; + + /** * The unique user name. * * @var string diff --git a/core/modules/user/user.install b/core/modules/user/user.install index 5ec57da..abb985b 100644 --- a/core/modules/user/user.install +++ b/core/modules/user/user.install @@ -132,6 +132,12 @@ function user_schema() { 'description' => 'Primary Key: Unique user ID.', 'default' => 0, ), + 'uuid' => array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), 'name' => array( 'type' => 'varchar', 'length' => 60, @@ -246,6 +252,7 @@ function user_schema() { 'picture' => array('picture'), ), 'unique keys' => array( + 'uuid' => array('uuid'), 'name' => array('name'), ), 'primary key' => array('uid'), @@ -442,5 +449,31 @@ function user_update_8002() { } /** + * Create a UUID column for users. + * + * @todo UUID upgrade path: http://drupal.org/node/1642526 + */ +function user_update_8003() { + $spec = array( + 'description' => 'Unique Key: Universally unique identifier for this entity.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ); + $keys = array( + 'unique keys' => array( + 'uuid' => array('uuid'), + ), + ); + // Account for sites having the contributed UUID module installed. + if (db_field_exists('users', 'uuid')) { + db_change_field('users', 'uuid', 'uuid', $spec, $keys); + } + else { + db_add_field('users', 'uuid', $spec, $keys); + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". */ diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 1e7aa99..156c61b 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -154,6 +154,7 @@ function user_entity_info() { 'entity class' => 'Drupal\user\User', 'entity keys' => array( 'id' => 'uid', + 'uuid' => 'uuid', ), 'bundles' => array( 'user' => array(