From 1c8c846ac101e77a200043f700fa40f3c8a11e0a Mon Sep 17 00:00:00 2001 From: Xaq Rothman <xaq@radcampaign.com> Date: Mon, 22 Jan 2024 10:43:32 -0500 Subject: [PATCH 01/20] Apply patch from issue #3100117 --- quick_node_clone.services.yml | 2 +- .../QuickNodeCloneEntityFormBuilder.php | 148 ++++++++++++++---- src/Form/QuickNodeCloneNodeForm.php | 38 +++++ 3 files changed, 154 insertions(+), 34 deletions(-) diff --git a/quick_node_clone.services.yml b/quick_node_clone.services.yml index 3f7322c..ac46814 100644 --- a/quick_node_clone.services.yml +++ b/quick_node_clone.services.yml @@ -1,7 +1,7 @@ services: quick_node_clone.entity.form_builder: class: Drupal\quick_node_clone\Entity\QuickNodeCloneEntityFormBuilder - arguments: ['@form_builder', '@entity_type.bundle.info', '@config.factory', '@module_handler', '@entity_type.manager', '@current_user', '@tempstore.private', '@string_translation'] + arguments: ['@form_builder', '@entity_type.bundle.info', '@config.factory', '@module_handler', '@entity_type.manager', '@current_user', '@tempstore.private', '@string_translation', '@uuid'] quick_node_clone.address_event_subscriber: class: Drupal\quick_node_clone\EventSubscriber\AddressEventSubscriber arguments: ['@tempstore.private', '@quick_node_clone.node_finder'] diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 361dbdf..37197d2 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -2,11 +2,13 @@ namespace Drupal\quick_node_clone\Entity; +use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityFormBuilder; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormState; @@ -15,7 +17,9 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; use Drupal\group\Entity\GroupContent; -use Drupal\node\Entity\Node; +use Drupal\layout_builder\Plugin\Block\InlineBlock; +use Drupal\layout_builder\SectionComponent; +use Drupal\node\NodeInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -74,17 +78,27 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { */ protected $stringTranslation; + /** + * The UUID service. + * + * @var \Drupal\Component\Uuid\UuidInterface + */ + protected $uuid; + /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( + $container->get('form_builder'), $container->get('entity_type.bundle.info'), $container->get('config.factory'), $container->get('module_handler'), $container->get('entity_type.manager'), $container->get('current_user'), - $container->get('tempstore.private') + $container->get('tempstore.private'), + $container->get('string_translation'), + $container->get('uuid') ); } @@ -107,8 +121,10 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * Private temp store factory. * @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation * The string translation service. + * @param \Drupal\Component\Uuid\UuidInterface $uuid + * The UUID service. */ - public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation) { + public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, UuidInterface $uuid) { $this->formBuilder = $formBuilder; $this->entityTypeBundleInfo = $entityTypeBundleInfo; $this->configFactory = $configFactory; @@ -117,6 +133,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { $this->currentUser = $currentUser; $this->privateTempStoreFactory = $privateTempStoreFactory; $this->stringTranslation = $stringTranslation; + $this->uuid = $uuid; } /** @@ -150,7 +167,8 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { foreach ($new_node->getTranslationLanguages() as $langcode => $language) { /** @var \Drupal\node\Entity\Node $translated_node */ $translated_node = $new_node->getTranslation($langcode); - $translated_node = $this->cloneParagraphs($translated_node); + $this->cloneParagraphs($translated_node); + $this->cloneInlineBlocks($translated_node); $this->moduleHandler->alter('cloned_node', $translated_node, $original_entity); // Unset excluded fields. @@ -209,41 +227,105 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * If we do not clone the paragraphs attached to the node, the linked * paragraphs would be linked to two nodes which is not ideal. * - * @param \Drupal\node\Entity\Node $node - * The node to clone. - * - * @return \Drupal\node\Entity\Node - * The node with cloned paragraph fields. + * @param \Drupal\node\Entity\FieldableEntityInterface $entity + * The entity being cloned. */ - public function cloneParagraphs(Node $node) { - foreach ($node->getFieldDefinitions() as $field_definition) { - $field_storage_definition = $field_definition->getFieldStorageDefinition(); - $field_settings = $field_storage_definition->getSettings(); - $field_name = $field_storage_definition->getName(); - if (isset($field_settings['target_type']) && $field_settings['target_type'] == "paragraph") { - if (!$node->get($field_name)->isEmpty()) { - foreach ($node->get($field_name) as $value) { - if ($value->entity) { - $value->entity = $value->entity->createDuplicate(); - foreach ($value->entity->getFieldDefinitions() as $field_definition) { - $field_storage_definition = $field_definition->getFieldStorageDefinition(); - $pfield_settings = $field_storage_definition->getSettings(); - $pfield_name = $field_storage_definition->getName(); - - // Check whether this field is excluded and if so unset. - if ($this->excludeParagraphField($pfield_name, $value->entity->bundle())) { - unset($value->entity->{$pfield_name}); - } - - $this->moduleHandler->alter('cloned_node_paragraph_field', $value->entity, $pfield_name, $pfield_settings); - } - } + public function cloneParagraphs(FieldableEntityInterface $entity) { + foreach ($entity->getFieldDefinitions() as $field_name => $field_definition) { + $field_settings = $field_definition->getFieldStorageDefinition() + ->getSettings(); + if (($field_settings['target_type'] ?? NULL) != 'paragraph') { + continue; + } + foreach ($entity->$field_name as $paragraph_item) { + $paragraph = $paragraph_item->entity; + if (!$paragraph) { + continue; + } + $cloned_paragraph = $paragraph->createDuplicate(); + $paragraph_item->entity = $cloned_paragraph; + foreach ($cloned_paragraph->getFieldDefinitions() as $paragraph_field_name => $paragraph_field_definition) { + if ($this->excludeParagraphField($paragraph_field_name, $cloned_paragraph->bundle())) { + unset($cloned_paragraph->$paragraph_field_name); } + $paragraph_field_storage_definition = $paragraph_field_definition + ->getFieldStorageDefinition(); + $paragraph_field_settings = $paragraph_field_storage_definition + ->getSettings(); + $this->moduleHandler->alter('cloned_node_paragraph_field', $cloned_paragraph, $paragraph_field_name, $paragraph_field_settings); } } } + } + + /** + * Clone the inline blocks of a node's layout. + * + * For nodes that have layout builder enabled, the inline blocks needs + * be to cloned as well. + * + * @param \Drupal\Core\Entity\FieldableEntityInterface $entity + * The entity being cloned. + */ + public function cloneInlineBlocks(FieldableEntityInterface $entity) { + $field_name = 'layout_builder__layout'; + + if (!$entity->hasField($field_name)) { + return; + } - return $node; + /** @var \Drupal\layout_builder\SectionListInterface $layout_field */ + $layout_field = $entity->$field_name; + + foreach ($layout_field->getSections() as $sid => $section) { + // Create a duplicate of each component. + foreach ($section->getComponents() as $component) { + $block = $component->getPlugin(); + + // Only clone inline blocks. + if (!$block instanceof InlineBlock) { + continue; + } + + $component_array = $component->toArray(); + $configuration = $component_array['configuration']; + + // Fetch the block content. + $block_content = NULL; + if (!empty($configuration['block_serialized'])) { + $block_content = unserialize($configuration['block_serialized']); + } + elseif (!empty($configuration['block_revision_id'])) { + $block_content = $this->entityTypeManager->getStorage('block_content') + ->loadRevision($configuration['block_revision_id']); + } + + // Create a duplicate block. + if ($block_content) { + /** @var \Drupal\block_content\BlockContentInterface $block_content */ + $cloned_block_content = $block_content->createDuplicate(); + $this->cloneParagraphs($cloned_block_content); + + // Unset the revision and add the serialized block content. + $configuration['block_revision_id'] = NULL; + $configuration['block_serialized'] = serialize($cloned_block_content); + } + + $new_component = new SectionComponent( + $this->uuid->generate(), + $component_array['region'], + $configuration, + $component_array['additional'] + ); + + // Remove existing components from the section and append a fresh copy. + $section->insertAfterComponent($component->getUuid(), $new_component); + $section->removeComponent($component->getUuid()); + } + + $layout_field->insertSection($sid, $section); + $layout_field->removeSection($sid + 1); + } } /** diff --git a/src/Form/QuickNodeCloneNodeForm.php b/src/Form/QuickNodeCloneNodeForm.php index bd84d39..08595a2 100644 --- a/src/Form/QuickNodeCloneNodeForm.php +++ b/src/Form/QuickNodeCloneNodeForm.php @@ -3,6 +3,7 @@ namespace Drupal\quick_node_clone\Form; use Drupal\Core\Form\FormStateInterface; +use Drupal\layout_builder\InlineBlockEntityOperations; use Drupal\node\NodeForm; /** @@ -38,7 +39,44 @@ class QuickNodeCloneNodeForm extends NodeForm { /** @var \Drupal\node\NodeInterface $node */ $node = $this->entity; $insert = $node->isNew(); + + // Temporarily create a copy of the layout builder field for all + // translations since we need an entity ID to correctly create block usage. + // For the first save we clear the field so layout_builder_entity_presave() + // doesn't try to save our cloned blocks without an entity ID. + $layout_values = []; + if ($node->hasField('layout_builder__layout') && !$node->get('layout_builder__layout')->isEmpty()) { + foreach (array_keys($node->getTranslationLanguages()) as $langcode) { + $translation = $node->getTranslation($langcode); + $layout_values[$langcode] = clone $translation->get('layout_builder__layout'); + $translation->set('layout_builder__layout', NULL); + } + } + $node->save(); + + // When using layout builder, we need to make sure that all cloned blocks + // in all translations are saved to the database before we save the node. + // Since we need a node ID to properly link the usage, we unfortunately need + // to save the node twice. We use SynchronizableEntityTrait to allow modules + // to detect the duplicate save in other entity hooks. + if ($layout_values && $this->moduleHandler->moduleExists('layout_builder') && $this->moduleHandler->moduleExists('block_content')) { + /** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */ + $entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class); + foreach (array_keys($node->getTranslationLanguages()) as $langcode) { + $translation = $node->getTranslation($langcode); + // Restore the cloned layout builder field. + $translation->set('layout_builder__layout', $layout_values[$langcode]->getValue()); + // Skip the presave for the default language since + // layout_builder_entity_presave() already handles that. + if ($langcode !== $node->language()->getId()) { + $entity_operations->handlePreSave($translation); + } + $translation->setNewRevision(FALSE); + } + $node->setSyncing(TRUE)->save(); + } + $node_link = $node->toLink($this->t('View'))->toString(); $context = [ '@type' => $node->getType(), -- GitLab From bf2c1ddbd51a40f7c234a5895f8de8e785f2a5ff Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 16 Feb 2024 17:17:43 +0000 Subject: [PATCH 02/20] Inject class_resolver service --- src/Form/QuickNodeCloneNodeForm.php | 49 ++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/Form/QuickNodeCloneNodeForm.php b/src/Form/QuickNodeCloneNodeForm.php index 1ccbe24..5d11ec9 100644 --- a/src/Form/QuickNodeCloneNodeForm.php +++ b/src/Form/QuickNodeCloneNodeForm.php @@ -2,6 +2,7 @@ namespace Drupal\quick_node_clone\Form; +use Drupal\Core\DependencyInjection\ClassResolverInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\layout_builder\InlineBlockEntityOperations; use Drupal\node\NodeForm; @@ -13,6 +14,52 @@ use Drupal\node\NodeForm; */ class QuickNodeCloneNodeForm extends NodeForm { + /** + * The class resolver service. + * + * @var \Drupal\Core\DependencyInjection\ClassResolverInterface + */ + protected $classResolver; + + /** + * Constructs a NodeForm object. + * + * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository + * The entity repository. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory + * The factory for the temp store object. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle service. + * @param \Drupal\Component\Datetime\TimeInterface $time + * The time service. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver + * The class resolver service. + */ + public function __construct(EntityRepositoryInterface $entity_repository, PrivateTempStoreFactory $temp_store_factory, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL, AccountInterface $current_user, DateFormatterInterface $date_formatter, ClassResolverInterface $class_resolver) { + parent::__construct($entity_repository, $temp_store_factory, $entity_type_bundle_info, $time, $current_user, $date_formatter); + $this->classResolver = $class_resolver; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.repository'), + $container->get('tempstore.private'), + $container->get('entity_type.bundle.info'), + $container->get('datetime.time'), + $container->get('current_user'), + $container->get('date.formatter'), + $container->get('class_resolver') + ); + } + + /** * {@inheritdoc} */ @@ -62,7 +109,7 @@ class QuickNodeCloneNodeForm extends NodeForm { // to detect the duplicate save in other entity hooks. if ($layout_values && $this->moduleHandler->moduleExists('layout_builder') && $this->moduleHandler->moduleExists('block_content')) { /** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */ - $entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class); + $entity_operations = $this->classResolver->getInstanceFromDefinition(InlineBlockEntityOperations::class); foreach (array_keys($node->getTranslationLanguages()) as $langcode) { $translation = $node->getTranslation($langcode); // Restore the cloned layout builder field. -- GitLab From 18fc16450238ff10b71217b03e12b93105daabd3 Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 16 Feb 2024 17:25:14 +0000 Subject: [PATCH 03/20] Update QuickNodeCloneNodeForm.php --- src/Form/QuickNodeCloneNodeForm.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Form/QuickNodeCloneNodeForm.php b/src/Form/QuickNodeCloneNodeForm.php index 5d11ec9..72dde93 100644 --- a/src/Form/QuickNodeCloneNodeForm.php +++ b/src/Form/QuickNodeCloneNodeForm.php @@ -2,10 +2,17 @@ namespace Drupal\quick_node_clone\Form; +use Drupal\Component\Datetime\TimeInterface; +use Drupal\Core\Datetime\DateFormatterInterface; use Drupal\Core\DependencyInjection\ClassResolverInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\TempStore\PrivateTempStoreFactory; use Drupal\layout_builder\InlineBlockEntityOperations; use Drupal\node\NodeForm; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Form controller for Quick Node Clone edit forms. @@ -59,7 +66,6 @@ class QuickNodeCloneNodeForm extends NodeForm { ); } - /** * {@inheritdoc} */ -- GitLab From 1991a06e2376bffde606edb6801f38c20d8a3443 Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Sat, 17 Feb 2024 10:34:43 +0000 Subject: [PATCH 04/20] Update file QuickNodeCloneEntityFormBuilder.php --- .../QuickNodeCloneEntityFormBuilder.php | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index ab34a20..908fb71 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -28,12 +28,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface; class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { use StringTranslationTrait; - /** - * The Form Builder. - * - * @var \Drupal\Core\Form\FormBuilderInterface - */ - protected $formBuilder; /** * The Entity Bundle Type Info. * @@ -52,12 +46,6 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * @var \Drupal\Core\Extension\ModuleHandlerInterface */ protected $moduleHandler; - /** - * The Entity Type Manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected $entityTypeManager; /** * The current user account. * @@ -85,23 +73,6 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { */ protected $uuid; - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('form_builder'), - $container->get('entity_type.bundle.info'), - $container->get('config.factory'), - $container->get('module_handler'), - $container->get('entity_type.manager'), - $container->get('current_user'), - $container->get('tempstore.private'), - $container->get('string_translation'), - $container->get('uuid') - ); - } - /** * QuickNodeCloneEntityFormBuilder constructor. * @@ -125,11 +96,10 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * The UUID service. */ public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, UuidInterface $uuid) { - $this->formBuilder = $formBuilder; + parent::__construct(entityTypeManager, formBuilder); $this->entityTypeBundleInfo = $entityTypeBundleInfo; $this->configFactory = $configFactory; $this->moduleHandler = $moduleHandler; - $this->entityTypeManager = $entityTypeManager; $this->currentUser = $currentUser; $this->privateTempStoreFactory = $privateTempStoreFactory; $this->stringTranslation = $stringTranslation; -- GitLab From 4222653978e4c6908fdc58d9b37bd8a0567d2a78 Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Sat, 17 Feb 2024 11:10:13 +0000 Subject: [PATCH 05/20] Update file QuickNodeCloneEntityFormBuilder.php --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 908fb71..d4500e1 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -96,7 +96,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * The UUID service. */ public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, UuidInterface $uuid) { - parent::__construct(entityTypeManager, formBuilder); + parent::__construct($entityTypeManager, $formBuilder); $this->entityTypeBundleInfo = $entityTypeBundleInfo; $this->configFactory = $configFactory; $this->moduleHandler = $moduleHandler; -- GitLab From 195f1c3045ece71797d883c8c2e373109bebeef3 Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Sat, 17 Feb 2024 11:10:31 +0000 Subject: [PATCH 06/20] Update file QuickNodeCloneEntityFormBuilder.php --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index d4500e1..6a78745 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -20,7 +20,6 @@ use Drupal\group\Entity\GroupContent; use Drupal\group\Entity\GroupRelationship; use Drupal\layout_builder\Plugin\Block\InlineBlock; use Drupal\layout_builder\SectionComponent; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Builds entity forms. -- GitLab From 779a29c489f9fe84b273a4c3b92486595c5783a4 Mon Sep 17 00:00:00 2001 From: Sean Blommaert <info@seanblommaert.nl> Date: Sat, 18 May 2024 08:12:11 +0200 Subject: [PATCH 07/20] Added a separate cloneLayoutSection method to allow other modules to reuse the code. --- .../QuickNodeCloneEntityFormBuilder.php | 93 +++++++++++-------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 6a78745..a627604 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -19,6 +19,7 @@ use Drupal\Core\TempStore\PrivateTempStoreFactory; use Drupal\group\Entity\GroupContent; use Drupal\group\Entity\GroupRelationship; use Drupal\layout_builder\Plugin\Block\InlineBlock; +use Drupal\layout_builder\Section; use Drupal\layout_builder\SectionComponent; /** @@ -247,54 +248,68 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { $layout_field = $entity->$field_name; foreach ($layout_field->getSections() as $sid => $section) { - // Create a duplicate of each component. - foreach ($section->getComponents() as $component) { - $block = $component->getPlugin(); - - // Only clone inline blocks. - if (!$block instanceof InlineBlock) { - continue; - } + $section = $this->cloneLayoutSection($section); + $layout_field->insertSection($sid, $section); + $layout_field->removeSection($sid + 1); + } + } - $component_array = $component->toArray(); - $configuration = $component_array['configuration']; + /** + * Clone a layout section. + * + * For nodes that have layout builder enabled, the inline blocks in a section + * need be to cloned as well. + * + * @param \Drupal\layout_builder\Section $section + * The section being cloned. + */ + public function cloneLayoutSection(Section $section) { + // Create a duplicate of each component. + foreach ($section->getComponents() as $component) { + $block = $component->getPlugin(); - // Fetch the block content. - $block_content = NULL; - if (!empty($configuration['block_serialized'])) { - $block_content = unserialize($configuration['block_serialized']); - } - elseif (!empty($configuration['block_revision_id'])) { - $block_content = $this->entityTypeManager->getStorage('block_content') - ->loadRevision($configuration['block_revision_id']); - } + // Only clone inline blocks. + if (!$block instanceof InlineBlock) { + continue; + } - // Create a duplicate block. - if ($block_content) { - /** @var \Drupal\block_content\BlockContentInterface $block_content */ - $cloned_block_content = $block_content->createDuplicate(); - $this->cloneParagraphs($cloned_block_content); + $component_array = $component->toArray(); + $configuration = $component_array['configuration']; - // Unset the revision and add the serialized block content. - $configuration['block_revision_id'] = NULL; - $configuration['block_serialized'] = serialize($cloned_block_content); - } + // Fetch the block content. + $block_content = NULL; + if (!empty($configuration['block_serialized'])) { + $block_content = unserialize($configuration['block_serialized']); + } + elseif (!empty($configuration['block_revision_id'])) { + $block_content = $this->entityTypeManager->getStorage('block_content') + ->loadRevision($configuration['block_revision_id']); + } - $new_component = new SectionComponent( - $this->uuid->generate(), - $component_array['region'], - $configuration, - $component_array['additional'] - ); + // Create a duplicate block. + if ($block_content) { + /** @var \Drupal\block_content\BlockContentInterface $block_content */ + $cloned_block_content = $block_content->createDuplicate(); + $this->cloneParagraphs($cloned_block_content); - // Remove existing components from the section and append a fresh copy. - $section->insertAfterComponent($component->getUuid(), $new_component); - $section->removeComponent($component->getUuid()); + // Unset the revision and add the serialized block content. + $configuration['block_revision_id'] = NULL; + $configuration['block_serialized'] = serialize($cloned_block_content); } - $layout_field->insertSection($sid, $section); - $layout_field->removeSection($sid + 1); + $new_component = new SectionComponent( + $this->uuid->generate(), + $component_array['region'], + $configuration, + $component_array['additional'] + ); + + // Remove existing components from the section and append a fresh copy. + $section->insertAfterComponent($component->getUuid(), $new_component); + $section->removeComponent($component->getUuid()); } + + return $section; } /** -- GitLab From da84985f78f67300a2a1d48d756cdb3694e74634 Mon Sep 17 00:00:00 2001 From: Mark Dorison <mark@dorison.org> Date: Tue, 23 Jul 2024 13:44:34 -0400 Subject: [PATCH 08/20] Fixed PHPCS issue. --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index a627604..b766e24 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -264,7 +264,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * The section being cloned. */ public function cloneLayoutSection(Section $section) { - // Create a duplicate of each component. + // Create a duplicate of each component. foreach ($section->getComponents() as $component) { $block = $component->getPlugin(); -- GitLab From 70716c83f69308b7d51a99020c9d6bfa9c44c01a Mon Sep 17 00:00:00 2001 From: Sean Blommaert <info@seanblommaert.nl> Date: Mon, 4 Nov 2024 05:20:42 +0100 Subject: [PATCH 09/20] Call $entity_operations->handlePreSave for all languages since this is not called for syncing nodes. --- src/Form/QuickNodeCloneNodeForm.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Form/QuickNodeCloneNodeForm.php b/src/Form/QuickNodeCloneNodeForm.php index 72dde93..0f9e8e2 100644 --- a/src/Form/QuickNodeCloneNodeForm.php +++ b/src/Form/QuickNodeCloneNodeForm.php @@ -120,11 +120,7 @@ class QuickNodeCloneNodeForm extends NodeForm { $translation = $node->getTranslation($langcode); // Restore the cloned layout builder field. $translation->set('layout_builder__layout', $layout_values[$langcode]->getValue()); - // Skip the presave for the default language since - // layout_builder_entity_presave() already handles that. - if ($langcode !== $node->language()->getId()) { - $entity_operations->handlePreSave($translation); - } + $entity_operations->handlePreSave($translation); $translation->setNewRevision(FALSE); } $node->setSyncing(TRUE)->save(); -- GitLab From c96a59d6a909c09f5dc2f8a57515ba18c4f07a1d Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 8 Nov 2024 10:37:47 +0000 Subject: [PATCH 10/20] Make less intrusive changes --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 5 ++--- src/Form/QuickNodeCloneNodeForm.php | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 04166bc..62b1868 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -21,7 +21,6 @@ use Drupal\group\Entity\GroupRelationship; use Drupal\layout_builder\Plugin\Block\InlineBlock; use Drupal\layout_builder\Section; use Drupal\layout_builder\SectionComponent; -use Drupal\node\Entity\Node; /** * Builds entity forms. @@ -131,8 +130,8 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { foreach ($new_node->getTranslationLanguages() as $langcode => $language) { /** @var \Drupal\node\Entity\Node $translated_node */ $translated_node = $new_node->getTranslation($langcode); - $this->cloneParagraphs($translated_node); - $this->cloneInlineBlocks($translated_node); + $translated_node = $this->cloneParagraphs($translated_node); + $translated_node = $this->cloneInlineBlocks($translated_node); $this->moduleHandler->alter('cloned_node', $translated_node, $original_entity); // Unset excluded fields. diff --git a/src/Form/QuickNodeCloneNodeForm.php b/src/Form/QuickNodeCloneNodeForm.php index 0f9e8e2..d535a6d 100644 --- a/src/Form/QuickNodeCloneNodeForm.php +++ b/src/Form/QuickNodeCloneNodeForm.php @@ -46,7 +46,7 @@ class QuickNodeCloneNodeForm extends NodeForm { * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver * The class resolver service. */ - public function __construct(EntityRepositoryInterface $entity_repository, PrivateTempStoreFactory $temp_store_factory, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL, AccountInterface $current_user, DateFormatterInterface $date_formatter, ClassResolverInterface $class_resolver) { + public function __construct(EntityRepositoryInterface $entity_repository, PrivateTempStoreFactory $temp_store_factory, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, AccountInterface $current_user, DateFormatterInterface $date_formatter, ClassResolverInterface $class_resolver) { parent::__construct($entity_repository, $temp_store_factory, $entity_type_bundle_info, $time, $current_user, $date_formatter); $this->classResolver = $class_resolver; } -- GitLab From 4b2e8cbbb1f18871d014b5a035e0651f9b925c5d Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 8 Nov 2024 10:49:22 +0000 Subject: [PATCH 11/20] Remove unrelated changes to paragraphs cloning --- .../QuickNodeCloneEntityFormBuilder.php | 97 +++++++++++-------- 1 file changed, 59 insertions(+), 38 deletions(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 62b1868..9df59ee 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -21,6 +21,7 @@ use Drupal\group\Entity\GroupRelationship; use Drupal\layout_builder\Plugin\Block\InlineBlock; use Drupal\layout_builder\Section; use Drupal\layout_builder\SectionComponent; +use Drupal\node\Entity\Node; /** * Builds entity forms. @@ -28,6 +29,12 @@ use Drupal\layout_builder\SectionComponent; class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { use StringTranslationTrait; +/** + * The Form Builder. + * + * @var \Drupal\Core\Form\FormBuilderInterface + */ + protected $formBuilder; /** * The Entity Bundle Type Info. * @@ -46,6 +53,12 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * @var \Drupal\Core\Extension\ModuleHandlerInterface */ protected $moduleHandler; + /** + * The Entity Type Manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; /** * The current user account. * @@ -88,11 +101,12 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * @param \Drupal\Component\Uuid\UuidInterface $uuid * The UUID service. */ - public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, UuidInterface $uuid) { - parent::__construct($entityTypeManager, $formBuilder); + public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, UuidInterface $uuid { + $this->formBuilder = $formBuilder; $this->entityTypeBundleInfo = $entityTypeBundleInfo; $this->configFactory = $configFactory; $this->moduleHandler = $moduleHandler; + $this->entityTypeManager = $entityTypeManager; $this->currentUser = $currentUser; $this->privateTempStoreFactory = $privateTempStoreFactory; $this->uuid = $uuid; @@ -184,42 +198,49 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { return $new_form; } - /** - * Clone the paragraphs of a node. - * - * If we do not clone the paragraphs attached to the node, the linked - * paragraphs would be linked to two nodes which is not ideal. - * - * @param \Drupal\node\Entity\FieldableEntityInterface $entity - * The entity being cloned. - */ - public function cloneParagraphs(FieldableEntityInterface $entity) { - foreach ($entity->getFieldDefinitions() as $field_name => $field_definition) { - $field_settings = $field_definition->getFieldStorageDefinition() - ->getSettings(); - if (($field_settings['target_type'] ?? NULL) != 'paragraph') { - continue; - } - foreach ($entity->$field_name as $paragraph_item) { - $paragraph = $paragraph_item->entity; - if (!$paragraph) { - continue; - } - $cloned_paragraph = $paragraph->createDuplicate(); - $paragraph_item->entity = $cloned_paragraph; - foreach ($cloned_paragraph->getFieldDefinitions() as $paragraph_field_name => $paragraph_field_definition) { - if ($this->excludeParagraphField($paragraph_field_name, $cloned_paragraph->bundle())) { - unset($cloned_paragraph->$paragraph_field_name); - } - $paragraph_field_storage_definition = $paragraph_field_definition - ->getFieldStorageDefinition(); - $paragraph_field_settings = $paragraph_field_storage_definition - ->getSettings(); - $this->moduleHandler->alter('cloned_node_paragraph_field', $cloned_paragraph, $paragraph_field_name, $paragraph_field_settings); - } - } - } - } + /** + * Clone the paragraphs of a node. + * + * If we do not clone the paragraphs attached to the node, the linked + * paragraphs would be linked to two nodes which is not ideal. + * + * @param \Drupal\node\Entity\Node $node + * The node to clone. + * + * @return \Drupal\node\Entity\Node + * The node with cloned paragraph fields. + */ + public function cloneParagraphs(Node $node) { + foreach ($node->getFieldDefinitions() as $field_definition) { + $field_storage_definition = $field_definition->getFieldStorageDefinition(); + $field_settings = $field_storage_definition->getSettings(); + $field_name = $field_storage_definition->getName(); + if (isset($field_settings['target_type']) && $field_settings['target_type'] == "paragraph") { + if (!$node->get($field_name)->isEmpty()) { + foreach ($node->get($field_name) as $value) { + if ($value->entity) { + $value->entity = $value->entity->createDuplicate(); + foreach ($value->entity->getFieldDefinitions() as $field_definition) { + $field_storage_definition = $field_definition->getFieldStorageDefinition(); + $pfield_settings = $field_storage_definition->getSettings(); + $pfield_name = $field_storage_definition->getName(); + + // Check whether this field is excluded and if so unset. + if ($this->excludeParagraphField($pfield_name, $value->entity->bundle())) { + unset($value->entity->{$pfield_name}); + } + + $this->moduleHandler->alter('cloned_node_paragraph_field', $value->entity, $pfield_name, $pfield_settings); + } + } + } + } + } + } + + return $node; + } + /** * Clone the inline blocks of a node's layout. -- GitLab From 84ed3fac295db77f84880dfcd44334f6daa3eba6 Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 8 Nov 2024 10:52:42 +0000 Subject: [PATCH 12/20] Typo fix --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 9df59ee..bb439c8 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -101,7 +101,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * @param \Drupal\Component\Uuid\UuidInterface $uuid * The UUID service. */ - public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, UuidInterface $uuid { + public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, UuidInterface $uuid) { $this->formBuilder = $formBuilder; $this->entityTypeBundleInfo = $entityTypeBundleInfo; $this->configFactory = $configFactory; -- GitLab From 85101235c7a6bf22d0189904d4e5907592d4cfda Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 8 Nov 2024 10:58:52 +0000 Subject: [PATCH 13/20] Fix codestyle --- .../QuickNodeCloneEntityFormBuilder.php | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index bb439c8..da65407 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -29,7 +29,7 @@ use Drupal\node\Entity\Node; class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { use StringTranslationTrait; -/** + /** * The Form Builder. * * @var \Drupal\Core\Form\FormBuilderInterface @@ -198,48 +198,48 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { return $new_form; } - /** - * Clone the paragraphs of a node. - * - * If we do not clone the paragraphs attached to the node, the linked - * paragraphs would be linked to two nodes which is not ideal. - * - * @param \Drupal\node\Entity\Node $node - * The node to clone. - * - * @return \Drupal\node\Entity\Node - * The node with cloned paragraph fields. - */ - public function cloneParagraphs(Node $node) { - foreach ($node->getFieldDefinitions() as $field_definition) { - $field_storage_definition = $field_definition->getFieldStorageDefinition(); - $field_settings = $field_storage_definition->getSettings(); - $field_name = $field_storage_definition->getName(); - if (isset($field_settings['target_type']) && $field_settings['target_type'] == "paragraph") { - if (!$node->get($field_name)->isEmpty()) { - foreach ($node->get($field_name) as $value) { - if ($value->entity) { - $value->entity = $value->entity->createDuplicate(); - foreach ($value->entity->getFieldDefinitions() as $field_definition) { - $field_storage_definition = $field_definition->getFieldStorageDefinition(); - $pfield_settings = $field_storage_definition->getSettings(); - $pfield_name = $field_storage_definition->getName(); - - // Check whether this field is excluded and if so unset. - if ($this->excludeParagraphField($pfield_name, $value->entity->bundle())) { - unset($value->entity->{$pfield_name}); - } - - $this->moduleHandler->alter('cloned_node_paragraph_field', $value->entity, $pfield_name, $pfield_settings); - } - } - } - } - } - } - - return $node; - } + /** + * Clone the paragraphs of a node. + * + * If we do not clone the paragraphs attached to the node, the linked + * paragraphs would be linked to two nodes which is not ideal. + * + * @param \Drupal\node\Entity\Node $node + * The node to clone. + * + * @return \Drupal\node\Entity\Node + * The node with cloned paragraph fields. + */ + public function cloneParagraphs(Node $node) { + foreach ($node->getFieldDefinitions() as $field_definition) { + $field_storage_definition = $field_definition->getFieldStorageDefinition(); + $field_settings = $field_storage_definition->getSettings(); + $field_name = $field_storage_definition->getName(); + if (isset($field_settings['target_type']) && $field_settings['target_type'] == "paragraph") { + if (!$node->get($field_name)->isEmpty()) { + foreach ($node->get($field_name) as $value) { + if ($value->entity) { + $value->entity = $value->entity->createDuplicate(); + foreach ($value->entity->getFieldDefinitions() as $field_definition) { + $field_storage_definition = $field_definition->getFieldStorageDefinition(); + $pfield_settings = $field_storage_definition->getSettings(); + $pfield_name = $field_storage_definition->getName(); + + // Check whether this field is excluded and if so unset. + if ($this->excludeParagraphField($pfield_name, $value->entity->bundle())) { + unset($value->entity->{$pfield_name}); + } + + $this->moduleHandler->alter('cloned_node_paragraph_field', $value->entity, $pfield_name, $pfield_settings); + } + } + } + } + } + } + + return $node; + } /** -- GitLab From f22867b68f35885868bc032131b74a204015bb51 Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 8 Nov 2024 11:09:02 +0000 Subject: [PATCH 14/20] Codestyle + unit tests fix --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index da65407..7f14588 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -145,7 +145,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { /** @var \Drupal\node\Entity\Node $translated_node */ $translated_node = $new_node->getTranslation($langcode); $translated_node = $this->cloneParagraphs($translated_node); - $translated_node = $this->cloneInlineBlocks($translated_node); + $this->cloneInlineBlocks($translated_node); $this->moduleHandler->alter('cloned_node', $translated_node, $original_entity); // Unset excluded fields. @@ -241,7 +241,6 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { return $node; } - /** * Clone the inline blocks of a node's layout. * -- GitLab From 68e383e43416c780d30b1bdaf6c36635477cbe54 Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 8 Nov 2024 11:21:36 +0000 Subject: [PATCH 15/20] Add a BC layer for the service constructior agruments change --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 7f14588..6e239ee 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -101,7 +101,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { * @param \Drupal\Component\Uuid\UuidInterface $uuid * The UUID service. */ - public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, UuidInterface $uuid) { + public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation, ?UuidInterface $uuid = NULL) { $this->formBuilder = $formBuilder; $this->entityTypeBundleInfo = $entityTypeBundleInfo; $this->configFactory = $configFactory; @@ -109,6 +109,12 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { $this->entityTypeManager = $entityTypeManager; $this->currentUser = $currentUser; $this->privateTempStoreFactory = $privateTempStoreFactory; + if (is_null($uuid) { + @trigger_error('Calling ' . __METHOD__ . '() without the $uuid argument is deprecated in quick_node_clone:1.20.0 and will be required in quick_node_clone:2.0.0. See https://www.drupal.org/node/3486344', E_USER_DEPRECATED); + + // @phpstan-ignore-next-line + $uuid = \Drupal::service('uuid'); + } $this->uuid = $uuid; $this->setStringTranslation($stringTranslation); } -- GitLab From 871411fa7accc7c18b149c4a948ded71faccbfac Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Fri, 8 Nov 2024 11:24:29 +0000 Subject: [PATCH 16/20] Typo (again) --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 6e239ee..03199f7 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -109,7 +109,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { $this->entityTypeManager = $entityTypeManager; $this->currentUser = $currentUser; $this->privateTempStoreFactory = $privateTempStoreFactory; - if (is_null($uuid) { + if (is_null($uuid)) { @trigger_error('Calling ' . __METHOD__ . '() without the $uuid argument is deprecated in quick_node_clone:1.20.0 and will be required in quick_node_clone:2.0.0. See https://www.drupal.org/node/3486344', E_USER_DEPRECATED); // @phpstan-ignore-next-line -- GitLab From a1d5696fa7f07e266575afdb06194c9e90fd7967 Mon Sep 17 00:00:00 2001 From: David Blankenship <david.blankenship@yale.edu> Date: Wed, 20 Nov 2024 17:00:07 -0500 Subject: [PATCH 17/20] fix(cloneParagraph): define mixed parameter Defining this as only taking nodes is not correct since BlockContent can be passed into it. This is a first stab to get this to work on existing instances while we work through a better way to handle this. --- .../QuickNodeCloneEntityFormBuilder.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 03199f7..206297a 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -205,25 +205,25 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { } /** - * Clone the paragraphs of a node. + * Clone the paragraphs of a node or a block content item. * * If we do not clone the paragraphs attached to the node, the linked * paragraphs would be linked to two nodes which is not ideal. * - * @param \Drupal\node\Entity\Node $node - * The node to clone. + * @param mixed $item + * The item to clone. * - * @return \Drupal\node\Entity\Node - * The node with cloned paragraph fields. + * @return mixed + * The item with cloned paragraph fields. */ - public function cloneParagraphs(Node $node) { - foreach ($node->getFieldDefinitions() as $field_definition) { + public function cloneParagraphs(mixed $item) { + foreach ($item->getFieldDefinitions() as $field_definition) { $field_storage_definition = $field_definition->getFieldStorageDefinition(); $field_settings = $field_storage_definition->getSettings(); $field_name = $field_storage_definition->getName(); if (isset($field_settings['target_type']) && $field_settings['target_type'] == "paragraph") { - if (!$node->get($field_name)->isEmpty()) { - foreach ($node->get($field_name) as $value) { + if (!$item->get($field_name)->isEmpty()) { + foreach ($item->get($field_name) as $value) { if ($value->entity) { $value->entity = $value->entity->createDuplicate(); foreach ($value->entity->getFieldDefinitions() as $field_definition) { @@ -244,7 +244,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { } } - return $node; + return $item; } /** -- GitLab From 52427144a1ce2c10f3f2c24a4aacff08a98d7353 Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Thu, 21 Nov 2024 10:15:06 +0000 Subject: [PATCH 18/20] Bring back FieldableEntityInterface --- .../QuickNodeCloneEntityFormBuilder.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index 206297a..cd848db 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -205,25 +205,25 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { } /** - * Clone the paragraphs of a node or a block content item. + * Clone the paragraphs of an entity. * - * If we do not clone the paragraphs attached to the node, the linked - * paragraphs would be linked to two nodes which is not ideal. + * If we do not clone the paragraphs attached to the entity, the linked + * paragraphs would be linked to two entities which is not ideal. * - * @param mixed $item - * The item to clone. + * @param \Drupal\node\Entity\FieldableEntityInterface $entity + * The entity to clone. * - * @return mixed - * The item with cloned paragraph fields. + * @return \Drupal\node\Entity\FieldableEntityInterface + * The entity with cloned paragraph fields. */ - public function cloneParagraphs(mixed $item) { - foreach ($item->getFieldDefinitions() as $field_definition) { + public function cloneParagraphs(FieldableEntityInterface $entity) { + foreach ($entity->getFieldDefinitions() as $field_definition) { $field_storage_definition = $field_definition->getFieldStorageDefinition(); $field_settings = $field_storage_definition->getSettings(); $field_name = $field_storage_definition->getName(); if (isset($field_settings['target_type']) && $field_settings['target_type'] == "paragraph") { - if (!$item->get($field_name)->isEmpty()) { - foreach ($item->get($field_name) as $value) { + if (!$entity->get($field_name)->isEmpty()) { + foreach ($entity->get($field_name) as $value) { if ($value->entity) { $value->entity = $value->entity->createDuplicate(); foreach ($value->entity->getFieldDefinitions() as $field_definition) { @@ -244,7 +244,7 @@ class QuickNodeCloneEntityFormBuilder extends EntityFormBuilder { } } - return $item; + return $entity; } /** -- GitLab From 6e46d31c5a05cf14fcfe329628b28e41fe6c9bfd Mon Sep 17 00:00:00 2001 From: Roman Paska <40929-Taran2L@users.noreply.drupalcode.org> Date: Thu, 21 Nov 2024 10:19:59 +0000 Subject: [PATCH 19/20] Fix PHPCS --- src/Entity/QuickNodeCloneEntityFormBuilder.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Entity/QuickNodeCloneEntityFormBuilder.php b/src/Entity/QuickNodeCloneEntityFormBuilder.php index cd848db..71049f9 100755 --- a/src/Entity/QuickNodeCloneEntityFormBuilder.php +++ b/src/Entity/QuickNodeCloneEntityFormBuilder.php @@ -21,7 +21,6 @@ use Drupal\group\Entity\GroupRelationship; use Drupal\layout_builder\Plugin\Block\InlineBlock; use Drupal\layout_builder\Section; use Drupal\layout_builder\SectionComponent; -use Drupal\node\Entity\Node; /** * Builds entity forms. -- GitLab From 814e7eb374b9ac06f242e5df91e6b96a835f2e5b Mon Sep 17 00:00:00 2001 From: Tatiana Kiseleva <tatiana.kiseleva@jakala.com> Date: Mon, 13 Jan 2025 13:33:39 +0700 Subject: [PATCH 20/20] 3100117: Skip restore NULL layout for node translation. --- src/Form/QuickNodeCloneNodeForm.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Form/QuickNodeCloneNodeForm.php b/src/Form/QuickNodeCloneNodeForm.php index d535a6d..889e6d3 100644 --- a/src/Form/QuickNodeCloneNodeForm.php +++ b/src/Form/QuickNodeCloneNodeForm.php @@ -119,9 +119,11 @@ class QuickNodeCloneNodeForm extends NodeForm { foreach (array_keys($node->getTranslationLanguages()) as $langcode) { $translation = $node->getTranslation($langcode); // Restore the cloned layout builder field. - $translation->set('layout_builder__layout', $layout_values[$langcode]->getValue()); - $entity_operations->handlePreSave($translation); - $translation->setNewRevision(FALSE); + if (!$layout_values[$langcode]->isEmpty()) { + $translation->set('layout_builder__layout', $layout_values[$langcode]->getValue()); + $entity_operations->handlePreSave($translation); + $translation->setNewRevision(FALSE); + } } $node->setSyncing(TRUE)->save(); } -- GitLab