diff --git a/paragraphs.module b/paragraphs.module index 1cd3570..81c2640 100644 --- a/paragraphs.module +++ b/paragraphs.module @@ -119,48 +119,6 @@ function paragraphs_form_field_storage_config_edit_form_alter(&$form, \Drupal\Co } /** - * Implements hook_form_FORM_ID_alter(). - * - * Indicate unsupported multilingual paragraphs field configuration. - */ -function paragraphs_form_field_config_edit_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { - $field = $form_state->getFormObject()->getEntity(); - - if (!\Drupal::hasService('content_translation.manager')) { - return; - } - - $bundle_is_translatable = \Drupal::service('content_translation.manager') - ->isEnabled($field->getTargetEntityTypeId(), $field->getTargetBundle()); - - if (!$bundle_is_translatable - || $field->getType() != 'entity_reference_revisions' - || $field->getSetting('target_type') != 'paragraph') { - return; - } - - // This is a translatable ERR field pointing to a paragraph. - $message_display = 'warning'; - $message_text = t('Paragraphs fields do not support translation. See the online documentation.', [ - ':documentation' => Url::fromUri('https://www.drupal.org/node/2735121') - ->toString() - ]); - - if ($form['translatable']['#default_value'] == TRUE) { - $message_display = 'error'; - } - - $form['paragraphs_message'] = array( - '#type' => 'container', - '#markup' => $message_text, - '#attributes' => array( - 'class' => array('messages messages--' . $message_display), - ), - '#weight' => 0, - ); -} - -/** * Implements hook_module_implements_alter(). * * Our paragraphs_form_field_config_edit_form_alter() needs to be run after @@ -179,73 +137,6 @@ function paragraphs_module_implements_alter(&$implementations, $hook) { } /** - * Implements hook_form_FORM_ID_alter(). - * - * Indicate unsupported multilingual paragraphs field configuration. - * - * Add a warning that paragraph fields can not be translated. - * Switch to error if a paragraph field is marked as translatable. - */ -function paragraphs_form_language_content_settings_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { - // Without it Paragraphs message are meaningless. - if (!\Drupal::hasService('content_translation.manager')) { - return; - } - - $content_translation_manager = \Drupal::service('content_translation.manager'); - $message_display = 'warning'; - $message_text = t('(* unsupported) Paragraphs fields do not support translation. See the online documentation.', [ - ':documentation' => Url::fromUri('https://www.drupal.org/node/2735121') - ->toString()]); - $map = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('entity_reference_revisions'); - foreach ($map as $entity_type_id => $info) { - if (!$content_translation_manager->isEnabled($entity_type_id)) { - continue; - } - $field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id); - - /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition */ - foreach ($field_storage_definitions as $name => $storage_definition) { - if ($storage_definition->getSetting('target_type') && $storage_definition->getSetting('target_type') == 'paragraph') { - - // For configurable fields, check all bundles on which the field exists, - // for base fields that are translable, check all bundles, - // untranslatable base fields do not show up at all. - $bundles = []; - if ($storage_definition instanceof FieldStorageConfigInterface) { - $bundles = $storage_definition->getBundles(); - } - elseif ($storage_definition->isTranslatable()) { - $bundles = Element::children($form['settings'][$entity_type_id]); - } - foreach($bundles as $bundle) { - if (!$content_translation_manager->isEnabled($entity_type_id, $bundle)) { - continue; - } - - // Update the label and if the paragraph field is translatable, - // display an error message instead of just a warning. - if (isset($form['settings'][$entity_type_id][$bundle]['fields'][$name]['#label'])) { - $form['settings'][$entity_type_id][$bundle]['fields'][$name]['#label'] = t('@field_label (* unsupported)', ['@field_label' => $form['settings'][$entity_type_id][$bundle]['fields'][$name]['#label']]); - } - if (!empty($form['settings'][$entity_type_id][$bundle]['fields'][$name]['#default_value'])) { - $message_display = 'error'; - } - } - } - } - } - $form['settings']['paragraphs_message'] = array( - '#type' => 'container', - '#markup' => $message_text, - '#attributes' => array( - 'class' => array('messages messages--' . $message_display), - ), - '#weight' => 0, - ); -} - -/** * Prepares variables for paragraph templates. * * Default template: paragraph.html.twig. diff --git a/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php index 36f681b..3bc03ee 100644 --- a/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php +++ b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php @@ -4,6 +4,7 @@ namespace Drupal\paragraphs\Plugin\Field\FieldWidget; use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\Html; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface; @@ -15,9 +16,11 @@ use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Render\Element; +use Drupal\field\Entity\FieldConfig; use Drupal\node\Entity\Node; use Drupal\paragraphs; use Drupal\paragraphs\ParagraphInterface; +use Drupal\paragraphs\Entity\Paragraph; use Symfony\Component\Validator\ConstraintViolationInterface; use Drupal\paragraphs\Plugin\EntityReferenceSelection\ParagraphSelection; @@ -231,7 +234,6 @@ class InlineParagraphsWidget extends WidgetBase { $info = []; $paragraphs_entity = NULL; - $host = $items->getEntity(); $widget_state = static::getWidgetState($parents, $field_name, $form_state); $entity_manager = \Drupal::entityTypeManager(); @@ -298,54 +300,7 @@ class InlineParagraphsWidget extends WidgetBase { } if ($paragraphs_entity) { - // Detect if we are translating. - $this->initIsTranslating($form_state, $host); - $langcode = $form_state->get('langcode'); - - if (!$this->isTranslating) { - // Set the langcode if we are not translating. - $langcode_key = $paragraphs_entity->getEntityType()->getKey('langcode'); - if ($paragraphs_entity->get($langcode_key)->value != $langcode) { - // If a translation in the given language already exists, switch to - // that. If there is none yet, update the language. - if ($paragraphs_entity->hasTranslation($langcode)) { - $paragraphs_entity = $paragraphs_entity->getTranslation($langcode); - } - else { - $paragraphs_entity->set($langcode_key, $langcode); - } - } - } - else { - // Add translation if missing for the target language. - if (!$paragraphs_entity->hasTranslation($langcode)) { - // Get the selected translation of the paragraph entity. - $entity_langcode = $paragraphs_entity->language()->getId(); - $source = $form_state->get(['content_translation', 'source']); - $source_langcode = $source ? $source->getId() : $entity_langcode; - // Make sure the source language version is used if available. It is a - // valid scenario to have no paragraphs items in the source version of - // the host and fetching the translation without this check could lead - // to an exception. - if ($paragraphs_entity->hasTranslation($source_langcode)) { - $paragraphs_entity = $paragraphs_entity->getTranslation($source_langcode); - } - // The paragraphs entity has no content translation source field if - // no paragraph entity field is translatable, even if the host is. - if ($paragraphs_entity->hasField('content_translation_source')) { - // Initialise the translation with source language values. - $paragraphs_entity->addTranslation($langcode, $paragraphs_entity->toArray()); - $translation = $paragraphs_entity->getTranslation($langcode); - $manager = \Drupal::service('content_translation.manager'); - $manager->getTranslationMetadata($translation)->setSource($paragraphs_entity->language()->getId()); - } - } - // If any paragraphs type is translatable do not switch. - if ($paragraphs_entity->hasField('content_translation_source')) { - // Switch the paragraph to the translation. - $paragraphs_entity = $paragraphs_entity->getTranslation($langcode); - } - } + $paragraphs_entity = $this->prepareEntity($paragraphs_entity, $items, $form_state); $element_parents = $parents; $element_parents[] = $field_name; @@ -399,7 +354,8 @@ class InlineParagraphsWidget extends WidgetBase { $links = array(); // Hide the button when translating. - $button_access = $paragraphs_entity->access('delete') && !$this->isTranslating; + $button_access = $paragraphs_entity->access('delete') && (!$this->isTranslating || $items->getFieldDefinition() + ->isTranslatable()); if ($item_mode != 'remove') { $links['remove_button'] = [ '#type' => 'submit', @@ -898,7 +854,7 @@ class InlineParagraphsWidget extends WidgetBase { $host = $items->getEntity(); $this->initIsTranslating($form_state, $host); - if (($this->realItemCount < $cardinality || $cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) && !$form_state->isProgrammed() && !$this->isTranslating) { + if (($this->realItemCount < $cardinality || $cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) && !$form_state->isProgrammed() && (!$this->isTranslating || $this->fieldDefinition->isTranslatable())) { $elements['add_more'] = $this->buildAddActions(); } @@ -1315,6 +1271,88 @@ class InlineParagraphsWidget extends WidgetBase { } /** + * Prepares the paragraph entity for translation. + * + * @param \Drupal\paragraphs\Entity\Paragraph $entity + * The paragraph entity. + * @param \Drupal\Core\Field\FieldItemListInterface $items + * The field items list that hosts this paragraph. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return \Drupal\paragraphs\Entity\Paragraph + * The prepared paragraph. + * + * @see \Drupal\Core\Entity\ContentEntityForm::initFormLangcodes(). + */ + protected function prepareEntity(Paragraph $entity, FieldItemListInterface $items, FormStateInterface $form_state) { + // Detect if we are translating. + $this->initIsTranslating($form_state, $items->getEntity()); + $langcode = $form_state->get('langcode'); + + if (!$this->isTranslating) { + // Set the langcode if we are not translating. + $langcode_key = $entity->getEntityType()->getKey('langcode'); + if ($entity->get($langcode_key)->value != $langcode) { + // If a translation in the given language already exists, switch to + // that. If there is none yet, update the language. + if ($entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } + else { + $entity->set($langcode_key, $langcode); + } + } + } + + // Localised Paragraphs. + // If the parent field is marked as translatable, assume paragraphs + // to be localized (host entity expects different paragraphs for + // different languages) + elseif ($items->getFieldDefinition()->isTranslatable()) { + if (!empty($form_state->get('content_translation'))) { + $entity = $this->cloneReferencedEntity($entity, $langcode); + } + if ($entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } + } + + // Translated Paragraphs + // If the parent field is not translatable, assume the paragraph + // entity itself (rather the fields within it) are marked as + // translatable. (host entity expects same paragraphs in different + // languages). + else { + // Add translation if missing for the target language. + if (!$entity->hasTranslation($langcode)) { + // Get the selected translation of the paragraph entity. + $entity_langcode = $entity->language()->getId(); + $source = $form_state->get(['content_translation', 'source']); + $source_langcode = $source ? $source->getId() : $entity_langcode; + $entity = $entity->getTranslation($source_langcode); + // The paragraphs entity has no content translation source field if + // no paragraph entity field is translatable, even if the host is. + if ($entity->hasField('content_translation_source')) { + // Initialise the translation with source language values. + $entity->addTranslation($langcode, $entity->toArray()); + $translation = $entity->getTranslation($langcode); + $manager = \Drupal::service('content_translation.manager'); + $manager->getTranslationMetadata($translation) + ->setSource($entity->language()->getId()); + } + } + // If any paragraphs type is translatable do not switch. + if ($entity->hasField('content_translation_source')) { + // Switch the paragraph to the translation. + $entity = $entity->getTranslation($langcode); + } + } + + return $entity; + } + + /** * Initializes the translation form state. * * @param \Drupal\Core\Form\FormStateInterface $form_state @@ -1347,6 +1385,64 @@ class InlineParagraphsWidget extends WidgetBase { } /** + * Clones Paragraphs (and field_collections) recursively, preparing them to be + * passed to the translated paragraph widget. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity_to_clone The Entity + * to clone + * @param string $langcode language code for all the clone entities created. + * @return \Drupal\Core\Entity\ContentEntityInterface New entity object which + * has the same data as the original $entity_to_clone, Note this entity is not + * saved. + */ + protected function cloneReferencedEntity(ContentEntityInterface $entity_to_clone, $langcode) { + $entity_manager = \Drupal::entityTypeManager(); + // Get the paragraph item as an array of values. + $paragraph_array = $entity_to_clone->toArray(); + $target_type = $entity_to_clone->getEntityTypeId(); + $entity_type = $entity_manager->getDefinition($target_type); + $bundle_key = $entity_type->getKey('bundle'); + + // Create a new entity for this language. + $new_entity = array( + $bundle_key => $entity_to_clone->bundle(), + 'langcode' => $langcode + ); + + // Loop through all fields in the paragraph and add to new entity. + foreach ($entity_to_clone->getFieldDefinitions() as $field_name => $field_definition) { + // Check that the value is a field config and not empty. + if ($field_definition instanceof FieldConfig && !empty($paragraph_array[$field_name])) { + if ($this->checkEntityTypeCloneable($field_definition->getSetting('target_type'))) { + /** @var [EntityInterface] $entities */ + $entities = $entity_to_clone->get($field_name)->referencedEntities(); + $cloned_entites = []; + foreach ($entities as $entity) { + $cloned_entites[] = $this->cloneReferencedEntity($entity, $langcode); + } + $new_entity[$field_name] = $cloned_entites; + } + else { + $new_entity[$field_name] = $paragraph_array[$field_name]; + } + } + } + return $entity_manager->getStorage($target_type)->create($new_entity); + } + + /** + * Checks whether we support cloning a certain entity type or not. + * + * @param string $entity_type_id the entity type ID to check whether it's cloneable + * @return bool + */ + protected function checkEntityTypeCloneable($entity_type_id) { + // @todo: maybe this list should be moved to widget configs with some sensible + // default? + return in_array($entity_type_id, ['field_collection_item', 'paragraph']); + } + + /** * After-build callback for removing the translatability clue from the widget. * * If the fields on the paragraph type are translatable, diff --git a/src/Tests/Classic/ParagraphsAsymmetricTranslationTest.php b/src/Tests/Classic/ParagraphsAsymmetricTranslationTest.php new file mode 100644 index 0000000..38aff2a --- /dev/null +++ b/src/Tests/Classic/ParagraphsAsymmetricTranslationTest.php @@ -0,0 +1,522 @@ +drupalPlaceBlock('local_tasks_block'); + $this->drupalPlaceBlock('page_title_block'); + + $this->admin_user = $this->drupalCreateUser( + [ + 'administer site configuration', + 'administer nodes', + 'create paragraphed_content_demo content', + 'edit any paragraphed_content_demo content', + 'delete any paragraphed_content_demo content', + 'administer paragraph form display', + 'administer node form display', + 'administer paragraph fields', + 'administer content translation', + 'translate any entity', + 'create content translations', + 'administer languages', + 'administer content types', + ] + ); + + $this->drupalLogin($this->admin_user); + + // Mark the paragraph entities as untranslatable and the paragraph field + // as translatable. + $edit = [ + 'entity_types[paragraph]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][field_paragraphs_demo]' => TRUE, +// 'settings[paragraph][images][translatable]' => FALSE, +// 'settings[paragraph][images][settings][language][language_alterable]' => FALSE, +// 'settings[paragraph][image_text][translatable]' => FALSE, +// 'settings[paragraph][image_text][settings][language][language_alterable]' => FALSE, + 'settings[paragraph][nested_paragraph][translatable]' => TRUE, + 'settings[paragraph][nested_paragraph][settings][language][language_alterable]' => FALSE, + 'settings[paragraph][text][translatable]' => TRUE, + 'settings[paragraph][text][settings][language][language_alterable]' => FALSE, + + 'settings[paragraph][nested_paragraph][fields][field_paragraphs_demo]' => TRUE, + + +// 'settings[paragraph][text_image][translatable]' => FALSE, +// 'settings[paragraph][text_image][settings][language][language_alterable]' => FALSE, +// 'settings[paragraph][user][translatable]' => FALSE, +// 'settings[paragraph][user][settings][language][language_alterable]' => FALSE, + ]; + $this->drupalPostForm( + 'admin/config/regional/content-language', + $edit, + t('Save configuration') + ); + } + + /** + * Test asymmetric translation. + * + * 1. Translate node and create different paragraphs. + * 2. Update the paragraphs on each translation. + * 3. Add different number of paragraphs on each translation. + * 4. Remove paragraphs from each translation. + * 5. Reorder the paragraphs on each translation. + */ + public function testParagraphsMultilingualFieldTranslation() { + // Edit widget to classic. + $this->drupalGet('/admin/structure/types/manage/paragraphed_content_demo/form-display'); + $this->drupalPostForm(NULL, array('fields[field_paragraphs_demo][type]' => 'entity_reference_paragraphs'), t('Save')); + + // 1. Translate node and create different paragraphs. + + // Add an English node. + $this->drupalGet('node/add/paragraphed_content_demo'); + $this->drupalPostForm(NULL, NULL, t('Add Text')); + + $edit = [ + 'title[0][value]' => 'Title in english', + 'field_paragraphs_demo[0][subform][field_text_demo][0][value]' => 'Text in english', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and publish')); + + // Translate the node to French. + $node = $this->drupalGetNodeByTitle('Title in english'); + $this->drupalGet('node/' . $node->id() . '/translations/add/en/fr'); + + $edit = [ + 'title[0][value]' => 'Title in french', + 'field_paragraphs_demo[0][subform][field_text_demo][0][value]' => 'Text in french', + 'revision' => TRUE, + 'revision_log[0][value]' => 'french 1', + ]; + $this->drupalPostForm( + NULL, + $edit, + t('Save and keep published (this translation)') + ); + + // Check the english translation. + $this->drupalGet('node/' . $node->id()); + $this->assertText('Title in english'); + $this->assertText('Text in english'); + $this->assertNoText('Title in french'); + $this->assertNoText('Text in french'); + + // Check the french translation. + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('Title in french'); + $this->assertText('Text in french'); + $this->assertNoText('Title in english'); + $this->assertNoText('Text in english'); + + // Check the db + $select = \Drupal::database()->select('node__field_paragraphs_demo', 'n'); + $select->innerJoin('paragraphs_item', 'p', 'p.id = n.field_paragraphs_demo_target_id'); + $select->addField('p', 'langcode'); + $select->condition('n.entity_id', $node->id()); + $paragraph_langcodes = $select->execute()->fetchCol(); + + $this->assertEqual( + $paragraph_langcodes, + ['en', 'fr'], + 'Translated paragraphs are separate entities' + ); + + // 2. Update the paragraphs on each translation. + + // Try to edit the paragraphs, to see if the correct translation gets + // updated. Start with the english. + $this->drupalGet('node/' . $node->id() . '/edit'); + $edit = [ + 'field_paragraphs_demo[0][subform][field_text_demo][0][value]' => 'The updated english text', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + // Check if only the english node had its paragraph text updated, and that + // there has been no mixing-up of the paragraph entities. + $this->drupalGet('node/' . $node->id()); + $this->assertText('The updated english text'); + $this->assertNoText('Text in english'); + $this->assertNoText('Text in french'); + + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('Text in french'); + $this->assertNoText('Text in english'); + $this->assertNoText('The updated english text'); + + // Now repeat for the french. + $this->drupalGet('fr/node/' . $node->id() . '/edit'); + $edit = [ + 'field_paragraphs_demo[0][subform][field_text_demo][0][value]' => 'The updated french text', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + // Check if only the french node had its paragraph text updated, and that + // there has been no mixing-up of the paragraph entities. + $this->drupalGet('node/' . $node->id()); + $this->assertText('The updated english text'); + $this->assertNoText('Text in french'); + $this->assertNoText('The updated french text'); + + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('The updated french text'); + $this->assertNoText('Text in french'); + $this->assertNoText('The updated english text'); + + + // 3. Add different number of paragraphs on each translation. + + // Add 2 more paragraphs on the english node. + $this->drupalGet('node/' . $node->id() . '/edit'); + $this->drupalPostForm(NULL, NULL, t('Add Text')); + $this->drupalPostForm(NULL, NULL, t('Add Text')); + $edit = [ + 'field_paragraphs_demo[1][subform][field_text_demo][0][value]' => 'Second text in english', + 'field_paragraphs_demo[2][subform][field_text_demo][0][value]' => 'Third text in english', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + + // Confirm that the english node has the new paragraphs, and the french + // node is intact. + $this->drupalGet('node/' . $node->id()); + $this->assertText('The updated english text'); + $this->assertText('Second text in english'); + $this->assertText('Third text in english'); + $this->assertNoText('The updated french text'); + + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('The updated french text'); + $this->assertNoText('The updated english text'); + $this->assertNoText('Second text in english'); + $this->assertNoText('Third text in english'); + + // Repeat the same, this time adding 3 new paragraphs on the french node. + $this->drupalGet('fr/node/' . $node->id() . '/edit'); + $this->drupalPostForm(NULL, NULL, t('Add Text')); + $this->drupalPostForm(NULL, NULL, t('Add Text')); + $this->drupalPostForm(NULL, NULL, t('Add Text')); + $edit = [ + 'field_paragraphs_demo[1][subform][field_text_demo][0][value]' => 'Second text in french', + 'field_paragraphs_demo[2][subform][field_text_demo][0][value]' => 'Third text in french', + 'field_paragraphs_demo[3][subform][field_text_demo][0][value]' => 'Fourth text in french', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + + // Confirm that the french node has the new paragraphs, and the english + // node is intact. + $this->drupalGet('node/' . $node->id()); + $this->assertText('The updated english text'); + $this->assertText('Second text in english'); + $this->assertText('Third text in english'); + $this->assertNoText('The updated french text'); + $this->assertNoText('Second text in french'); + $this->assertNoText('Third text in french'); + $this->assertNoText('Fourth text in french'); + + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('The updated french text'); + $this->assertText('Second text in french'); + $this->assertText('Third text in french'); + $this->assertText('Fourth text in french'); + $this->assertNoText('The updated english text'); + $this->assertNoText('Second text in english'); + $this->assertNoText('Third text in english'); + + + + // 4. Remove paragraphs from each translation. + + // Delete one of the paragraphs on the english node, and confirm that the + // rest are intact, and also that the french node is intact. + $this->drupalGet('node/' . $node->id() . '/edit'); + + $this->assertNotNull($this->xpath('//*[@name="field_paragraphs_demo_1_remove"]')); + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_1_remove'); + $this->assertNotNull($this->xpath('//*[@name="field_paragraphs_demo_1_confirm_remove"]')); + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_1_confirm_remove'); + $this->drupalPostForm(NULL, NULL, t('Save and keep published (this translation)')); + + $this->drupalGet('node/' . $node->id()); + $this->assertText('The updated english text'); + $this->assertNoText('Second text in english'); // The deleted paragraph + $this->assertText('Third text in english'); + $this->assertNoText('The updated french text'); + $this->assertNoText('Second text in french'); + $this->assertNoText('Third text in french'); + $this->assertNoText('Fourth text in french'); + + $this->drupalGet('fr/node/' . $node->id()); + $this->assertNoText('The updated english text'); + $this->assertNoText('Second text in english'); + $this->assertNoText('Third text in english'); + $this->assertText('The updated french text'); + $this->assertText('Second text in french'); + $this->assertText('Third text in french'); + $this->assertText('Fourth text in french'); + + // Repeat the same for the french node, deleting 2 paragraphs now. + $this->drupalGet('fr/node/' . $node->id() . '/edit'); + + $this->assertNotNull($this->xpath('//*[@name="field_paragraphs_demo_1_remove"]')); + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_1_remove'); + $this->assertNotNull($this->xpath('//*[@name="field_paragraphs_demo_1_confirm_remove"]')); + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_1_confirm_remove'); + $this->assertNotNull($this->xpath('//*[@name="field_paragraphs_demo_3_remove"]')); + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_3_remove'); + $this->assertNotNull($this->xpath('//*[@name="field_paragraphs_demo_3_confirm_remove"]')); + $this->drupalPostAjaxForm(NULL, [], 'field_paragraphs_demo_3_confirm_remove'); + $this->drupalPostForm(NULL, NULL, t('Save and keep published (this translation)')); + + $this->drupalGet('fr/node/' . $node->id()); + $this->assertNoText('The updated english text'); + $this->assertNoText('Second text in english'); + $this->assertNoText('Third text in english'); + $this->assertText('The updated french text'); + $this->assertNoText('Second text in french'); // The deleted paragraph + $this->assertText('Third text in french'); + $this->assertNoText('Fourth text in french'); // The other deleted paragraph + + $this->drupalGet('node/' . $node->id()); + $this->assertText('The updated english text'); + $this->assertNoText('Second text in english'); // The previously deleted paragraph + $this->assertText('Third text in english'); + $this->assertNoText('The updated french text'); + $this->assertNoText('Second text in french'); + $this->assertNoText('Third text in french'); + $this->assertNoText('Fourth text in french'); + + + // 5. Reorder the paragraphs on each translation. + + // Reminder: This is our current content + // + // - English node + // -- The updated english text + // -- Third text in english + // + // - French node + // -- The updated french text + // -- Third text in french + + + // First check the english node, that "The updated english text" is indeed + // before the "Third text in english", as it has been set on the previous + // steps. + $this->drupalGet('node/' . $node->id()); + $regex = '/The updated english text.*Third text in english/s'; + $this->assertPattern($regex); + + $this->drupalGet('fr/node/' . $node->id()); + $regex = '/The updated french text.*Third text in french/s'; + $this->assertPattern($regex); + + // Reorder the paragraphs in the english node, and check if it applied + // correctly. Check also that the french node is intact. + $this->drupalGet('node/' . $node->id() . '/edit'); + $edit = [ + 'field_paragraphs_demo[0][_weight]' => 2, + 'field_paragraphs_demo[1][_weight]' => -2, + ]; + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + + $this->drupalGet('node/' . $node->id()); + $regex = '/Third text in english.*The updated english text/s'; + $this->assertPattern($regex); + + $this->drupalGet('fr/node/' . $node->id()); + $regex = '/The updated french text.*Third text in french/s'; + $this->assertPattern($regex); + + // And now reorder the french node, and then confirm that the new order + // applied correctly, and that the english node is intact. + $this->drupalGet('fr/node/' . $node->id() . '/edit'); + $edit = [ + 'field_paragraphs_demo[0][_weight]' => 2, + 'field_paragraphs_demo[1][_weight]' => -2, + ]; + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + + $this->drupalGet('fr/node/' . $node->id()); + $regex = '/Third text in french.*The updated french text/s'; + $this->assertPattern($regex); + + $this->drupalGet('node/' . $node->id()); + $regex = '/Third text in english.*The updated english text/s'; + $this->assertPattern($regex); + } + + public function testParagraphsMultilingualFieldTranslationNested() { + // Edit widget to classic. + $this->drupalGet('/admin/structure/types/manage/paragraphed_content_demo/form-display'); + $this->drupalPostForm(NULL, array('fields[field_paragraphs_demo][type]' => 'entity_reference_paragraphs'), t('Save')); + + // 1. Translate node and create different paragraphs. + + // Add an English node. + $this->drupalGet('node/add/paragraphed_content_demo'); + $this->drupalPostForm(NULL, NULL, t('Add Nested Paragraph'), array(), array(), ''); // Add a "nested paragraph" + $this->drupalPostAjaxForm(NULL, NULL, 'field_paragraphs_demo_0_subform_field_paragraphs_demo_text_add_more'); // Add a "text paragraph" within the nested + $edit = [ + 'title[0][value]' => 'Title in english', + 'field_paragraphs_demo[0][subform][field_paragraphs_demo][0][subform][field_text_demo][0][value]' => 'Text in english', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and publish')); + + // Translate the node to French. + $node = $this->drupalGetNodeByTitle('Title in english'); + $this->drupalGet('node/' . $node->id() . '/translations/add/en/fr'); + + $edit = [ + 'title[0][value]' => 'Title in french', + 'field_paragraphs_demo[0][subform][field_paragraphs_demo][0][subform][field_text_demo][0][value]' => 'Text in french', + ]; + $this->drupalPostForm( + NULL, + $edit, + t('Save and keep published (this translation)') + ); + + // Check the english translation. + $this->drupalGet('node/' . $node->id()); + $this->assertText('Title in english'); + $this->assertText('Text in english'); + $this->assertNoText('Title in french'); + $this->assertNoText('Text in french'); + + // Check the french translation. + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('Title in french'); + $this->assertText('Text in french'); + $this->assertNoText('Title in english'); + $this->assertNoText('Text in english'); + + + // 2. Update the paragraphs. + + // Try to edit the paragraphs, to see if the correct translation gets + // updated. Start with the english. + $this->drupalGet('node/' . $node->id() . '/edit'); + $edit = [ + 'field_paragraphs_demo[0][subform][field_paragraphs_demo][0][subform][field_text_demo][0][value]' => 'The updated english text', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + // Check if only the english node had its paragraph text updated, and that + // there has been no mixing-up of the paragraph entities. + $this->drupalGet('node/' . $node->id()); + $this->assertText('The updated english text'); + $this->assertNoText('Text in english'); + $this->assertNoText('Text in french'); + + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('Text in french'); + $this->assertNoText('Text in english'); + $this->assertNoText('The updated english text'); + + // 3. Add different number of paragraphs on one translation. + + // Add one more paragraph on the english node. + $this->drupalGet('node/' . $node->id() . '/edit'); + $this->drupalPostAjaxForm(NULL, NULL, 'field_paragraphs_demo_0_subform_field_paragraphs_demo_text_add_more'); // Add one more "text paragraph" within the nested + $edit = [ + 'field_paragraphs_demo[0][subform][field_paragraphs_demo][1][subform][field_text_demo][0][value]' => 'New english text', + ]; + $this->drupalPostForm( + NULL, + $edit, + t('Save and keep published (this translation)') + ); + + // Confirm that the english node has the new paragraphs, and the french + // node is intact. + $this->drupalGet('node/' . $node->id()); + $this->assertText('The updated english text'); + $this->assertText('New english text'); + $this->assertNoText('Text in french'); + + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('Text in french'); + $this->assertNoText('The updated english text'); + $this->assertNoText('New english text'); + } + + public function testParagraphsMultilingualFieldDeleteTranslation() { + // Edit widget to classic. + $this->drupalGet('/admin/structure/types/manage/paragraphed_content_demo/form-display'); + $this->drupalPostForm(NULL, array('fields[field_paragraphs_demo][type]' => 'entity_reference_paragraphs'), t('Save')); + + // 1. Translate node and create different paragraphs. Delete the translation + // and check if the original is intact. + + // Add an English node. + $this->drupalGet('node/add/paragraphed_content_demo'); + $this->drupalPostForm(NULL, NULL, t('Add Nested Paragraph'), array(), array(), ''); // Add a "nested paragraph" + $this->drupalPostAjaxForm(NULL, NULL, 'field_paragraphs_demo_0_subform_field_paragraphs_demo_text_add_more'); // Add a "text paragraph" within the nested + $edit = [ + 'title[0][value]' => 'Title in english', + 'field_paragraphs_demo[0][subform][field_paragraphs_demo][0][subform][field_text_demo][0][value]' => 'Text in english', + ]; + $this->drupalPostForm(NULL, $edit, t('Save and publish')); + + // Translate the node to French. + $node = $this->drupalGetNodeByTitle('Title in english'); + $this->drupalGet('node/' . $node->id() . '/translations/add/en/fr'); + + $edit = [ + 'title[0][value]' => 'Title in french', + 'field_paragraphs_demo[0][subform][field_paragraphs_demo][0][subform][field_text_demo][0][value]' => 'Text in french', + ]; + $this->drupalPostForm( + NULL, + $edit, + t('Save and keep published (this translation)') + ); + + // Check the english translation. + $this->drupalGet('node/' . $node->id()); + $this->assertText('Title in english'); + $this->assertText('Text in english'); + $this->assertNoText('Title in french'); + $this->assertNoText('Text in french'); + + // Check the french translation. + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('Title in french'); + $this->assertText('Text in french'); + $this->assertNoText('Title in english'); + $this->assertNoText('Text in english'); + + // Now delete the french translation. + $this->drupalGet('fr/node/' . $node->id() . '/delete'); + $this->drupalPostForm(NULL, NULL, t('Delete French translation')); + + // Check the english translation. + $this->drupalGet('node/' . $node->id()); + $this->assertText('Title in english'); + $this->assertText('Text in english'); + $this->assertNoText('Title in french'); + $this->assertNoText('Text in french'); + } +} diff --git a/src/Tests/Classic/ParagraphsConfigTest.php b/src/Tests/Classic/ParagraphsConfigTest.php index 6494543..f1783f2 100644 --- a/src/Tests/Classic/ParagraphsConfigTest.php +++ b/src/Tests/Classic/ParagraphsConfigTest.php @@ -65,74 +65,6 @@ class ParagraphsConfigTest extends ParagraphsTestBase { } /** - * Tests content translation form translatability constraints messages. - */ - public function testContentTranslationForm() { - $this->loginAsAdmin([ - 'administer languages', - 'administer content translation', - 'create content translations', - 'translate any entity', - ]); - - // Check warning message is displayed. - $this->drupalGet('admin/config/regional/content-language'); - $this->assertText('(* unsupported) Paragraphs fields do not support translation.'); - - $this->addParagraphedContentType('paragraphed_test', 'paragraphs_field', 'entity_reference_paragraphs'); - - // Check error message is not displayed. - $this->drupalGet('admin/config/regional/content-language'); - $this->assertText('(* unsupported) Paragraphs fields do not support translation.'); - $this->assertNoRaw('
drupalGet('admin/structure/types/manage/paragraphed_test/fields/node.paragraphed_test.paragraphs_field'); - $this->assertText('Paragraphs fields do not support translation.'); - - // Make the paragraphs field translatable. - $edit = [ - 'entity_types[node]' => TRUE, - 'settings[node][paragraphed_test][translatable]' => TRUE, - 'settings[node][paragraphed_test][fields][paragraphs_field]' => TRUE, - ]; - $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration')); - - // Check content type field management error. - $this->drupalGet('admin/structure/types/manage/paragraphed_test/fields/node.paragraphed_test.paragraphs_field'); - $this->assertText('Paragraphs fields do not support translation.'); - $this->assertRaw('
loginAsAdmin([ - 'administer languages', - 'administer content translation', - 'create content translations', - 'translate any entity', - ]); - - // Check warning message is displayed. - $this->drupalGet('admin/config/regional/content-language'); - $this->assertText('(* unsupported) Paragraphs fields do not support translation.'); - - $this->addParagraphedContentType('paragraphed_test'); - // Check error message is not displayed. - $this->drupalGet('admin/config/regional/content-language'); - $this->assertText('(* unsupported) Paragraphs fields do not support translation.'); - $this->assertNoRaw('
drupalGet('admin/structure/types/manage/paragraphed_test/fields/node.paragraphed_test.field_paragraphs'); - $this->assertText('Paragraphs fields do not support translation.'); - - // Make the paragraphs field translatable. - $edit = [ - 'entity_types[node]' => TRUE, - 'settings[node][paragraphed_test][translatable]' => TRUE, - 'settings[node][paragraphed_test][fields][field_paragraphs]' => TRUE, - ]; - $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration')); - - // Check content type field management error. - $this->drupalGet('admin/structure/types/manage/paragraphed_test/fields/node.paragraphed_test.field_paragraphs'); - $this->assertText('Paragraphs fields do not support translation.'); - $this->assertRaw('