From aae43b1854d0e4124adf20743ab792f1a2d03922 Mon Sep 17 00:00:00 2001 From: catch <6915-catch@users.noreply.drupalcode.org> Date: Tue, 11 Feb 2025 14:39:44 +0000 Subject: [PATCH] Issue #3040556 by sakiland, taran2l, jhuhta, berdir, richgerdes, julien.sibi, ksenzee, aaronmchale, bojanz, nicxvan, hchonov, godotislate, joachim: It is not possible to react to an entity being duplicated --- .../Drupal/Core/Entity/ContentEntityBase.php | 5 ++++ core/lib/Drupal/Core/Entity/EntityBase.php | 6 ++++ core/lib/Drupal/Core/Entity/entity.api.php | 30 +++++++++++++++++++ .../entity_test/src/Hook/EntityTestHooks.php | 21 +++++++++++++ .../Core/Entity/EntityUUIDTest.php | 13 +++++++- .../Entity/ConfigEntityBaseUnitTest.php | 5 ++++ 6 files changed, 79 insertions(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 3b658a7ed35e..039fdce703eb 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -1217,6 +1217,11 @@ public function createDuplicate() { $duplicate->loadedRevisionId = NULL; } + // Modules might need to add or change the data initially held by the new + // entity object, for instance to fill-in default values. + \Drupal::moduleHandler()->invokeAll($this->getEntityTypeId() . '_duplicate', [$duplicate, $this]); + \Drupal::moduleHandler()->invokeAll('entity_duplicate', [$duplicate, $this]); + return $duplicate; } diff --git a/core/lib/Drupal/Core/Entity/EntityBase.php b/core/lib/Drupal/Core/Entity/EntityBase.php index 129d3ddbfc99..ad048198d1fb 100644 --- a/core/lib/Drupal/Core/Entity/EntityBase.php +++ b/core/lib/Drupal/Core/Entity/EntityBase.php @@ -393,6 +393,12 @@ public function createDuplicate() { if ($entity_type->hasKey('uuid')) { $duplicate->{$entity_type->getKey('uuid')} = $this->uuidGenerator()->generate(); } + + // Modules might need to add or change the data initially held by the new + // entity object, for instance to fill-in default values. + \Drupal::moduleHandler()->invokeAll($this->getEntityTypeId() . '_duplicate', [$duplicate, $this]); + \Drupal::moduleHandler()->invokeAll('entity_duplicate', [$duplicate, $this]); + return $duplicate; } diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php index 82fea8aed03d..31d3061975c4 100644 --- a/core/lib/Drupal/Core/Entity/entity.api.php +++ b/core/lib/Drupal/Core/Entity/entity.api.php @@ -977,6 +977,36 @@ function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) { \Drupal::logger('example')->info('ENTITY_TYPE created: @label', ['@label' => $entity->label()]); } +/** + * Acts when duplicating an existing entity. + * + * @param \Drupal\Core\Entity\EntityInterface $duplicate + * The duplicated entity object. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The original entity object. + * + * @ingroup entity_crud + * @see hook_ENTITY_TYPE_duplicate() + */ +function hook_entity_duplicate(\Drupal\Core\Entity\EntityInterface $duplicate, \Drupal\Core\Entity\EntityInterface $entity): void { + \Drupal::logger('example')->info('Entity duplicated: @label', ['@label' => $entity->label()]); +} + +/** + * Acts when duplicating an existing entity of a specific type. + * + * @param \Drupal\Core\Entity\EntityInterface $duplicate + * The duplicated entity object. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The original entity object. + * + * @ingroup entity_crud + * @see hook_entity_duplicate() + */ +function hook_ENTITY_TYPE_duplicate(\Drupal\Core\Entity\EntityInterface $duplicate, \Drupal\Core\Entity\EntityInterface $entity): void { + \Drupal::logger('example')->info('ENTITY_TYPE duplicated: @label', ['@label' => $entity->label()]); +} + /** * Respond to entity revision creation. * diff --git a/core/modules/system/tests/modules/entity_test/src/Hook/EntityTestHooks.php b/core/modules/system/tests/modules/entity_test/src/Hook/EntityTestHooks.php index 268a12435ac3..d665a70186cd 100644 --- a/core/modules/system/tests/modules/entity_test/src/Hook/EntityTestHooks.php +++ b/core/modules/system/tests/modules/entity_test/src/Hook/EntityTestHooks.php @@ -22,6 +22,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Hook\Attribute\Hook; use Drupal\entity_test\EntityTestHelper; +use Drupal\entity_test\Entity\EntityTest; /** * Hook implementations for entity_test. @@ -705,4 +706,24 @@ public function entityTestFormModeAlter(&$form_mode, EntityInterface $entity) : } } + /** + * Implements hook_entity_duplicate(). + */ + #[Hook('entity_duplicate')] + public function entityDuplicateAlter(EntityInterface $duplicate, EntityInterface $entity) : void { + if ($duplicate instanceof ContentEntityInterface && str_contains($duplicate->label(), 'UUID CRUD test entity') && $duplicate->hasField('name')) { + $duplicate->set('name', $duplicate->label() . ' duplicate'); + } + } + + /** + * Implements hook_ENTITY_TYPE_duplicate(). + */ + #[Hook('entity_test_duplicate')] + public function entityTestDuplicate(EntityTest $duplicate, EntityTest $entity) : void { + if (str_contains($duplicate->label(), 'UUID CRUD test entity') && $duplicate->hasField('name')) { + $duplicate->set('name', 'prefix ' . $duplicate->label()); + } + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityUUIDTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityUUIDTest.php index dee6c23dff8e..2713761dd2f4 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityUUIDTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityUUIDTest.php @@ -62,7 +62,7 @@ protected function assertCRUD(string $entity_type): void { // Verify that a new UUID is generated upon creating an entity. $entity = $this->container->get('entity_type.manager') ->getStorage($entity_type) - ->create(['name' => $this->randomMachineName()]); + ->create(['name' => 'UUID CRUD test entity']); $uuid = $entity->uuid(); $this->assertNotEmpty($uuid); @@ -109,6 +109,17 @@ protected function assertCRUD(string $entity_type): void { $this->assertNotEquals($entity->{$property}->getValue(), $entity_duplicate->{$property}->getValue()); break; + case 'name': + // Assert alter hooks in \Drupal\entity_test\Hook\EntityTestHooks. + if ($entity_type === 'entity_test') { + $this->assertEquals('prefix UUID CRUD test entity duplicate', $entity_duplicate->label()); + } + else { + $this->assertEquals('UUID CRUD test entity duplicate', $entity_duplicate->label()); + } + $this->assertEquals('UUID CRUD test entity', $entity->label()); + break; + default: $this->assertEquals($entity->{$property}->getValue(), $entity_duplicate->{$property}->getValue()); } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php index ccb5f6138dd8..f876e75ad33f 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php @@ -507,6 +507,11 @@ public function testCreateDuplicate(): void { $this->assertNull($duplicate->getOriginalId()); $this->assertNotEquals($this->entity->uuid(), $duplicate->uuid()); $this->assertSame($new_uuid, $duplicate->uuid()); + + $this->moduleHandler->invokeAll($this->entityTypeId . '_duplicate', [$duplicate, $this->entity]) + ->shouldHaveBeenCalled(); + $this->moduleHandler->invokeAll('entity_duplicate', [$duplicate, $this->entity]) + ->shouldHaveBeenCalled(); } /** -- GitLab