diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index afddb22..c5421ed 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -322,7 +322,7 @@ public function isDefaultRevision($new_value = NULL) { * {@inheritdoc} */ public function isRevisionTranslationAffected() { - $field_name = 'revision_translation_affected'; + $field_name = $this->getEntityType()->getKey('revision_translation_affected'); return $this->hasField($field_name) ? $this->get($field_name)->value : TRUE; } @@ -330,7 +330,7 @@ public function isRevisionTranslationAffected() { * {@inheritdoc} */ public function setRevisionTranslationAffected($affected) { - $field_name = 'revision_translation_affected'; + $field_name = $this->getEntityType()->getKey('revision_translation_affected'); if ($this->hasField($field_name)) { $this->set($field_name, $affected); } diff --git a/core/lib/Drupal/Core/Entity/EditorialContentEntityBase.php b/core/lib/Drupal/Core/Entity/EditorialContentEntityBase.php index ff6abcd..85e2e6b 100644 --- a/core/lib/Drupal/Core/Entity/EditorialContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/EditorialContentEntityBase.php @@ -2,6 +2,8 @@ namespace Drupal\Core\Entity; +use Drupal\Core\Field\BaseFieldDefinition; + /** * Provides a base entity class with extended revision and publishing support. * diff --git a/core/lib/Drupal/Core/Entity/EntityFieldManager.php b/core/lib/Drupal/Core/Entity/EntityFieldManager.php index 378a95c..a0fff7e 100644 --- a/core/lib/Drupal/Core/Entity/EntityFieldManager.php +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php @@ -220,6 +220,20 @@ protected function buildBaseFieldDefinitions($entity_type_id) { } } + // Make sure that revisionable entity types are correctly defined. + if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) { + // The 'revision_translation_affected' field should always be defined. + // This field has been added unconditionally in Drupal 8.4.0 and it is + // overriding any pre-existing definition on purpose so that any + // differences are immediately available in the status report. + $base_field_definitions[$keys['revision_translation_affected']] = BaseFieldDefinition::create('boolean') + ->setLabel($this->t('Revision translation affected')) + ->setDescription($this->t('Indicates if the last edit of a translation belongs to current revision.')) + ->setReadOnly(TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(TRUE); + } + // Assign base field definitions the entity type provider. $provider = $entity_type->getProvider(); foreach ($base_field_definitions as $definition) { diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php index b39fb3f..5f6589a 100644 --- a/core/lib/Drupal/Core/Entity/EntityType.php +++ b/core/lib/Drupal/Core/Entity/EntityType.php @@ -302,6 +302,7 @@ public function __construct($definition) { 'bundle' => '', 'langcode' => '', 'default_langcode' => 'default_langcode', + 'revision_translation_affected' => 'revision_translation_affected', ]; $this->handlers += [ 'access' => 'Drupal\Core\Entity\EntityAccessControlHandler', diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php index ad28f76..915af91 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php @@ -1689,8 +1689,9 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st // few "entity keys", in order to keep their indexes optimized. // @todo Fix this in https://www.drupal.org/node/2841291. $not_null_keys = $this->entityType->getKeys(); - // Label fields are not necessarily required. - unset($not_null_keys['label']); + // Label and the 'revision_translation_affected' fields are not necessarily + // required. + unset($not_null_keys['label'], $not_null_keys['revision_translation_affected']); // Because entity ID and revision ID are both serial fields in the base and // revision table respectively, the revision ID is not known yet, when // inserting data into the base table. Instead the revision ID in the base diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchemaConverter.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchemaConverter.php index 30befd2..a589f56 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchemaConverter.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchemaConverter.php @@ -245,6 +245,7 @@ protected function copyData(array &$sandbox) { $original_base_table = $original_entity_type->getBaseTable(); $revision_id_key = $temporary_entity_type->getKey('revision'); + $revision_translation_affected_key = $temporary_entity_type->getKey('revision_translation_affected'); // If 'progress' is not set, then this will be the first run of the batch. if (!isset($sandbox['progress'])) { @@ -287,6 +288,11 @@ protected function copyData(array &$sandbox) { // Set the revision ID to be same as the entity ID. $entity->set($revision_id_key, $entity_id); + // Set the 'revision_translation_affected' flag to TRUE to match the + // previous API return value: if the field was not defined the value + // returned was always TRUE. + $entity->set($revision_translation_affected_key, TRUE); + // Treat the entity as new in order to make the storage do an INSERT // rather than an UPDATE. $entity->enforceIsNew(TRUE); @@ -370,9 +376,26 @@ protected function updateFieldStorageDefinitionsToRevisionable(ContentEntityType if ($update_cached_definitions) { $this->entityDefinitionUpdateManager->installFieldStorageDefinition($revision_field->getName(), $entity_type->id(), $entity_type->getProvider(), $revision_field); } - $updated_storage_definitions[$entity_type->getKey('revision')] = $revision_field; + // Add the 'revision_translation_affected' field if needed. + if ($entity_type->isTranslatable()) { + $revision_translation_affected_field = BaseFieldDefinition::create('boolean') + ->setName($entity_type->getKey('revision_translation_affected')) + ->setTargetEntityTypeId($entity_type->id()) + ->setTargetBundle(NULL) + ->setLabel(new TranslatableMarkup('Revision translation affected')) + ->setDescription(new TranslatableMarkup('Indicates if the last edit of a translation belongs to current revision.')) + ->setReadOnly(TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(TRUE); + + if ($update_cached_definitions) { + $this->entityDefinitionUpdateManager->installFieldStorageDefinition($revision_translation_affected_field->getName(), $entity_type->id(), $entity_type->getProvider(), $revision_translation_affected_field); + } + $updated_storage_definitions[$entity_type->getKey('revision_translation_affected')] = $revision_translation_affected_field; + } + return $updated_storage_definitions; } diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index 76071df..66a7a8f 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -216,13 +216,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setSetting('target_type', 'user') ->setRevisionable(TRUE); - $fields['revision_translation_affected'] = BaseFieldDefinition::create('boolean') - ->setLabel(t('Revision translation affected')) - ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.')) - ->setReadOnly(TRUE) - ->setRevisionable(TRUE) - ->setTranslatable(TRUE); - return $fields; } diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index e5544a2..368ce1b 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -400,13 +400,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ]) ->setDisplayConfigurable('form', TRUE); - $fields['revision_translation_affected'] = BaseFieldDefinition::create('boolean') - ->setLabel(t('Revision translation affected')) - ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.')) - ->setReadOnly(TRUE) - ->setRevisionable(TRUE) - ->setTranslatable(TRUE); - return $fields; } diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php index ebd4e03..5b71b93 100644 --- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php +++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php @@ -126,6 +126,9 @@ public function testNormalize() { 'default_langcode' => [ ['value' => TRUE], ], + 'revision_translation_affected' => [ + ['value' => TRUE], + ], 'non_rev_field' => [], 'field_test_text' => [ [ @@ -197,6 +200,7 @@ public function testSerialize() { 'user_id' => '' . $this->user->id() . '' . $this->user->getEntityTypeId() . '' . $this->user->uuid() . '' . $this->user->url() . '', 'revision_id' => '' . $this->entity->getRevisionId() . '', 'default_langcode' => '1', + 'revision_translation_affected' => '1', 'non_rev_field' => '', 'field_test_text' => '' . $this->values['field_test_text']['value'] . '' . $this->values['field_test_text']['format'] . '', ]; diff --git a/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php b/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php index 3a0aca2..b6a8695 100644 --- a/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php +++ b/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageSchemaConverterTest.php @@ -92,6 +92,9 @@ public function testMakeRevisionable() { $entity_test_update = $this->lastInstalledSchemaRepository->getLastInstalledDefinition('entity_test_update'); $this->assertTrue($entity_test_update->isRevisionable()); + $field_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update'); + $this->assertTrue(isset($field_storage_definitions['revision_translation_affected'])); + /** @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage */ $storage = \Drupal::entityTypeManager()->getStorage('entity_test_update'); $this->assertEqual(count($storage->loadMultiple()), 102, 'All test entities were found.'); @@ -104,6 +107,10 @@ public function testMakeRevisionable() { $this->assertEqual($i, $revision->id()); $this->assertEqual($i, $revision->getRevisionId()); + // Check that the correct initial value was provided for the + // 'revision_translation_affected' field. + $this->assertTrue($revision->revision_translation_affected->value); + $this->assertEqual($i . ' - test single property', $revision->test_single_property->value); $this->assertEqual($i . ' - test multiple properties - value1', $revision->test_multiple_properties->value1); diff --git a/core/modules/system/src/Tests/Update/EntityUpdateAddRevisionTranslationAffected.php b/core/modules/system/src/Tests/Update/EntityUpdateAddRevisionTranslationAffected.php new file mode 100644 index 0000000..ad405de --- /dev/null +++ b/core/modules/system/src/Tests/Update/EntityUpdateAddRevisionTranslationAffected.php @@ -0,0 +1,88 @@ +entityManager = \Drupal::entityManager(); + $this->lastInstalledSchemaRepository = \Drupal::service('entity.last_installed_schema.repository'); + $this->state = \Drupal::state(); + } + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.0.0-rc1-filled.standard.entity_test_update_mul_rev.php.gz', + ]; + } + + /** + * Tests the addition of the 'revision_translation_affected' base field. + * + * @covers system_update_8402 + */ + public function testAddingTheRevisionTranslationAffectedField() { + // Make the entity type revisionable and translatable prior to running the + // updates. + $this->updateEntityTypeToRevisionableAndTranslatable(); + + // Check that the test entity type does not have the + // 'revision_translation_affected' field before running the updates. + $field_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update'); + $this->assertFalse(isset($field_storage_definitions['revision_translation_affected'])); + + $this->runUpdates(); + + // Check that the 'revision_translation_affected' field has been added by + // system_update_8402(). + $field_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update'); + $this->assertTrue(isset($field_storage_definitions['revision_translation_affected'])); + + // Check that the correct initial value was set when the field was + // installed. + $entity = \Drupal::entityTypeManager()->getStorage('entity_test_update')->load(1); + $this->assertTrue($entity->revision_translation_affected->value); + } + +} diff --git a/core/modules/system/src/Tests/Update/EntityUpdateToRevisionableAndPublishableTest.php b/core/modules/system/src/Tests/Update/EntityUpdateToRevisionableAndPublishableTest.php index 167bf12..3edf6f7 100644 --- a/core/modules/system/src/Tests/Update/EntityUpdateToRevisionableAndPublishableTest.php +++ b/core/modules/system/src/Tests/Update/EntityUpdateToRevisionableAndPublishableTest.php @@ -77,6 +77,8 @@ protected function setDatabaseDumpFiles() { /** * Tests the conversion of an entity type to revisionable and publishable. + * + * @covers entity_test_update_update_8400 */ public function testConvertToRevisionableAndPublishable() { // Check that entity type is not revisionable nor publishable prior to diff --git a/core/modules/system/system.install b/core/modules/system/system.install index a4ffffa..3c8332e 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -17,6 +17,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\DrupalKernel; +use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Site\Settings; use Drupal\Core\StreamWrapper\PrivateStream; use Drupal\Core\StreamWrapper\PublicStream; @@ -1984,3 +1985,43 @@ function system_update_8401() { ->clear('response') ->save(); } + +/** + * Add the 'revision_translation_affected' field to all entity types. + */ +function system_update_8402() { + $definition_update_manager = \Drupal::entityDefinitionUpdateManager(); + + // Clear the cached entity type definitions so we get the new + // 'revision_translation_affected' entity key. + \Drupal::entityTypeManager()->clearCachedDefinitions(); + + // Get a list of revisionable and translatable entity types. + /** @var \Drupal\Core\Entity\ContentEntityTypeInterface[] $definitions */ + $definitions = array_filter(\Drupal::entityTypeManager()->getDefinitions(), function (EntityTypeInterface $entity_type) use ($definition_update_manager) { + if ($entity_type = $definition_update_manager->getEntityType($entity_type->id())) { + return $entity_type->isRevisionable() && $entity_type->isTranslatable(); + } + return FALSE; + }); + + foreach ($definitions as $entity_type_id => $entity_type) { + $field_name = $entity_type->getKey('revision_translation_affected'); + // Install the 'revision_translation_affected' field if needed. + if (!$definition_update_manager->getFieldStorageDefinition($field_name, $entity_type_id)) { + $storage_definition = BaseFieldDefinition::create('boolean') + ->setLabel(t('Revision translation affected')) + ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.')) + ->setReadOnly(TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(TRUE) + // Mark all pre-existing revisions as affected in order to be consistent + // with the previous API return value: if the field was not defined the + // value returned was always TRUE. + ->setInitialValue(TRUE); + + $definition_update_manager + ->installFieldStorageDefinition($field_name, $entity_type_id, $entity_type_id, $storage_definition); + } + } +} diff --git a/core/modules/system/tests/modules/entity_test_update/entity_test_update.module b/core/modules/system/tests/modules/entity_test_update/entity_test_update.module index 5b32eb8..225c549 100644 --- a/core/modules/system/tests/modules/entity_test_update/entity_test_update.module +++ b/core/modules/system/tests/modules/entity_test_update/entity_test_update.module @@ -79,6 +79,11 @@ function entity_test_update_entity_presave(EntityInterface $entity) { * (e.g. content_translation_status); * - 52 more test entities (with the IDs 51 - 102) crated, translated and saved. * + * The 'drupal-8.0.0-rc1-filled.standard.entity_test_update_mul_rev.php.gz' db + * dump was created like the multilingual one described above, with one change: + * The annotation of the entity_test_update entity type was also manually edited + * in order to make it revisionable. + * * @param int $start * (optional) The entity ID to start from. Defaults to 1. * @param int $end diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php index 4985470..d525514 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php @@ -542,6 +542,7 @@ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $f $this->entityType->getKeys()->willReturn($entity_keys + ['default_langcode' => 'default_langcode']); $this->entityType->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE); $this->entityType->isTranslatable()->willReturn(FALSE); + $this->entityType->isRevisionable()->willReturn(FALSE); $this->entityType->getProvider()->willReturn('the_provider'); $this->entityType->id()->willReturn('the_entity_id'); @@ -651,6 +652,7 @@ public function testGetFieldMap() { $entity_type->getKeys()->willReturn(['default_langcode' => 'default_langcode']); $entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE); $entity_type->isTranslatable()->shouldBeCalled(); + $entity_type->isRevisionable()->shouldBeCalled(); $entity_type->getProvider()->shouldBeCalled(); $non_content_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE); @@ -752,6 +754,7 @@ public function testGetFieldMapByFieldType() { $entity_type->getKeys()->willReturn(['default_langcode' => 'default_langcode'])->shouldBeCalled(); $entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE)->shouldBeCalled(); $entity_type->isTranslatable()->shouldBeCalled(); + $entity_type->isRevisionable()->shouldBeCalled(); $entity_type->getProvider()->shouldBeCalled(); $override_entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE)->shouldBeCalled(); diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php index d1045f5..431831c 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php @@ -58,7 +58,11 @@ public function testSet($key, $value) { */ public function testGetKeys($entity_keys, $expected) { $entity_type = $this->setUpEntityType(['entity_keys' => $entity_keys]); - $this->assertSame($expected + ['default_langcode' => 'default_langcode'], $entity_type->getKeys()); + $expected += [ + 'default_langcode' => 'default_langcode', + 'revision_translation_affected' => 'revision_translation_affected', + ]; + $this->assertSame($expected, $entity_type->getKeys()); } /**