diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index bc56c4271eb70fffba9f7e3235aa97384a3ced76..2eb7c2f6b91d26ac7c1f22c4f6be586c1b6e10d0 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -241,6 +241,8 @@ public function createRevision(RevisionableInterface $entity, $default = TRUE, $ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $new_revision = clone $entity; + $original_keep_untranslatable_fields = $keep_untranslatable_fields; + // For translatable entities, create a merged revision of the active // translation and the other translations in the default revision. This // permits the creation of pending revisions that can always be saved as the @@ -311,6 +313,11 @@ public function createRevision(RevisionableInterface $entity, $default = TRUE, $ // to the correct translation. $new_revision->setRevisionTranslationAffected(TRUE); + // Notify modules about the new revision. + $arguments = [$new_revision, $entity, $original_keep_untranslatable_fields]; + $this->moduleHandler()->invokeAll($this->entityTypeId . '_revision_create', $arguments); + $this->moduleHandler()->invokeAll('entity_revision_create', $arguments); + return $new_revision; } diff --git a/core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php b/core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php index b86317ff010d1f072b3fc92d681e7191e03c6873..b8a55659280efdff4a41adbb342a77047f21417e 100644 --- a/core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php @@ -20,7 +20,9 @@ interface TranslatableRevisionableStorageInterface extends TranslatableStorageIn * to TRUE. * @param bool|null $keep_untranslatable_fields * (optional) Whether untranslatable field values should be kept or copied - * from the default revision when generating a merged revision. + * from the default revision when generating a merged revision. Defaults to + * TRUE if the provided entity is the default translation and untranslatable + * fields should only affect the default translation, FALSE otherwise. * * @return \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface * A new translatable entity revision object. diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php index b4eda00f1a883c3988abc543498be5ab11c4ade9..ac137330f8dc7e1ae1e82663dca095741c7ef76f 100644 --- a/core/lib/Drupal/Core/Entity/entity.api.php +++ b/core/lib/Drupal/Core/Entity/entity.api.php @@ -909,6 +909,54 @@ function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) { \Drupal::logger('example')->info('ENTITY_TYPE created: @label', ['@label' => $entity->label()]); } +/** + * Respond to entity revision creation. + * + * @param \Drupal\Core\Entity\EntityInterface $new_revision + * The new revision that was created. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The original entity that was used to create the revision from. + * @param bool|null $keep_untranslatable_fields + * Whether untranslatable field values were kept (TRUE) or copied from the + * default revision (FALSE) when generating a merged revision. If no value was + * explicitly specified (NULL), a default value of TRUE should be assumed if + * the provided entity is the default translation and untranslatable fields + * should only affect the default translation, FALSE otherwise. + * + * @ingroup entity_crud + * @see \Drupal\Core\Entity\RevisionableStorageInterface::createRevision() + * @see \Drupal\Core\Entity\TranslatableRevisionableStorageInterface::createRevision() + */ +function hook_entity_revision_create(Drupal\Core\Entity\EntityInterface $new_revision, Drupal\Core\Entity\EntityInterface $entity, $keep_untranslatable_fields) { + // Retain the value from an untranslatable field, which are by default + // synchronized from the default revision. + $new_revision->set('untranslatable_field', $entity->get('untranslatable_field')); +} + +/** + * Respond to entity revision creation. + * + * @param \Drupal\Core\Entity\EntityInterface $new_revision + * The new revision that was created. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The original entity that was used to create the revision from. + * @param bool|null $keep_untranslatable_fields + * Whether untranslatable field values were kept (TRUE) or copied from the + * default revision (FALSE) when generating a merged revision. If no value was + * explicitly specified (NULL), a default value of TRUE should be assumed if + * the provided entity is the default translation and untranslatable fields + * should only affect the default translation, FALSE otherwise. + * + * @ingroup entity_crud + * @see \Drupal\Core\Entity\RevisionableStorageInterface::createRevision() + * @see \Drupal\Core\Entity\TranslatableRevisionableStorageInterface::createRevision() + */ +function hook_ENTITY_TYPE_revision_create(Drupal\Core\Entity\EntityInterface $new_revision, Drupal\Core\Entity\EntityInterface $entity, $keep_untranslatable_fields) { + // Retain the value from an untranslatable field, which are by default + // synchronized from the default revision. + $new_revision->set('untranslatable_field', $entity->get('untranslatable_field')); +} + /** * Act on entities when loaded. * diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index f98ae10b0440aeb9277f1711ca7bb3be0bcdf494..66a42e2931c68ca4620d9b7bc61effdfc101046c 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -680,6 +680,23 @@ function entity_test_entity_test_mul_langcode_key_translation_delete(EntityInter _entity_test_record_hooks('entity_test_mul_langcode_key_translation_delete', $translation->language()->getId()); } +/** + * Implements hook_entity_revision_create(). + */ +function entity_test_entity_revision_create(EntityInterface $new_revision, EntityInterface $entity, $keep_untranslatable_fields) { + _entity_test_record_hooks('entity_revision_create', ['new_revision' => $new_revision, 'entity' => $entity, 'keep_untranslatable_fields' => $keep_untranslatable_fields]); +} + +/** + * Implements hook_ENTITY_TYPE_revision_create() for 'entity_test_mulrev'. + */ +function entity_test_entity_test_mulrev_revision_create(EntityInterface $new_revision, EntityInterface $entity, $keep_untranslatable_fields) { + if ($new_revision->get('name')->value == 'revision_create_test_it') { + $new_revision->set('name', 'revision_create_test_it_altered'); + } + _entity_test_record_hooks('entity_test_mulrev_revision_create', ['new_revision' => $new_revision, 'entity' => $entity, 'keep_untranslatable_fields' => $keep_untranslatable_fields]); +} + /** * Field default value callback. * diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php index 616ee1640d7c2ce9f6babb54cf2b8aede81cb450..d4fbcfcb205a9c13e067514ae3cf17081ddfabed 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php @@ -588,4 +588,58 @@ public function testRemovedTranslations() { $this->assertFalse($en_revision->hasTranslation('it')); } + /** + * Checks that the revision create hook works as expected. + * + * @covers ::createRevision + */ + public function testCreateRevisionHook() { + $entity = EntityTestMulRev::create(); + $entity->get('name')->value = 'revision_create_test_en'; + $this->storage->save($entity); + + /** @var \Drupal\Core\Entity\ContentEntityInterface $translation */ + $translation = $entity->addTranslation('it'); + $translation->set('name', 'revision_create_test_it'); + /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */ + $revision = $this->storage->createRevision($translation, FALSE, TRUE); + + // Assert that the alter hook can alter the new revision. + $this->assertEquals('revision_create_test_it_altered', $revision->get('name')->value); + + // Assert the data passed to the hook. + $data = $this->state->get('entity_test.hooks'); + $this->assertEquals('revision_create_test_it', $data['entity_test_mulrev_revision_create']['entity']->get('name')->value); + $this->assertEquals('revision_create_test_it_altered', $data['entity_test_mulrev_revision_create']['new_revision']->get('name')->value); + $this->assertFalse($data['entity_test_mulrev_revision_create']['entity']->isNewRevision()); + $this->assertTrue($data['entity_test_mulrev_revision_create']['new_revision']->isNewRevision()); + $this->assertTrue($data['entity_test_mulrev_revision_create']['entity']->isDefaultRevision()); + $this->assertFalse($data['entity_test_mulrev_revision_create']['new_revision']->isDefaultRevision()); + $this->assertTrue($data['entity_test_mulrev_revision_create']['keep_untranslatable_fields']); + + $this->assertEquals('revision_create_test_it', $data['entity_revision_create']['entity']->get('name')->value); + $this->assertEquals('revision_create_test_it_altered', $data['entity_revision_create']['new_revision']->get('name')->value); + $this->assertFalse($data['entity_revision_create']['entity']->isNewRevision()); + $this->assertTrue($data['entity_revision_create']['new_revision']->isNewRevision()); + $this->assertTrue($data['entity_revision_create']['entity']->isDefaultRevision()); + $this->assertFalse($data['entity_revision_create']['new_revision']->isDefaultRevision()); + $this->assertTrue($data['entity_revision_create']['keep_untranslatable_fields']); + + // Test again with different arguments. + $translation->isDefaultRevision(FALSE); + $this->storage->createRevision($translation); + $data = $this->state->get('entity_test.hooks'); + $this->assertFalse($data['entity_revision_create']['entity']->isNewRevision()); + $this->assertTrue($data['entity_revision_create']['new_revision']->isNewRevision()); + $this->assertFalse($data['entity_revision_create']['entity']->isDefaultRevision()); + $this->assertTrue($data['entity_revision_create']['new_revision']->isDefaultRevision()); + $this->assertNull($data['entity_revision_create']['keep_untranslatable_fields']); + + $this->assertFalse($data['entity_test_mulrev_revision_create']['entity']->isNewRevision()); + $this->assertTrue($data['entity_test_mulrev_revision_create']['new_revision']->isNewRevision()); + $this->assertFalse($data['entity_test_mulrev_revision_create']['entity']->isDefaultRevision()); + $this->assertTrue($data['entity_test_mulrev_revision_create']['new_revision']->isDefaultRevision()); + $this->assertNull($data['entity_test_mulrev_revision_create']['keep_untranslatable_fields']); + } + }