diff --git a/src/Entity/Paragraph.php b/src/Entity/Paragraph.php index 00bc6cb..6b41f19 100644 --- a/src/Entity/Paragraph.php +++ b/src/Entity/Paragraph.php @@ -328,4 +328,22 @@ class Paragraph extends ContentEntityBase implements ParagraphInterface, EntityN return array(\Drupal::currentUser()->id()); } + /** + * {@inheritdoc} + */ + public function createDuplicate() { + $duplicate = parent::createDuplicate(); + // Loop over entity fields and duplicate nested paragraphs. + foreach ($duplicate->getFields() as $field) { + if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions') { + if ($field->getFieldDefinition()->getTargetEntityTypeId() == "paragraph") { + foreach ($field as $item) { + $item->entity = $item->entity->createDuplicate(); + } + } + } + } + return $duplicate; + } + } diff --git a/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php index c43f46d..2ddbde0 100644 --- a/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php +++ b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php @@ -429,6 +429,25 @@ class InlineParagraphsWidget extends WidgetBase { '#paragraphs_mode' => 'edit', ); + $links['duplicate_button'] = [ + '#type' => 'submit', + '#value' => $this->t('Duplicate'), + '#name' => strtr($id_prefix, '-', '_') . '_duplicate', + '#weight' => 502, + '#submit' => [[get_class($this), 'duplicateSubmit']], + '#limit_validation_errors' => [array_merge($parents, [$field_name, 'add_more'])], + '#delta' => $delta, + '#ajax' => [ + 'callback' => [get_class($this), 'itemAjax'], + 'wrapper' => $widget_state['ajax_wrapper_id'], + 'effect' => 'fade', + ], + '#access' => $paragraphs_entity->access('update'), + '#prefix' => '
  • ', + '#suffix' => '
  • ', + '#paragraphs_mode' => 'duplicate', + ]; + $element['top']['paragraphs_remove_button_container']['paragraphs_remove_button'] = [ '#type' => 'submit', '#value' => $this->t('Remove'), @@ -1053,6 +1072,35 @@ class InlineParagraphsWidget extends WidgetBase { $form_state->setRebuild(); } + /** + * Creates a duplicate of the paragraph entity. + */ + public static function duplicateSubmit(array $form, FormStateInterface $form_state) { + $button = $form_state->getTriggeringElement(); + // Go one level up in the form, to the widgets container. + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4)); + $field_name = $element['#field_name']; + $parents = $element['#field_parents']; + + // Inserting new element in the array. + $field_state = static::getWidgetState($parents, $field_name, $form_state); + $delta = $button['#delta']; + $field_state['items_count']++; + $field_state['real_item_count']++; + $field_state['original_deltas'] = array_merge($field_state['original_deltas'], ['1' => 1]) ; + + // Create the duplicated paragraph and insert it below the original. + $paragraph[] = [ + 'entity' => $field_state['paragraphs'][$delta]['entity']->createDuplicate(), + 'display' => $field_state['paragraphs'][$delta]['display'], + 'mode' => 'edit' + ]; + array_splice($field_state['paragraphs'], $delta + 1, 0, $paragraph); + + static::setWidgetState($parents, $field_name, $form_state, $field_state); + $form_state->setRebuild(); + } + public static function paragraphsItemSubmit(array $form, FormStateInterface $form_state) { $button = $form_state->getTriggeringElement(); diff --git a/src/Tests/ParagraphsTestBase.php b/src/Tests/ParagraphsTestBase.php index 222ff96..fd92980 100644 --- a/src/Tests/ParagraphsTestBase.php +++ b/src/Tests/ParagraphsTestBase.php @@ -129,9 +129,9 @@ abstract class ParagraphsTestBase extends WebTestBase { 'field_name' => $paragraphs_field_name, 'entity_type' => $entity_type, 'type' => 'entity_reference_revisions', + 'cardinality' => '-1', 'settings' => [ 'target_type' => 'paragraph', - 'cardinality' => '-1', ], ]); $field_storage->save(); diff --git a/src/Tests/ParagraphsWidgetButtonsTest.php b/src/Tests/ParagraphsWidgetButtonsTest.php index 73d0b8a..3781a61 100644 --- a/src/Tests/ParagraphsWidgetButtonsTest.php +++ b/src/Tests/ParagraphsWidgetButtonsTest.php @@ -105,6 +105,106 @@ class ParagraphsWidgetButtonsTest extends ParagraphsTestBase { } /** + * Tests duplicate paragraph feature. + */ + public function testDuplicateButton(){ + $this->addParagraphedContentType('paragraphed_test', 'field_paragraphs'); + + $this->loginAsAdmin(['create paragraphed_test content', 'edit any paragraphed_test content']); + // Add a Paragraph type. + $paragraph_type = 'text_paragraph'; + $this->addParagraphsType($paragraph_type); + $this->addParagraphsType('text'); + + // Add a text field to the text_paragraph type. + static::fieldUIAddNewField('admin/structure/paragraphs_type/' . $paragraph_type, 'text', 'Text', 'text_long', [], []); + $this->drupalPostAjaxForm('node/add/paragraphed_test', [], 'field_paragraphs_text_paragraph_add_more'); + + // Create a node with a Paragraph. + $text = 'recognizable_text'; + $edit = [ + 'title[0][value]' => 'paragraphs_mode_test', + 'field_paragraphs[0][subform][field_text][0][value]' => $text, + ]; + $this->drupalPostForm(NULL, $edit, t('Save and publish')); + $node = $this->drupalGetNodeByTitle('paragraphs_mode_test'); + + // Change edit mode to "closed". + $this->setParagraphsWidgetMode('paragraphed_test', 'field_paragraphs', 'closed'); + $this->drupalGet('node/' . $node->id() . '/edit'); + + // Click "Duplicate" button. + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_0_duplicate'); + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_0_edit'); + $this->assertFieldByName('field_paragraphs[0][subform][field_text][0][value]', $text); + $this->assertFieldByName('field_paragraphs[1][subform][field_text][0][value]', $text); + + // Save and check if both paragraphs are present. + $this->drupalPostForm(NULL, $edit, t('Save and keep published')); + $this->assertNoUniqueText($text); + } + + /** + * Tests duplicate paragraph feature with nested paragraphs. + */ + public function testDuplicateButtonWithNesting(){ + $this->addParagraphedContentType('paragraphed_test', 'field_paragraphs'); + + $this->loginAsAdmin(['create paragraphed_test content', 'edit any paragraphed_test content']); + // Add nested Paragraph type. + $nested_paragraph_type = 'nested_paragraph'; + $this->addParagraphsType($nested_paragraph_type); + // Add text Paragraph type + $paragraph_type = 'text'; + $this->addParagraphsType($paragraph_type); + + // Add a text field to the text_paragraph type. + static::fieldUIAddNewField('admin/structure/paragraphs_type/' . $paragraph_type, 'text', 'Text', 'text_long', [], []); + + // Add a ERR paragraph field to the nested_paragraph type. + static::fieldUIAddNewField('admin/structure/paragraphs_type/' . $nested_paragraph_type, 'nested', 'Nested', 'field_ui:entity_reference_revisions:paragraph', [ + 'settings[target_type]' => 'paragraph', + 'cardinality' => '-1', + ], []); + $this->drupalPostAjaxForm('node/add/paragraphed_test', [], 'field_paragraphs_nested_paragraph_add_more'); + + // Create a node with a Paragraph. + $edit = [ + 'title[0][value]' => 'paragraphs_mode_test', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and publish')); + $node = $this->drupalGetNodeByTitle('paragraphs_mode_test'); + + // Add a text field to nested paragraph + $text = 'recognizable_text'; + $this->drupalPostAjaxForm('node/' . $node->id() . '/edit', [], 'field_paragraphs_0_subform_field_nested_text_add_more'); + $edit = [ + 'field_paragraphs[0][subform][field_nested][0][subform][field_text][0][value]' => $text, + ]; + $this->drupalPostForm(NULL, $edit, 'Save and keep published'); + + // Switch mode to closed + $this->setParagraphsWidgetMode('paragraphed_test', 'field_paragraphs', 'closed'); + $this->drupalGet('node/' . $node->id() . '/edit'); + + // Click "Duplicate" button. + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_0_duplicate'); + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_0_edit'); + $this->assertFieldByName('field_paragraphs[0][subform][field_nested][0][subform][field_text][0][value]', $text); + + // Change the text paragraph value of duplicated nested paragraph + $second_paragraph_text = 'duplicated_text'; + $edit = [ + 'field_paragraphs[1][subform][field_nested][0][subform][field_text][0][value]' => $second_paragraph_text, + ]; + + // Save and check if the changed text paragraph value of the duplicated + // paragraph is not the same as in the original paragraph + $this->drupalPostForm(NULL, $edit, t('Save and keep published')); + $this->assertUniqueText($text); + } + + /** * Sets the Paragraphs widget display mode. * * @param string $content_type