diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 3b658a7ed35e005d6077cefddb61050aa75fcbae..039fdce703eb547565fa37f0018564a32f9de12b 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 129d3ddbfc9913456dd7d697ad9a3a2d936e028e..ad048198d1fb96425ce18c8938a120867432dd10 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 82fea8aed03ddf590c1c6af0d38b08e406997ce6..31d3061975c447ceb36a15ea0dd40dd72da63534 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 268a12435ac33740d94b80b94c2a111d9b30d27d..d665a70186cdbf8697df51e3828e6798f8a3e703 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 dee6c23dff8e723c9481c4a5ac35e9ec1f582740..2713761dd2f4fc6254e5bd0cf769b417479bb6d1 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 ccb5f6138dd8d26fbb9e655701edb40ee8a4bd84..f876e75ad33f335533b86c48386c70674c7ab3c9 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(); } /**