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') ); } /** * QuickNodeCloneEntityFormBuilder constructor. * * @param \Drupal\Core\Form\FormBuilderInterface $formBuilder * The form builder. * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo * The entity type bundle info provider. * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory * The configuration factory. * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler * The module handler. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager. * @param \Drupal\Core\Session\AccountInterface $currentUser * Current user. * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $privateTempStoreFactory * Private temp store factory. * @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation * The string translation service. */ public function __construct(FormBuilderInterface $formBuilder, EntityTypeBundleInfoInterface $entityTypeBundleInfo, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, PrivateTempStoreFactory $privateTempStoreFactory, TranslationInterface $stringTranslation) { $this->formBuilder = $formBuilder; $this->entityTypeBundleInfo = $entityTypeBundleInfo; $this->configFactory = $configFactory; $this->moduleHandler = $moduleHandler; $this->entityTypeManager = $entityTypeManager; $this->currentUser = $currentUser; $this->privateTempStoreFactory = $privateTempStoreFactory; $this->stringTranslation = $stringTranslation; } /** * {@inheritdoc} */ public function getForm(EntityInterface $original_entity, $operation = 'default', array $form_state_additions = []) { // Clone the node using the awesome createDuplicate() core function. /** @var \Drupal\node\Entity\Node $new_node */ $new_node = $original_entity->createDuplicate(); $new_node->set('uid', $this->currentUser->id()); $new_node->set('created', time()); $new_node->set('changed', time()); $new_node->set('revision_timestamp', time()); // Get and store groups of original entity, if any. $groups = []; if ($this->moduleHandler->moduleExists('gnode')) { $relation_class = class_exists(GroupContent::class) ? GroupContent::class : GroupRelationship::class; /** @var \Drupal\Core\Entity\ContentEntityInterface $original_entity */ foreach ($relation_class::loadByEntity($original_entity) as $group_relationship) { $groups[] = $group_relationship->getGroup(); } } $form_state_additions['quick_node_clone_groups_storage'] = $groups; // Get default status value of node bundle. $default_bundle_status = $this->entityTypeManager->getStorage('node')->create(['type' => $new_node->bundle()])->status->value; // Clone all translations of a node. 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->moduleHandler->alter('cloned_node', $translated_node, $original_entity); // Unset excluded fields. $config_name = 'exclude.node.' . $translated_node->getType(); if ($exclude_fields = $this->getConfigSettings($config_name)) { foreach ($exclude_fields as $field) { unset($translated_node->{$field}); } } $prepend_text = ""; $title_prepend_config = $this->getConfigSettings('text_to_prepend_to_title'); if (!empty($title_prepend_config)) { $prepend_text = $title_prepend_config . " "; } $clone_status_config = $this->getConfigSettings('clone_status'); if (!$clone_status_config) { $key = $translated_node->getEntityType()->getKey('published'); $translated_node->set($key, $default_bundle_status); } $translated_node->setTitle($this->t('@prepend_text@title', [ '@prepend_text' => $prepend_text, '@title' => $translated_node->getTitle(), ], [ 'langcode' => $langcode, ] ) ); } // Get the form object for the entity defined in entity definition. $form_object = $this->entityTypeManager->getFormObject($translated_node->getEntityTypeId(), $operation); // Assign the form's entity to our duplicate! $form_object->setEntity($translated_node); $form_state = (new FormState())->setFormState($form_state_additions); $new_form = $this->formBuilder->buildForm($form_object, $form_state); // If we are cloning addresses, we need to reset our delta counter // once the form is built. $tempstore = $this->privateTempStoreFactory->get('quick_node_clone'); if ($tempstore->get('address_initial_value_delta') !== NULL) { $tempstore->set('address_initial_value_delta', NULL); } return $new_form; } /** * Clone the paragraphs of an entity. * * 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 \Drupal\Core\Entity\ContentEntityInterface $entity * The entity to clone paragraphs for. * * @return \Drupal\Core\Entity\ContentEntityInterface $entity * The entity with cloned paragraph fields. */ public function cloneParagraphs(ContentEntityInterface $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 (!$entity->get($field_name)->isEmpty()) { // TODO: Layout Paragraphs integration: // TODO: See https://www.drupal.org/project/layout_paragraphs/issues/3218226#comment-14129846 // TODO: See https://www.drupal.org/project/quick_node_clone/issues/3218222 // TODO: All the Steps 1-6 don't belong here in a perfect world but belong to layout_paragraphs // 1. Create helper array to store old / new uuids from all paragraphs // to update parent_uuids from all children later here: $uuidOldNewMap = []; foreach ($entity->get($field_name) as $value) { if ($value->entity) { // 2. Store the old uuid of this paragraph: $oldUuid = $value->entity->uuid(); $value->entity = $value->entity->createDuplicate(); // 3. Store the new uuid of this paragraph: $newUuid = $value->entity->uuid(); // 4. Save old => new uuid relations in array to replace the old one later: $uuidOldNewMap[$oldUuid] = $newUuid; 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); if (isset($paragraph_field_settings['target_type']) && $paragraph_field_settings['target_type'] == "paragraph" && !empty($value->entity->{$paragraph_field_name})) { // Do the same for any nested paragraphs. self::cloneParagraphs($value->entity); } } // 5. Update children paragraphs parent_uuid's to the new uuids from // the cloned paragraphs: $behavior_settings = $value->entity->getAllBehaviorSettings(); if (isset($behavior_settings['layout_paragraphs']) && !empty($behavior_settings['layout_paragraphs']['parent_uuid'])) { $oldParentUuid = $behavior_settings['layout_paragraphs']['parent_uuid']; if (!empty($uuidOldNewMap[$oldParentUuid])) { $newParentUuid = $uuidOldNewMap[$oldParentUuid]; $behavior_settings['layout_paragraphs']['parent_uuid'] = $newParentUuid; $value->entity->setBehaviorSettings('layout_paragraphs', $behavior_settings['layout_paragraphs']); } } } } } } } return $entity; } /** * Check whether to exclude the paragraph field. * * @param string $field_name * The field name. * @param string $bundle_name * The bundle name. * * @return bool|null * TRUE or FALSE depending on config setting, or NULL if config not found. */ public function excludeParagraphField($field_name, $bundle_name) { $config_name = 'exclude.paragraph.' . $bundle_name; if ($exclude_fields = $this->getConfigSettings($config_name)) { return in_array($field_name, $exclude_fields); } } /** * Get the settings. * * @param string $value * The setting name. * * @return array|mixed|null * Returns the setting value if it exists, or NULL. */ public function getConfigSettings($value) { $settings = $this->configFactory->get('quick_node_clone.settings') ->get($value); return $settings; } }