diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php index a14a83f..335e492 100644 --- a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php @@ -166,7 +166,6 @@ public static function open(array &$connection_options = []) { return $pdo; } - /** * Destructor for the SQLite connection. * diff --git a/core/lib/Drupal/Core/Database/Schema.php b/core/lib/Drupal/Core/Database/Schema.php index 80a68f6..bd8d6c3 100644 --- a/core/lib/Drupal/Core/Database/Schema.php +++ b/core/lib/Drupal/Core/Database/Schema.php @@ -305,7 +305,7 @@ public function fieldExists($table, $column) { * created field will be set to the value of the key in all rows. * This is most useful for creating NOT NULL columns with no default * value in existing tables. - * Alternatively, the 'initial_form_field' key may be used, which will + * Alternatively, the 'initial_from_field' key may be used, which will * auto-populate the new field with values from the specified field. * @param $keys_new * (optional) Keys and indexes specification to be created on the diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 0c7fa28..ecd828d 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -18,7 +18,7 @@ * * @ingroup entity_api */ -abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface { +abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface, RevisionUuidInterface { /** * The plain data values of the contained fields. @@ -270,6 +270,11 @@ public function setNewRevision($value = TRUE) { // to ensure that a new revision will actually be created. $this->set($this->getEntityType()->getKey('revision'), NULL); + if ($this->getEntityType()->hasKey('revision_uuid')) { + // Also generate a new revision uuid. + $this->set($this->getEntityType()->getKey('revision_uuid'), \Drupal::service('uuid')->generate()); + } + // Make sure that the flag tracking which translations are affected by the // current revision is reset. foreach ($this->translations as $langcode => $data) { @@ -354,6 +359,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. @@ -1052,6 +1064,12 @@ public function createDuplicate() { if ($entity_type->isRevisionable()) { $duplicate->{$entity_type->getKey('revision')}->value = NULL; $duplicate->loadedRevisionId = 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; @@ -1231,6 +1249,12 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setReadOnly(TRUE) ->setSetting('unsigned', TRUE); } + if ($entity_type->hasKey('revision_uuid')) { + $fields[$entity_type->getKey('revision_uuid')] = BaseFieldDefinition::create('uuid') + ->setLabel(new TranslatableMarkup('Revision UUID')) + ->setRevisionable(TRUE) + ->setReadOnly(TRUE); + } if ($entity_type->hasKey('langcode')) { $fields[$entity_type->getKey('langcode')] = BaseFieldDefinition::create('language') ->setLabel(new TranslatableMarkup('Language')) @@ -1337,6 +1361,10 @@ public function hasTranslationChanges() { if (in_array($field_name, $skip_fields, TRUE)) { continue; } + if ($this->getEntityType()->hasKey('revision_uuid') && + $field_name == $this->getEntityType()->getKey('revision_uuid')) { + continue; + } $field = $this->get($field_name); // When saving entities in the user interface, the changed timestamp is // automatically incremented by ContentEntityForm::submitForm() even if diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php index b39fb3f..d8363e2 100644 --- a/core/lib/Drupal/Core/Entity/EntityType.php +++ b/core/lib/Drupal/Core/Entity/EntityType.php @@ -299,10 +299,12 @@ public function __construct($definition) { // Ensure defaults. $this->entity_keys += [ 'revision' => '', + 'revision_uuid' => '', 'bundle' => '', 'langcode' => '', 'default_langcode' => 'default_langcode', ]; + $this->handlers += [ 'access' => 'Drupal\Core\Entity\EntityAccessControlHandler', ]; diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php index c8a28b4..5355cd0 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php @@ -72,6 +72,8 @@ 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. * - 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/RevisionUuidInterface.php b/core/lib/Drupal/Core/Entity/RevisionUuidInterface.php new file mode 100644 index 0000000..3dcbcb8 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/RevisionUuidInterface.php @@ -0,0 +1,25 @@ +tableMapping = NULL; $this->revisionKey = NULL; + $this->revisionUuidKey = NULL; $this->revisionTable = NULL; $this->dataTable = NULL; $this->revisionDataTable = NULL; @@ -180,6 +188,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'); $this->revisionTable = $this->entityType->getRevisionTable() ?: $this->entityTypeId . '_revision'; } $translatable = $this->entityType->isTranslatable(); @@ -311,7 +320,10 @@ public function getTableMapping(array $storage_definitions = NULL) { // 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 = [$this->idKey, $this->revisionKey]; - $table_mapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields)); + if (!empty($this->revisionUuidKey)) { + $revision_key_fields[] = $this->revisionUuidKey; + } + $table_mapping->setFieldNames($this->revisionTable, array_unique(array_merge($revision_key_fields, $revisionable_fields))); } elseif (!$revisionable && $translatable) { // Multilingual layouts store key field values in the base table. The @@ -337,15 +349,18 @@ public function getTableMapping(array $storage_definitions = NULL) { // 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, [$this->uuidKey], $revision_metadata_fields)); + $data_fields = array_values(array_diff($all_fields, [$this->uuidKey, $this->revisionUuidKey], $revision_metadata_fields)); $table_mapping->setFieldNames($this->dataTable, $data_fields); $revision_base_fields = array_merge([$this->idKey, $this->revisionKey, $this->langcodeKey], $revision_metadata_fields); + if (!empty($this->revisionUuidKey)) { + $revision_base_fields[] = $this->revisionUuidKey; + } $table_mapping->setFieldNames($this->revisionTable, $revision_base_fields); $revision_data_key_fields = [$this->idKey, $this->revisionKey, $this->langcodeKey]; - $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$this->langcodeKey]); - $table_mapping->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields)); + $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$this->langcodeKey, $this->revisionUuidKey]); + $table_mapping->setFieldNames($this->revisionDataTable, array_unique(array_merge($revision_data_key_fields, $revision_data_fields))); } // Add dedicated tables. diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php index f9dd0c0..be28413 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php @@ -201,7 +201,40 @@ public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterfac return FALSE; } - return $this->getSchemaFromStorageDefinition($storage_definition) != $this->loadFieldSchemaData($original); + $storage_definition_schema = $this->getSchemaFromStorageDefinition($storage_definition); + $original_schema = $this->loadFieldSchemaData($original); + + // Filter out irrelevant schema. + return $this->removeIrrelevantSchemaKeys($storage_definition_schema) != $this->removeIrrelevantSchemaKeys($original_schema); + } + + /** + * Remove irrelevant schema keys. + * + * We need to remove irrelevant schema keys before comparing two schemas to + * see if they've changed as there are certain keys that can safely be added + * at any point to existing schema. E.g. initial or initial_from_field. + * + * @param array $schema + * The schema array. + * @param array $remove_keys + * (optional) An array of keys to remove regardless of the depth. + * + * @return array + * The schema with any irrelevant keys removed. + */ + protected function removeIrrelevantSchemaKeys(&$schema, $remove_keys = ['initial', 'initial_from_field']) { + foreach ($schema as $key => &$value) { + if (is_array($value)) { + $this->removeIrrelevantSchemaKeys($value, $remove_keys); + } + else { + if (in_array($key, $remove_keys, TRUE)) { + unset($schema[$key]); + } + } + } + return $schema; } /** @@ -1180,7 +1213,9 @@ protected function createSharedTableSchema(FieldStorageDefinitionInterface $stor } if (!empty($schema[$table_name]['unique keys'])) { foreach ($schema[$table_name]['unique keys'] as $name => $specifier) { - $schema_handler->addUniqueKey($table_name, $name, $specifier); + // Check if the unique key exists because it might already have + // been created as part of the earlier entity type update event. + $this->addUniqueKey($table_name, $name, $specifier); } } } @@ -1971,7 +2006,19 @@ protected function isTableEmpty($table_name) { * Returns TRUE if there are schema changes in the column definitions. */ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { - if ($storage_definition->getColumns() != $original->getColumns()) { + // Remove any fields that can be changed in the column array structure such + // as initial. + $new_columns = $storage_definition->getColumns(); + $original_columns = $original->getColumns(); + foreach ($new_columns as $delta => $column) { + foreach ($column as $key => $value) { + if (in_array($key, ['initial', 'initial_from_field'], TRUE)) { + unset($new_columns[$delta][$key]); + unset($original_columns[$delta][$key]); + } + } + } + if ($new_columns != $original_columns) { // Base field definitions have schema data stored in the original // definition. return TRUE; diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UuidItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UuidItem.php index a5f9401..34ff52c 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UuidItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UuidItem.php @@ -4,6 +4,7 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\FieldDefinitionInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; /** * Defines the 'uuid' entity field type. @@ -46,6 +47,19 @@ public function applyDefaultValue($notify = TRUE) { public static function schema(FieldStorageDefinitionInterface $field_definition) { $schema = parent::schema($field_definition); $schema['unique keys']['value'] = ['value']; + + // @todo inject an initial value in a better way. + try { + $entity_type = \Drupal::entityTypeManager() + ->getDefinition($field_definition->getTargetEntityTypeId()); + if ($entity_type->hasKey('revision') && $entity_type->getKey('revision_uuid') == $field_definition->getName()) { + $schema['columns']['value']['initial_from_field'] = $entity_type->getKey('revision'); + } + } + catch (ServiceNotFoundException $exception) { + // Just a hack to get \Drupal\Tests\views\Unit\EntityViewsDataTest passing. + } + return $schema; } diff --git a/core/modules/block_content/block_content.install b/core/modules/block_content/block_content.install index 6af2ac4..767f92e 100644 --- a/core/modules/block_content/block_content.install +++ b/core/modules/block_content/block_content.install @@ -6,6 +6,7 @@ */ use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Add 'revision_translation_affected' field to 'block_content' entities. @@ -72,3 +73,19 @@ function block_content_update_8300() { $definition_update_manager->updateEntityType($entity_type); } + +/** + * Install revision_uuid base field. + */ +function block_content_update_8301() { + $revision_uuid = BaseFieldDefinition::create('uuid') + ->setLabel(new TranslatableMarkup('Revision UUID')) + ->setRevisionable(TRUE) + ->setReadOnly(TRUE); + \Drupal::entityDefinitionUpdateManager()->installFieldStorageDefinition('revision_uuid', 'block_content', 'block_content', $revision_uuid); + + $state = \Drupal::state(); + $revision_uuid_update = $state->get('revision_uuid_update'); + $revision_uuid_update[] = 'block_content'; + $state->set('revision_uuid_update', $revision_uuid_update); +} diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index b7c3d4d..55c7454 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -47,6 +47,7 @@ * entity_keys = { * "id" = "id", * "revision" = "revision_id", + * "revision_uuid" = "revision_uuid", * "bundle" = "type", * "label" = "info", * "langcode" = "langcode", diff --git a/core/modules/block_content/src/Tests/BlockContentUIDUpdateTest.php b/core/modules/block_content/src/Tests/BlockContentUIDUpdateTest.php new file mode 100644 index 0000000..ec17121 --- /dev/null +++ b/core/modules/block_content/src/Tests/BlockContentUIDUpdateTest.php @@ -0,0 +1,39 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../system/tests/fixtures/update/drupal-8-rc1.filled.standard.php.gz', + ]; + } + + /** + * Tests block_content_update_8300(). + * + * @see block_content_update_8300() + */ + public function testRevisionUuidUpdate() { + $this->runUpdates(); + $this->cronRun(); + + $block_contents = \Drupal::entityTypeManager()->getStorage('block_content')->loadMultiple(); + foreach ($block_contents as $block_content) { + $this->assertTrue(Uuid::isValid($block_content->get('revision_uuid')->value)); + } + } + +} diff --git a/core/modules/comment/src/CommentStorage.php b/core/modules/comment/src/CommentStorage.php index d164d8a..bc52e97 100644 --- a/core/modules/comment/src/CommentStorage.php +++ b/core/modules/comment/src/CommentStorage.php @@ -2,6 +2,7 @@ namespace Drupal\comment; +use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityManagerInterface; @@ -43,6 +44,8 @@ class CommentStorage extends SqlContentEntityStorage implements CommentStorageIn * Cache backend instance to use. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. + * @param \Drupal\Component\Uuid\UuidInterface $uuid_service + * The UUID service. */ public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, AccountInterface $current_user, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) { parent::__construct($entity_info, $database, $entity_manager, $cache, $language_manager); diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 4e2c240..6123ebd 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -7,6 +7,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\user\RoleInterface; /** @@ -246,3 +247,19 @@ function node_update_8301() { $entity_type->set('entity_keys', $keys); $definition_update_manager->updateEntityType($entity_type); } + + /** + * Install revision_uuid base field. + */ +function node_update_8302() { + $revision_uuid = BaseFieldDefinition::create('uuid') + ->setLabel(new TranslatableMarkup('Revision UUID')) + ->setRevisionable(TRUE) + ->setReadOnly(TRUE); + \Drupal::entityDefinitionUpdateManager()->installFieldStorageDefinition('revision_uuid', 'node', 'node', $revision_uuid); + + $state = \Drupal::state(); + $revision_uuid_update = $state->get('revision_uuid_update'); + $revision_uuid_update[] = 'node'; + $state->set('revision_uuid_update', $revision_uuid_update); +} diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index c11a188..530e4e0 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -53,6 +53,7 @@ * entity_keys = { * "id" = "nid", * "revision" = "vid", + * "revision_uuid" = "revision_uuid", * "bundle" = "type", * "label" = "title", * "langcode" = "langcode", diff --git a/core/modules/node/src/Tests/Update/NodeUpdateTest.php b/core/modules/node/src/Tests/Update/NodeUpdateTest.php index b8b30be..e1d8964 100644 --- a/core/modules/node/src/Tests/Update/NodeUpdateTest.php +++ b/core/modules/node/src/Tests/Update/NodeUpdateTest.php @@ -2,6 +2,7 @@ namespace Drupal\node\Tests\Update; +use Drupal\Component\Uuid\Uuid; use Drupal\system\Tests\Update\UpdatePathTestBase; /** @@ -16,7 +17,7 @@ class NodeUpdateTest extends UpdatePathTestBase { */ protected function setDatabaseDumpFiles() { $this->databaseDumpFiles = [ - __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz', + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.filled.standard.php.gz', ]; } @@ -38,4 +39,19 @@ public function testPublishedEntityKey() { $this->assertEqual('status', $entity_type->getKey('published')); } + /** + * Tests node_update_8302(). + * + * @see node_update_8302() + */ + public function testRevisionUuidUpdate() { + $this->runUpdates(); + $this->cronRun(); + + $nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple(); + foreach ($nodes as $node) { + $this->assertTrue(Uuid::isValid($node->get('revision_uuid')->value)); + } + } + } diff --git a/core/modules/node/src/Tests/Views/FilterUidRevisionTest.php b/core/modules/node/src/Tests/Views/FilterUidRevisionTest.php index 1827300..9132d21 100644 --- a/core/modules/node/src/Tests/Views/FilterUidRevisionTest.php +++ b/core/modules/node/src/Tests/Views/FilterUidRevisionTest.php @@ -35,7 +35,7 @@ public function testFilter() { $expected_result[] = ['nid' => $node->id()]; $revision = clone $node; // Force to add a new revision. - $revision->set('vid', NULL); + $revision->setNewRevision(TRUE); $revision->set('revision_uid', $author->id()); $revision->save(); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php index cfbd971..481287a 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php @@ -97,6 +97,9 @@ protected function getExpectedNormalizedEntity() { 'vid' => [ ['value' => 1], ], + 'revision_uuid' => [ + ['value' => $this->entity->getRevisionUuid()], + ], 'langcode' => [ [ 'value' => 'en', diff --git a/core/modules/system/src/Controller/FieldValueUpdater.php b/core/modules/system/src/Controller/FieldValueUpdater.php new file mode 100644 index 0000000..46a9e1e --- /dev/null +++ b/core/modules/system/src/Controller/FieldValueUpdater.php @@ -0,0 +1,39 @@ +revisionUuidUpdater = $revision_uuid_updater; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('system.revision_uuid_updater') + ); + } + + /** + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function update() { + $this->revisionUuidUpdater->updateAll(); + return $this->redirect('system.admin_reports'); + } +} diff --git a/core/modules/system/src/RevisionUuidUpdater.php b/core/modules/system/src/RevisionUuidUpdater.php new file mode 100644 index 0000000..098cc2f --- /dev/null +++ b/core/modules/system/src/RevisionUuidUpdater.php @@ -0,0 +1,82 @@ +state = $state; + $this->entityTypeManager = $entity_type_manager; + $this->database = $database; + } + + /** + * @param int $range + */ + public function update($range = 100) { + $revision_uuid_update = $this->state->get('revision_uuid_update'); + foreach ((array) $revision_uuid_update as $entity_type_id) { + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); + $query = $this->database + ->select($entity_type->getRevisionTable(), 'revision_table') + ->fields('revision_table', [$entity_type->getKey('revision')]) + ->where($entity_type->getKey('revision') . ' = ' . $entity_type->getKey('revision_uuid')) + ->range(0, $range); + $revisions = $query->execute()->fetchCol(); + if ($revisions) { + $this->updateRevisions($entity_type, $revisions); + } + else { + unset($revision_uuid_update[$entity_type->id()]); + $this->state->set('node:revision_uuid_update', $revision_uuid_update); + } + } + } + + /** + * Update all revisions. + */ + public function updateAll() { + $this->update(null); + } + + /** + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * @param array $revisions + */ + protected function updateRevisions(EntityTypeInterface $entity_type, $revisions) { + foreach ($revisions as $revision) { + $this->database + ->update($entity_type->getRevisionTable()) + ->fields([$entity_type->getKey('revision_uuid') => \Drupal::service('uuid')->generate()]) + ->condition($entity_type->getKey('revision'), $revision) + ->execute(); + } + } + +} \ No newline at end of file diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php index 3b1e7bd..8ce42e6 100644 --- a/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php +++ b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php @@ -36,6 +36,7 @@ protected function updateEntityTypeToRevisionable() { $keys = $entity_type->getKeys(); $keys['revision'] = 'revision_id'; + $keys['revision_uuid'] = 'revision_uuid'; $entity_type->set('entity_keys', $keys); $entity_type->set('revision_table', 'entity_test_update_revision'); diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 614ad9e..27e4aaa 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -741,6 +741,15 @@ function system_requirements($phase) { $requirements['entity_update']['value'] = t('Mismatched entity and/or field definitions'); $requirements['entity_update']['description'] = t('The following changes were detected in the entity type and field definitions. @updates', ['@updates' => $entity_update_issues]); } + + $revision_uuid_update = \Drupal::state()->get('revision_uuid_update'); + if (!empty($node_revision_uuid_update) || !empty($block_content_revision_uuid_update)) { + $requirements['entity_field_value_update'] = [ + 'severity' => REQUIREMENT_WARNING, + 'value' => t('Field value updates required'), + 'description' => t('There are fields with the incorrect value which need updating. Update field values.', [':update' => Url::fromRoute('system.field_value_updater')]), + ]; + } } // Verify the update.php access setting diff --git a/core/modules/system/system.module b/core/modules/system/system.module index a1a6b71..c7c003f 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1335,6 +1335,9 @@ function system_cron() { // Clean up PHP storage. PhpStorageFactory::get('container')->garbageCollection(); PhpStorageFactory::get('service_container')->garbageCollection(); + + \Drupal::service('system.revision_uuid_updater')->update(); + } /** diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index ee9afc1..dfc7f13 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -505,3 +505,11 @@ system.csrftoken: _controller: '\Drupal\system\Controller\CsrfTokenController::csrfToken' requirements: _access: 'TRUE' + +system.field_value_updater: + path: '/admin/reports/field_value_updater' + defaults: + _controller: '\Drupal\system\Controller\FieldValueUpdater::update' + _title: 'Field value updater' + requirements: + _permission: 'access site reports' diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml index c70889d..1cc4424 100644 --- a/core/modules/system/system.services.yml +++ b/core/modules/system/system.services.yml @@ -43,3 +43,6 @@ services: arguments: ['@theme_handler', '@cache_tags.invalidator'] tags: - { name: event_subscriber } + system.revision_uuid_updater: + class: Drupal\system\RevisionUuidUpdater + arguments: ['@state', '@entity_type.manager', '@database'] diff --git a/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module index 45892a8..ecd9da6 100644 --- a/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module +++ b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module @@ -20,6 +20,7 @@ function entity_schema_test_entity_type_alter(array &$entity_types) { $entity_type->set('data_table', 'entity_test_field_data'); $keys = $entity_type->getKeys(); $keys['revision'] = 'revision_id'; + $keys['revision_uuid'] = 'revision_uuid'; $entity_type->set('entity_keys', $keys); } } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php index 646ad2e..2f2a052 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php @@ -112,6 +112,7 @@ public function testEntityTypeUpdateWithoutData() { // The revision key is now defined, so the revision field needs to be // created. t('The %field_name field needs to be installed.', ['%field_name' => 'Revision ID']), + t('The %field_name field needs to be installed.', ['%field_name' => $this->entityManager->getFieldStorageDefinitions('entity_test_update')['revision_uuid']->getLabel()]), ], ]; $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.'); diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php index e0bf655..f1b8c41 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php @@ -574,9 +574,8 @@ protected function doTestDataStructureInterfaces($entity_type) { NULL, ]; - if ($entity instanceof RevisionLogInterface) { - // Adding empty string for revision message. - $target_strings[] = ''; + if ($uuid = $entity->getRevisionUuid()) { + $target_strings[] = $uuid; } asort($strings); diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityUUIDTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityUUIDTest.php index a3466a4..91a15ae 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityUUIDTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityUUIDTest.php @@ -97,6 +97,10 @@ protected function assertCRUD($entity_type) { $this->assertNotEqual($entity_duplicate->getRevisionId(), $entity->getRevisionId()); $this->assertNotEqual($entity_duplicate->{$property}->getValue(), $entity->{$property}->getValue()); break; + case 'revision_uuid': + $this->assertNotNull($entity->getRevisionUuid()); + $this->assertNotEqual($entity_duplicate->getRevisionUuid(), $entity->getRevisionUuid()); + break; default: $this->assertEqual($entity_duplicate->{$property}->getValue(), $entity->{$property}->getValue()); } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php index d1045f5..dab0aa3 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php @@ -117,9 +117,9 @@ public function providerTestSet() { */ public function providerTestGetKeys() { return [ - [[], ['revision' => '', 'bundle' => '', 'langcode' => '']], - [['id' => 'id'], ['id' => 'id', 'revision' => '', 'bundle' => '', 'langcode' => '']], - [['bundle' => 'bundle'], ['bundle' => 'bundle', 'revision' => '', 'langcode' => '']], + [[], ['revision' => '', 'revision_uuid' => '', 'bundle' => '', 'langcode' => '']], + [['id' => 'id'], ['id' => 'id', 'revision' => '', 'revision_uuid' => '', 'bundle' => '', 'langcode' => '']], + [['bundle' => 'bundle'], ['bundle' => 'bundle', 'revision' => '', 'revision_uuid' => '', 'langcode' => '']], ]; }