diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 9e83814..1f99e5f 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -340,6 +340,13 @@ public function getRevisionId() { /** * {@inheritdoc} */ + public function getRevisionUuid() { + return $this->getEntityKey('revision_uuid'); + } + + /** + * {@inheritdoc} + */ public function isTranslatable() { // Check that the bundle is translatable, the entity has a language defined // and if we have more than one language on the site. @@ -1001,6 +1008,12 @@ public function createDuplicate() { // Check whether the entity type supports revisions and initialize it if so. if ($entity_type->isRevisionable()) { $duplicate->{$entity_type->getKey('revision')}->value = NULL; + + // Check if the entity type supports revision UUIDs and generate a new one + // if so. + if ($entity_type->hasKey('revision_uuid')) { + $duplicate->{$entity_type->getKey('revision_uuid')}->value = $this->uuidGenerator()->generate(); + } } return $duplicate; diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php index a7d1134..3eba84a 100644 --- a/core/lib/Drupal/Core/Entity/EntityType.php +++ b/core/lib/Drupal/Core/Entity/EntityType.php @@ -269,6 +269,7 @@ public function __construct($definition) { // Ensure defaults. $this->entity_keys += array( 'revision' => '', + 'revision_uuid' => '', 'bundle' => '', 'langcode' => '', 'default_langcode' => 'default_langcode', diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php index 1120eff..32efa95 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php @@ -93,6 +93,10 @@ public function getOriginalClass(); * revision ID of the entity. The Field API assumes that all revision IDs * are unique across all entities of a type. If this entry is omitted * the entities of this type are not revisionable. + * - revision_uuid: (optional) The name of the property that contains the + * revision UUID of the entity. + * @todo Should the revision_uuid key be populated by default if the + * revision key is set? * - bundle: (optional) The name of the property that contains the bundle * name for the entity. The bundle name defines which set of fields are * attached to the entity (e.g. what nodes call "content type"). This diff --git a/core/lib/Drupal/Core/Entity/RevisionableInterface.php b/core/lib/Drupal/Core/Entity/RevisionableInterface.php index f9433bc..e120c96 100644 --- a/core/lib/Drupal/Core/Entity/RevisionableInterface.php +++ b/core/lib/Drupal/Core/Entity/RevisionableInterface.php @@ -45,6 +45,15 @@ public function setNewRevision($value = TRUE); public function getRevisionId(); /** + * Gets the revision universally-unique identifier of the entity. + * + * @return string + * The revision UUID of the entity, or NULL if the entity does not + * have a revision UUID. + */ + public function getRevisionUuid(); + + /** * Checks if this entity is the default revision. * * @param bool $new_value diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index 2b4f9a4..5acf92e 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Entity\Sql; +use Drupal\Component\Uuid\Uuid; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Database; @@ -60,6 +61,15 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt protected $revisionKey = FALSE; /** + * Name of entity's revision UUID database field, if it supports revisions. + * + * Has the value FALSE if this entity does not use revisions. + * + * @var string + */ + protected $revisionUuidKey = FALSE; + + /** * The entity langcode key. * * @var string|bool @@ -175,6 +185,7 @@ protected function initTableLayout() { // are correctly reflected in the table layout. $this->tableMapping = NULL; $this->revisionKey = NULL; + $this->revisionUuidKey = NULL; $this->revisionTable = NULL; $this->dataTable = NULL; $this->revisionDataTable = NULL; @@ -185,6 +196,7 @@ protected function initTableLayout() { $revisionable = $this->entityType->isRevisionable(); if ($revisionable) { $this->revisionKey = $this->entityType->getKey('revision') ?: 'revision_id'; + $this->revisionUuidKey = $this->entityType->getKey('revision_uuid') ?: 'revision_uuid'; $this->revisionTable = $this->entityType->getRevisionTable() ?: $this->entityTypeId . '_revision'; } $translatable = $this->entityType->isTranslatable(); @@ -291,7 +303,7 @@ public function getTableMapping(array $storage_definitions = NULL) { return $table_mapping->allowsSharedTableStorage($definition); }); - $key_fields = array_values(array_filter(array($this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey))); + $key_fields = array_values(array_filter(array($this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->revisionUuidKey, $this->langcodeKey))); $all_fields = array_keys($definitions); $revisionable_fields = array_keys(array_filter($definitions, function (FieldStorageDefinitionInterface $definition) { return $definition->isRevisionable(); @@ -321,7 +333,7 @@ public function getTableMapping(array $storage_definitions = NULL) { // denormalized in the base table but also stored in the revision table // together with the entity ID and the revision ID as identifiers. $table_mapping->setFieldNames($this->baseTable, array_diff($all_fields, $revision_metadata_fields)); - $revision_key_fields = array($this->idKey, $this->revisionKey); + $revision_key_fields = array($this->idKey, $this->revisionKey, $this->revisionUuidKey); $table_mapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields)); } elseif (!$revisionable && $translatable) { @@ -351,10 +363,10 @@ public function getTableMapping(array $storage_definitions = NULL) { $data_fields = array_values(array_diff($all_fields, array($this->uuidKey), $revision_metadata_fields)); $table_mapping->setFieldNames($this->dataTable, $data_fields); - $revision_base_fields = array_merge(array($this->idKey, $this->revisionKey, $this->langcodeKey), $revision_metadata_fields); + $revision_base_fields = array_merge(array($this->idKey, $this->revisionKey, $this->revisionUuidKey, $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_key_fields = array($this->idKey, $this->revisionKey, $this->revisionUuidKey, $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)); } @@ -1023,6 +1035,16 @@ protected function saveRevision(ContentEntityInterface $entity) { $entity->preSaveRevision($this, $record); if ($entity->isNewRevision()) { + // Generating a new revision should generate a new revision UUID. There + // are cases where external sources may preset the revision UUID, so we + // need to allow for that here. + if ($this->revisionUuidKey && (empty($record->{$this->revisionUuidKey}) + || (isset($entity->original) + && $entity->original->{$this->revisionUuidKey} == $record->{$this->revisionUuidKey}))) { + $uuid = new Uuid(); + $record->{$this->revisionUuidKey} = $uuid->generate(); + } + $insert_id = $this->database ->insert($this->revisionTable, array('return' => Database::RETURN_INSERT_ID)) ->fields((array) $record) @@ -1047,8 +1069,9 @@ protected function saveRevision(ContentEntityInterface $entity) { ->execute(); } - // Make sure to update the new revision key for the entity. + // Make sure to update the new revision ID and UUID keys for the entity. $entity->{$this->revisionKey}->value = $record->{$this->revisionKey}; + $entity->{$this->revisionUuidKey}->value = $record->{$this->revisionUuidKey}; return $record->{$this->revisionKey}; }