diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 9f21344..47da72c 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -308,6 +308,13 @@ public function getLatestRevisionId() { /** * {@inheritdoc} */ + public function getLatestTranslationAffectedRevisionId() { + return $this->getEntityKey('latest_revision_translation_affected'); + } + + /** + * {@inheritdoc} + */ public function isNewRevision() { return $this->newRevision || ($this->getEntityType()->hasKey('revision') && !$this->getRevisionId()); } @@ -1242,6 +1249,13 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setReadOnly(TRUE) ->setSetting('unsigned', TRUE); } + if ($entity_type->hasKey('langcode') && $entity_type->hasKey('revision')) { + $fields[$entity_type->getKey('latest_revision_translation_affected')] = BaseFieldDefinition::create('integer') + ->setLabel(new TranslatableMarkup('Latest Revision Translation Affected')) + ->setReadOnly(TRUE) + ->setTranslatable(TRUE) + ->setSetting('unsigned', TRUE); + } if ($entity_type->hasKey('langcode')) { $fields[$entity_type->getKey('langcode')] = BaseFieldDefinition::create('language') ->setLabel(new TranslatableMarkup('Language')) @@ -1299,6 +1313,7 @@ protected function getFieldsToSkipFromTranslationChangesCheck() { $fields = [ $entity_type->getKey('revision'), $entity_type->getKey('latest_revision'), + $entity_type->getKey('latest_revision_translation_affected'), 'revision_translation_affected', ]; $fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys())); diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php index e25b24c..f146c8e 100644 --- a/core/lib/Drupal/Core/Entity/EntityType.php +++ b/core/lib/Drupal/Core/Entity/EntityType.php @@ -303,6 +303,7 @@ public function __construct($definition) { 'langcode' => '', 'default_langcode' => 'default_langcode', 'latest_revision' => 'latest_revision', + 'latest_revision_translation_affected' => 'latest_revision_translation_affected', ]; $this->handlers += [ 'access' => 'Drupal\Core\Entity\EntityAccessControlHandler', diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index 04f6e94..566126a 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -62,6 +62,13 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt protected $latestRevisionKey; /** + * Name of the entity's latest translation affected revision key if supported. + * + * @var string + */ + protected $latestRevisionTranslationAffectedKey; + + /** * The entity langcode key. * * @var string|bool @@ -178,6 +185,7 @@ protected function initTableLayout() { $this->tableMapping = NULL; $this->revisionKey = NULL; $this->latestRevisionKey = NULL; + $this->latestRevisionTranslationAffectedKey = NULL; $this->revisionTable = NULL; $this->dataTable = NULL; $this->revisionDataTable = NULL; @@ -199,6 +207,7 @@ protected function initTableLayout() { } if ($revisionable && $translatable) { $this->revisionDataTable = $this->entityType->getRevisionDataTable() ?: $this->entityTypeId . '_field_revision'; + $this->latestRevisionTranslationAffectedKey = $this->entityType->getKey('latest_revision_translation_affected'); } } @@ -834,7 +843,7 @@ protected function doSaveFieldItems(ContentEntityInterface $entity, array $names if ($default_revision && $this->dataTable) { $this->saveToSharedTables($entity); } - if ($this->revisionTable && $this->latestRevisionKey && $entity->isNewRevision()) { + if ($this->latestRevisionKey && $entity->isNewRevision()) { $this->database ->update($this->dataTable ? $this->dataTable : $this->baseTable) ->fields([$this->latestRevisionKey => $entity->{$this->revisionKey}->value]) @@ -842,6 +851,15 @@ protected function doSaveFieldItems(ContentEntityInterface $entity, array $names ->execute(); $entity->{$this->latestRevisionKey} = $entity->{$this->revisionKey}->value; } + if ($this->latestRevisionTranslationAffectedKey && $entity->isNewRevision() && $entity->isRevisionTranslationAffected()) { + $this->database + ->update($this->dataTable ? $this->dataTable : $this->baseTable) + ->fields([$this->latestRevisionTranslationAffectedKey => $entity->{$this->revisionKey}->value]) + ->condition($this->idKey, $record->{$this->idKey}) + ->condition($this->langcodeKey, $entity->language()->getId()) + ->execute(); + $entity->{$this->latestRevisionTranslationAffectedKey} = $entity->{$this->revisionKey}->value; + } if ($this->revisionDataTable) { $new_revision = $full_save && $entity->isNewRevision(); $this->saveToSharedTables($entity, $this->revisionDataTable, $new_revision); @@ -862,6 +880,7 @@ protected function doSaveFieldItems(ContentEntityInterface $entity, array $names if ($this->revisionTable) { $record->{$this->revisionKey} = $this->saveRevision($entity); $entity->{$this->latestRevisionKey} = $record->{$this->revisionKey}; + $entity->{$this->latestRevisionTranslationAffectedKey} = $record->{$this->revisionKey}; } if ($this->dataTable) { $this->saveToSharedTables($entity); diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php index 78e1678..cc915c4 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php @@ -1641,6 +1641,7 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st // The latest revision key is stored in the data table and is also updated // after a revision has been created. unset($not_null_keys['latest_revision']); + unset($not_null_keys['latest_revision_translation_affected']); foreach ($column_mapping as $field_column_name => $schema_field_name) { $column_schema = $field_schema['columns'][$field_column_name]; diff --git a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php index 5b57a87..a9ff612 100644 --- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php +++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php @@ -113,4 +113,13 @@ public function removeTranslation($langcode); */ public function isTranslatable(); + /** + * Get the latest translation affected revision for the loaded entity. + * + * @return int|null + * The latest revision that has been affected by translation or null if none + * exists. + */ + public function getLatestTranslationAffectedRevisionId(); + } 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 55fe89d..d329b32 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php @@ -100,6 +100,9 @@ protected function getExpectedNormalizedEntity() { 'latest_revision' => [ ['value' => 1], ], + 'latest_revision_translation_affected' => [ + ['value' => 1], + ], 'langcode' => [ [ 'value' => 'en', diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php index e1070b0..89cfcc9 100644 --- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php +++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php @@ -123,6 +123,12 @@ public function testNormalize() { 'latest_revision' => [ ['value' => 1], ], + 'latest_revision_translation_affected' => [ + ['value' => 1], + ], + 'revision_translation_affected' => [ + ['value' => TRUE], + ], 'default_langcode' => [ ['value' => TRUE], ], @@ -195,6 +201,8 @@ public function testSerialize() { 'user_id' => '' . $this->user->id() . '' . $this->user->getEntityTypeId() . '' . $this->user->uuid() . '' . $this->user->url() . '', 'revision_id' => '' . $this->entity->getRevisionId() . '', 'latest_revision' => '' . $this->entity->getLatestRevisionId() . '', + 'revision_translation_affected' => '' . $this->entity->isRevisionTranslationAffected() . '', + 'latest_revision_translation_affected' => '' . $this->entity->getLatestTranslationAffectedRevisionId() . '', 'default_langcode' => '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/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php index f01675f..a16a3f2 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php @@ -1,6 +1,8 @@ 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/tests/Drupal/KernelTests/Core/Entity/EntityLatestRevisionTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityLatestRevisionTest.php index c213911..49b6ab6 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityLatestRevisionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityLatestRevisionTest.php @@ -87,7 +87,7 @@ public function latestRevisionTestCases() { /** * Test the latest revision tracking with translations. */ - public function testWithTranslations() { + public function testLatestRevisionTranslations() { $entity = EntityTestMulRev::create([]); $entity->save(); $this->assertEquals(1, $entity->getLatestRevisionId()); @@ -110,4 +110,49 @@ public function testWithTranslations() { $this->assertEquals(2, EntityTestMulRev::load(1)->getLatestRevisionId()); } + /** + * Test the latest_revision_translation_affected column. + */ + public function testLatestRevisionTranslationAffected() { + $storage = $this->entityTypeManager->getStorage('entity_test_mulrev'); + + // Creating a new entity is translation affected. + $entity = EntityTestMulRev::create([ + 'name' => 'Original Entity', + ]); + $entity->save(); + $this->assertEquals(1, $entity->getLatestTranslationAffectedRevisionId()); + + // The first revision of a new entity is also translation affected. + $translated_entity = $entity->addTranslation('de', ['name' => 'Added Translation']); + $translated_entity->save(); + $this->assertEquals(1, $entity->getLatestTranslationAffectedRevisionId()); + + // Create a forward revision of the translated entity. + $translated_entity->setNewRevision(TRUE); + $translated_entity->isDefaultRevision(FALSE); + $translated_entity->name = 'Translated Forward Revision'; + $translated_entity->save(); + + // The latest translation affected entity will update for the translated + // version, but not the original. + $this->assertEquals(2, $translated_entity->getLatestTranslationAffectedRevisionId()); + $reloaded_entity = $storage->load($entity->id()); + $this->assertEquals(1, $reloaded_entity->getLatestTranslationAffectedRevisionId()); + $this->assertEquals(2, $reloaded_entity->getLatestRevisionId()); + + // After reloading create a forward revision of the original entity. + $reloaded_entity->setNewRevision(TRUE); + $reloaded_entity->isDefaultRevision(FALSE); + $reloaded_entity->name = 'Updated Original Entity'; + $reloaded_entity->save(); + + // Creating a forward revision of the original entity should not alter + // the latest translation affected revision of the translated entity. + $reloaded_translated_entity = $reloaded_entity->getTranslation('de'); + $this->assertEquals(2, $reloaded_translated_entity->getLatestTranslationAffectedRevisionId()); + $this->assertEquals(3, $reloaded_translated_entity->getLatestRevisionId()); + $this->assertEquals(3, $reloaded_entity->getLatestRevisionId()); + } + } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php index 0e17181..6cc609f 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php @@ -58,7 +58,7 @@ 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', 'latest_revision' => 'latest_revision'], $entity_type->getKeys()); + $this->assertSame($expected + ['default_langcode' => 'default_langcode', 'latest_revision' => 'latest_revision', 'latest_revision_translation_affected' => 'latest_revision_translation_affected'], $entity_type->getKeys()); } /**