From c50917a7c82ebe64c856bb0898720c610d1f76ff Mon Sep 17 00:00:00 2001 From: itamair <itamair@1179076.no-reply.drupal.org> Date: Sun, 28 Jun 2020 13:00:54 +0200 Subject: [PATCH] Issue #3154765 by itamair: Translations support --- js/layout-paragraphs-widget.js | 2 +- .../FieldWidget/LayoutParagraphsWidget.php | 1112 +++++++++++------ 2 files changed, 751 insertions(+), 363 deletions(-) diff --git a/js/layout-paragraphs-widget.js b/js/layout-paragraphs-widget.js index 325507ef..aea0cf3b 100644 --- a/js/layout-paragraphs-widget.js +++ b/js/layout-paragraphs-widget.js @@ -578,7 +578,7 @@ }); }); /** - * Click hanlders for "Add New Section" buttons. + * Click handlers for "Add New Section" buttons. */ $(".layout-paragraphs-field", context) .once("layout-paragraphs-add-section") diff --git a/src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php b/src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php index cd908880..94d1b7c4 100644 --- a/src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php +++ b/src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php @@ -2,6 +2,7 @@ namespace Drupal\layout_paragraphs\Plugin\Field\FieldWidget; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -16,6 +17,7 @@ use Drupal\Core\Render\Renderer; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Plugin\PluginWithFormsInterface; +use Drupal\Core\Plugin\PluginFormFactoryInterface; use Drupal\Core\Layout\LayoutInterface; use Drupal\Core\Form\SubformState; use Drupal\Component\Utility\NestedArray; @@ -72,6 +74,13 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi */ protected $layoutPluginManager; + /** + * The plugin form manager. + * + * @var \Drupal\Core\Plugin\PluginFormFactoryInterface + */ + protected $pluginFormFactory; + /** * The entity that contains this field. * @@ -156,6 +165,8 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi * The entity type bundle info. * @param \Drupal\Core\Layout\LayoutPluginManager $layout_plugin_manager * Core layout plugin manager service. + * @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager + * The plugin form manager. * @param \Drupal\Core\Language\LanguageManager $language_manager * Core language manager service. * @param \Drupal\Core\Session\AccountProxyInterface $current_user @@ -173,6 +184,7 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, LayoutPluginManager $layout_plugin_manager, + PluginFormFactoryInterface $plugin_form_manager, LanguageManager $language_manager, AccountProxyInterface $current_user, EntityDisplayRepositoryInterface $entity_display_repository) { @@ -183,6 +195,7 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $this->entityTypeManager = $entity_type_manager; $this->entityTypeBundleInfo = $entity_type_bundle_info; $this->layoutPluginManager = $layout_plugin_manager; + $this->pluginFormFactory = $plugin_form_manager; $this->fieldName = $this->fieldDefinition->getName(); $this->languageManager = $language_manager; $this->currentUser = $current_user; @@ -203,12 +216,170 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $container->get('entity_type.manager'), $container->get('entity_type.bundle.info'), $container->get('plugin.manager.core.layout'), + $container->get('plugin_form.factory'), $container->get('language_manager'), $container->get('current_user'), $container->get('entity_display.repository') ); } + /** + * Builds the widget form array for an individual item. + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + + $parents = $form['#parents']; + $widget_state = static::getWidgetState($parents, $this->fieldName, $form_state); + if (isset($widget_state['items'][intval($delta)])) { + $widget_state_item = $widget_state['items'][intval($delta)]; + } + else { + return []; + } + + if (!isset($widget_state_item['entity'])) { + return []; + } + + if (isset($widget_state_item['is_new'])) { + return []; + } + + /** @var \Drupal\paragraphs\ParagraphInterface $entity */ + $entity = $widget_state_item['entity']; + $layout_settings = $this->getLayoutSettings($entity); + $layout = $layout_settings['layout'] ?? ''; + $config = $layout_settings['config'] ?? []; + + // These fields are manipulated via JS and interacting with the DOM. + // We have to check the submitted form for their values. + $region = $this->extractInput($form, $form_state, $delta, 'region', $layout_settings['region']); + $this->setLayoutSetting($widget_state['items'][$delta]['entity'], 'region', $region); + $parent_uuid = $this->extractInput($form, $form_state, $delta, 'parent_uuid', $layout_settings['parent_uuid']); + $this->setLayoutSetting($widget_state['items'][$delta]['entity'], 'parent_uuid', $parent_uuid); + $weight = $this->extractInput($form, $form_state, $delta, '_weight', $widget_state_item['weight']); + + $layout_instance = $layout + ? $this->layoutPluginManager->createInstance($layout, $config) + : FALSE; + + // Build the preview and render it in the form. + $preview = []; + if (isset($entity)) { + $preview_view_mode = $this->getSetting('preview_view_mode'); + $view_builder = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId()); + $preview = $view_builder->view($entity, $preview_view_mode); + $preview['#cache']['max-age'] = 0; + $preview['#attributes']['class'][] = Html::cleanCssIdentifier($entity->uuid() . '-preview'); + } + + $element = [ + '#widget_item' => TRUE, + '#type' => 'container', + '#delta' => $delta, + '#entity' => $entity, + '#layout' => $layout, + '#region' => $region, + '#parent_uuid' => $parent_uuid, + '#weight' => $weight, + '#attributes' => [ + 'class' => [ + 'layout-paragraphs-item', + 'paragraph-' . $entity->uuid(), + ], + 'id' => [ + $this->wrapperId . '--item-' . $delta, + ], + ], + 'preview' => $preview, + 'region' => [ + '#type' => 'hidden', + '#attributes' => ['class' => ['layout-paragraphs-region']], + '#default_value' => $region, + ], + // Used by DOM to set parent uuids for nested items. + 'uuid' => [ + '#type' => 'hidden', + '#attributes' => ['class' => ['layout-paragraphs-uuid']], + '#value' => $entity->uuid(), + // Must be at top for JS to work correctly. + '#weight' => -999, + ], + 'parent_uuid' => [ + '#type' => 'hidden', + '#attributes' => ['class' => ['layout-paragraphs-parent-uuid']], + '#default_value' => $parent_uuid, + ], + 'entity' => [ + '#type' => 'value', + '#value' => $entity, + ], + 'toggle_button' => $this->allowReferenceChanges() ? $this->toggleButton('layout-paragraphs-parent-uuid') : [], + // Edit and remove button. + 'actions' => [ + '#type' => 'container', + '#weight' => -1000, + '#attributes' => ['class' => ['layout-paragraphs-actions']], + 'edit' => [ + '#type' => 'submit', + '#name' => 'edit_' . $this->wrapperId . '_' . $delta, + '#value' => $this->t('Edit'), + '#attributes' => ['class' => ['layout-paragraphs-edit']], + '#limit_validation_errors' => [], + '#submit' => [[$this, 'editItemSubmit']], + '#delta' => $delta, + '#ajax' => [ + 'callback' => [$this, 'editItemAjax'], + 'progress' => 'none', + ], + '#element_parents' => $parents, + ], + 'remove' => [ + '#type' => 'submit', + '#name' => 'remove_' . $this->wrapperId . '_' . $delta, + '#value' => $this->t('Remove'), + '#attributes' => ['class' => ['layout-paragraphs-remove']], + '#limit_validation_errors' => [], + '#submit' => [[$this, 'removeItemSubmit']], + '#delta' => $delta, + '#ajax' => [ + 'callback' => [$this, 'removeItemAjax'], + 'progress' => 'none', + ], + '#element_parents' => $parents, + ], + ], + ]; + + // Nested elements for regions. + if ($layout_instance) { + $element['#layout_instance'] = $layout_instance; + $element['#attributes']['class'][] = 'layout-paragraphs-layout'; + foreach ($layout_instance->getPluginDefinition()->getRegionNames() as $region_name) { + $element['preview']['regions'][$region_name] = [ + '#attributes' => [ + 'class' => [ + 'layout-paragraphs-layout-region', + 'layout-paragraphs-layout-region--' . $region_name, + ], + ], + 'toggle_button' => $this->allowReferenceChanges() ? $this->toggleButton('layout-paragraphs-uuid') : [], + ]; + } + } + + // New items are rendered in layout but hidden. + // This way we can track their weights, region names, etc. + if (!empty($widget_state['items'][$delta]['is_new'])) { + $element['#is_new'] = TRUE; + } + else { + $element['#is_new'] = FALSE; + } + + return $element; + } + /** * Builds the main widget form array container/wrapper. * @@ -234,6 +405,11 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $title = $this->fieldDefinition->getLabel(); $description = FieldFilteredMarkup::create(\Drupal::token()->replace($this->fieldDefinition->getDescription())); + /** @var \Drupal\Core\Entity\ContentEntityInterface $host */ + $host = $items->getEntity(); + // Detect if we are translating. + $this->initIsTranslating($form_state, $host); + // Save items to widget state when the form first loads. if (!isset($widget_state['items'])) { $widget_state['items'] = []; @@ -242,6 +418,56 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** @var \Drupal\entity_reference_revisions\Plugin\Field\FieldType\EntityReferenceRevisionsItem $item */ foreach ($items as $delta => $item) { if ($paragraph = $item->entity) { + if ($item->entity instanceof ParagraphInterface) { + $langcode = $form_state->get('langcode'); + if (!$this->isTranslating) { + // Set the langcode if we are not translating. + $langcode_key = $item->entity->getEntityType()->getKey('langcode'); + if ($item->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 ($item->entity->hasTranslation($langcode)) { + $item->entity = $item->entity->getTranslation($langcode); + } + else { + $item->entity->set($langcode_key, $langcode); + } + } + } + else { + // Add translation if missing for the target language. + if (!$item->entity->hasTranslation($langcode)) { + // Get the selected translation of the paragraph entity. + $entity_langcode = $item->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. + // Fetching the translation without this check could lead valid + // scenario to have no paragraphs items in the source version of + // to an exception. + if ($item->entity->hasTranslation($source_langcode)) { + $entity = $item->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 ($item->entity->hasField('content_translation_source')) { + // Initialise the translation with source language values. + $item->entity->addTranslation($langcode, $entity->toArray()); + $translation = $item->entity->getTranslation($langcode); + $manager = \Drupal::service('content_translation.manager'); + $manager->getTranslationMetadata($translation) + ->setSource($item->entity->language()->getId()); + } + } + // If any paragraphs type is translatable do not switch. + if ($item->entity->hasField('content_translation_source')) { + // Switch the paragraph to the translation. + $item->entity = $item->entity->getTranslation($langcode); + } + } + } + $widget_state['items'][$delta] = [ 'entity' => $paragraph, 'weight' => $delta, @@ -260,6 +486,7 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi if (empty($item['entity']) || $item['entity']->get('langcode')->value == $langcode) { continue; } + /* @var \Drupal\Core\Entity\EntityInterface $duplicate */ $duplicate = $item['entity']->createDuplicate(); $duplicate->set('langcode', $langcode); $widget_state['items'][$delta]['entity'] = $duplicate; @@ -298,78 +525,84 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi ]; $elements['#id'] = $this->wrapperId; - // Button to add new section and other paragraphs. - $elements['add_more'] = [ - 'actions' => [ - '#attributes' => ['class' => ['js-hide']], - '#type' => 'container', - ], - ]; - $bundle_info = $this->entityTypeBundleInfo->getBundleInfo('paragraph'); - $options = []; - $types = [ - 'layout' => [], - 'content' => [], - ]; - $bundle_ids = $target_bundles; - $target_type = $items->getSetting('target_type'); - $definition = $this->entityTypeManager->getDefinition($target_type); - $storage = $this->entityTypeManager->getStorage($definition->getBundleEntityType()); - foreach ($bundle_ids as $bundle_id) { - $type = $storage->load($bundle_id); - $has_layout = count($this->getAvailableLayoutsByType($type)) > 0; - - $path = ''; - // Get the icon and pass to Javascript. - if (method_exists($type, 'getIconFile')) { - if ($icon = $type->getIconFile()) { - $path = $icon->url(); + // Add logic for new elements Add, if not in a translation context. + if ($this->allowReferenceChanges()) { + // Button to add new section and other paragraphs. + $elements['add_more'] = [ + 'actions' => [ + '#attributes' => ['class' => ['js-hide']], + '#type' => 'container', + ], + ]; + $bundle_info = $this->entityTypeBundleInfo->getBundleInfo('paragraph'); + $options = []; + $types = [ + 'layout' => [], + 'content' => [], + ]; + $bundle_ids = $target_bundles; + $target_type = $items->getSetting('target_type'); + $definition = $this->entityTypeManager->getDefinition($target_type); + $storage = $this->entityTypeManager->getStorage($definition->getBundleEntityType()); + foreach ($bundle_ids as $bundle_id) { + $type = $storage->load($bundle_id); + $has_layout = count($this->getAvailableLayoutsByType($type)) > 0; + + $path = ''; + // Get the icon and pass to Javascript. + if (method_exists($type, 'getIconFile')) { + if ($icon = $type->getIconFile()) { + $path = $icon->url(); + } } + $options[$bundle_id] = $bundle_info[$bundle_id]['label']; + $types[($has_layout ? 'layout' : 'content')][] = [ + 'id' => $bundle_id, + 'name' => $bundle_info[$bundle_id]['label'], + 'image' => $path, + ]; } - $options[$bundle_id] = $bundle_info[$bundle_id]['label']; - $types[($has_layout ? 'layout' : 'content')][] = [ - 'id' => $bundle_id, - 'name' => $bundle_info[$bundle_id]['label'], - 'image' => $path, + $elements['add_more']['actions']['type'] = [ + '#title' => $this->t('Choose type'), + '#type' => 'select', + '#options' => $options, + '#attributes' => ['class' => ['layout-paragraphs-item-type']], ]; - } - $elements['add_more']['actions']['type'] = [ - '#title' => $this->t('Choose type'), - '#type' => 'select', - '#options' => $options, - '#attributes' => ['class' => ['layout-paragraphs-item-type']], - ]; - $elements['add_more']['actions']['item'] = [ - '#type' => 'submit', - '#host' => $items->getEntity(), - '#value' => $this->t('Create New'), - '#submit' => [[$this, 'newItemSubmit']], - '#limit_validation_errors' => [array_merge($parents, [$this->fieldName, 'add_more'])], - '#attributes' => ['class' => ['layout-paragraphs-add-item']], - '#ajax' => [ - 'callback' => [$this, 'editItemAjax'], - ], - '#name' => implode('_', $parents) . '_add_item', - '#element_parents' => $parents, - ]; - // Add region and parent_delta hidden items only in this is a new entity. - // Prefix with underscore to prevent namespace collisions. - $elements['add_more']['actions']['_region'] = [ - '#type' => 'hidden', - '#attributes' => ['class' => ['layout-paragraphs-new-item-region']], - ]; - $elements['add_more']['actions']['_new_item_weight'] = [ - '#type' => 'hidden', - '#attributes' => ['class' => ['layout-paragraphs-new-item-weight']], - ]; - $elements['add_more']['actions']['_parent_uuid'] = [ - '#type' => 'hidden', - '#attributes' => ['class' => ['layout-paragraphs-new-item-parent-uuid']], - ]; - // Template for javascript behaviors. - $elements['add_more']['menu'] = [ - '#type' => 'inline_template', - '#template' => ' + $elements['add_more']['actions']['item'] = [ + '#type' => 'submit', + '#host' => $items->getEntity(), + '#value' => $this->t('Create New'), + '#submit' => [[$this, 'newItemSubmit']], + '#limit_validation_errors' => [array_merge($parents, [ + $this->fieldName, + 'add_more', + ]), + ], + '#attributes' => ['class' => ['layout-paragraphs-add-item']], + '#ajax' => [ + 'callback' => [$this, 'editItemAjax'], + ], + '#name' => implode('_', $parents) . '_add_item', + '#element_parents' => $parents, + ]; + // Add region and parent_delta hidden items only in this is a new entity. + // Prefix with underscore to prevent namespace collisions. + $elements['add_more']['actions']['_region'] = [ + '#type' => 'hidden', + '#attributes' => ['class' => ['layout-paragraphs-new-item-region']], + ]; + $elements['add_more']['actions']['_new_item_weight'] = [ + '#type' => 'hidden', + '#attributes' => ['class' => ['layout-paragraphs-new-item-weight']], + ]; + $elements['add_more']['actions']['_parent_uuid'] = [ + '#type' => 'hidden', + '#attributes' => ['class' => ['layout-paragraphs-new-item-parent-uuid']], + ]; + // Template for javascript behaviors. + $elements['add_more']['menu'] = [ + '#type' => 'inline_template', + '#template' => ' <div class="layout-paragraphs-add-more-menu hidden"> <h4 class="visually-hidden">Add Item</h4> <div class="layout-paragraphs-add-more-menu__search hidden"> @@ -407,210 +640,84 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi {% endfor %} {% if types.content %} </div> - {% endif %} - </div> - </div>', - '#context' => [ - 'types' => $types, - 'search_text' => $this->t('Search'), - ], - ]; - $elements['toggle_button'] = $this->toggleButton(); - if ($widget_state['open_form'] !== FALSE) { - $this->entityForm($elements, $form_state, $form); - } - - // Add remove confirmation form if we're removing. - if ($widget_state['remove_item'] !== FALSE) { - $this->removeForm($elements, $form_state, $form); - } - - // Container for disabled / orphaned items. - $elements['disabled'] = [ - '#type' => 'fieldset', - '#attributes' => ['class' => ['layout-paragraphs-disabled-items']], - '#weight' => 999, - '#title' => $this->t('Disabled Items'), - 'items' => [ - '#type' => 'container', - '#attributes' => [ - 'class' => [ - 'layout-paragraphs-disabled-items__items', - ], - ], - 'description' => [ - '#markup' => '<div class="layout-paragraphs-disabled-items__description">' . $this->t('Drop items here that you want to keep disabled / hidden, without removing them permanently.') . '</div>', - ], - ], - ]; - - $elements['#attached']['drupalSettings']['paragraphsLayoutWidget']['maxDepth'] = $this->getSetting('nesting_depth'); - $elements['#attached']['drupalSettings']['paragraphsLayoutWidget']['requireLayouts'] = $this->getSetting('require_layouts'); - $elements['#attached']['library'][] = 'layout_paragraphs/layout_paragraphs_widget'; - return $elements; - } - - /** - * Builds the widget form array for an individual item. - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - - $parents = $form['#parents']; - $widget_state = static::getWidgetState($parents, $this->fieldName, $form_state); - if (isset($widget_state['items'][intval($delta)])) { - $widget_state_item = $widget_state['items'][intval($delta)]; - } - else { - return []; - } - - if (!isset($widget_state_item['entity'])) { - return; - } - - if (isset($widget_state_item['is_new'])) { - return; - } - - /** @var \Drupal\paragraphs\ParagraphInterface $entity */ - $entity = $widget_state_item['entity']; - $layout_settings = $this->getLayoutSettings($entity); - $layout = $layout_settings['layout'] ?? ''; - $config = $layout_settings['config'] ?? []; - - // These fields are manipulated via JS and interacting with the DOM. - // We have to check the submitted form for their values. - $region = $this->extractInput($form, $form_state, $delta, 'region', $layout_settings['region']); - $this->setLayoutSetting($widget_state['items'][$delta]['entity'], 'region', $region); - $parent_uuid = $this->extractInput($form, $form_state, $delta, 'parent_uuid', $layout_settings['parent_uuid']); - $this->setLayoutSetting($widget_state['items'][$delta]['entity'], 'parent_uuid', $parent_uuid); - $weight = $this->extractInput($form, $form_state, $delta, '_weight', $widget_state_item['weight']); - - $layout_instance = $layout - ? $this->layoutPluginManager->createInstance($layout, $config) - : FALSE; - - // Build the preview and render it in the form. - $preview = []; - if (isset($entity)) { - $preview_view_mode = $this->getSetting('preview_view_mode'); - $view_builder = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId()); - $preview = $view_builder->view($entity, $preview_view_mode); - $preview['#cache']['max-age'] = 0; - $preview['#attributes']['class'][] = Html::cleanCssIdentifier($entity->uuid() . '-preview'); - } - - $element = [ - '#widget_item' => TRUE, - '#type' => 'container', - '#delta' => $delta, - '#entity' => $entity, - '#layout' => $layout, - '#region' => $region, - '#parent_uuid' => $parent_uuid, - '#weight' => $weight, - '#attributes' => [ - 'class' => [ - 'layout-paragraphs-item', - 'paragraph-' . $entity->uuid(), - ], - 'id' => [ - $this->wrapperId . '--item-' . $delta, - ], - ], - 'preview' => $preview, - 'region' => [ - '#type' => 'hidden', - '#attributes' => ['class' => ['layout-paragraphs-region']], - '#default_value' => $region, - ], - // Used by DOM to set parent uuids for nested items. - 'uuid' => [ - '#type' => 'hidden', - '#attributes' => ['class' => ['layout-paragraphs-uuid']], - '#value' => $entity->uuid(), - // Must be at top for JS to work correctly. - '#weight' => -999, - ], - 'parent_uuid' => [ - '#type' => 'hidden', - '#attributes' => ['class' => ['layout-paragraphs-parent-uuid']], - '#default_value' => $parent_uuid, - ], - 'entity' => [ - '#type' => 'value', - '#value' => $entity, - ], - 'toggle_button' => $this->toggleButton('layout-paragraphs-parent-uuid'), - // Edit and remove button. - 'actions' => [ + {% endif %} + </div> + </div>', + '#context' => [ + 'types' => $types, + 'search_text' => $this->t('Search'), + ], + ]; + $elements['toggle_button'] = $this->toggleButton(); + } + else { + // Add the #isTranslating attribute, if in a translation context. + $elements['is_translating_warning'] = [ + '#type' => "html_tag", + '#tag' => 'div', + '#value' => t("This is Translation Context (editing a version not in the original language). <b>No new Layout Sections and Paragraphs can be added</b>."), + '#weight' => -1100, + '#attributes' => [ + 'class' => ['is_translating_warning'], + ], + ]; + $elements['add_more'] = [ + 'actions' => [ + '#isTranslating' => TRUE, + ], + ]; + } + + if ($widget_state['open_form'] !== FALSE) { + $this->entityForm($elements, $form_state, $form); + } + + // Add remove confirmation form if we're removing. + if ($widget_state['remove_item'] !== FALSE) { + $this->removeForm($elements, $form_state, $form); + } + + // Container for disabled / orphaned items. + $elements['disabled'] = [ + '#type' => 'fieldset', + '#attributes' => ['class' => ['layout-paragraphs-disabled-items']], + '#weight' => 999, + '#title' => $this->t('Disabled Items'), + 'items' => [ '#type' => 'container', - '#weight' => -1000, - '#attributes' => ['class' => ['layout-paragraphs-actions']], - 'edit' => [ - '#type' => 'submit', - '#name' => 'edit_' . $this->wrapperId . '_' . $delta, - '#value' => $this->t('Edit'), - '#attributes' => ['class' => ['layout-paragraphs-edit']], - '#limit_validation_errors' => [], - '#submit' => [[$this, 'editItemSubmit']], - '#delta' => $delta, - '#ajax' => [ - 'callback' => [$this, 'editItemAjax'], - 'progress' => 'none', + '#attributes' => [ + 'class' => [ + 'layout-paragraphs-disabled-items__items', ], - '#element_parents' => $parents, ], - 'remove' => [ - '#type' => 'submit', - '#name' => 'remove_' . $this->wrapperId . '_' . $delta, - '#value' => $this->t('Remove'), - '#attributes' => ['class' => ['layout-paragraphs-remove']], - '#limit_validation_errors' => [ - array_merge($parents, [ - $this->fieldName, - $delta, - ]), - ], - '#limit_validation_errors' => [], - '#submit' => [[$this, 'removeItemSubmit']], - '#delta' => $delta, - '#ajax' => [ - 'callback' => [$this, 'removeItemAjax'], - 'progress' => 'none', - ], - '#element_parents' => $parents, + 'description' => [ + '#markup' => '<div class="layout-paragraphs-disabled-items__description">' . $this->t('Drop items here that you want to keep disabled / hidden, without removing them permanently.') . '</div>', ], ], ]; - // Nested elements for regions. - if ($layout_instance) { - $element['#layout_instance'] = $layout_instance; - $element['#attributes']['class'][] = 'layout-paragraphs-layout'; - foreach ($layout_instance->getPluginDefinition()->getRegionNames() as $region_name) { - $element['preview']['regions'][$region_name] = [ - '#attributes' => [ - 'class' => [ - 'layout-paragraphs-layout-region', - 'layout-paragraphs-layout-region--' . $region_name, - ], - ], - 'toggle_button' => $this->toggleButton('layout-paragraphs-uuid'), - ]; - } - } + // Pass specific paragraphsLayoutWidget settings to js. + $elements['#attached']['drupalSettings']['paragraphsLayoutWidget'] = [ + 'maxDepth' => $this->getSetting('nesting_depth'), + 'requireLayouts' => $this->getSetting('require_layouts'), + 'isTranslating' => $elements["add_more"]["actions"]["#isTranslating"] ?? NULL, + ]; + // Add layout_paragraphs_widget library. + $elements['#attached']['library'][] = 'layout_paragraphs/layout_paragraphs_widget'; + return $elements; + } - // New items are rendered in layout but hidden. - // This way we can track their weights, region names, etc. - if (!empty($widget_state['items'][$delta]['is_new'])) { - $element['#is_new'] = TRUE; - } - else { - $element['#is_new'] = FALSE; - } + /** + * {@inheritdoc} + */ + public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) { + $elements = parent::form($items, $form, $form_state, $get_delta); - return $element; + // Signal to content_translation that this field should be treated as + // multilingual and not be hidden, see + // \Drupal\content_translation\ContentTranslationHandler::entityFormSharedElements(). + $elements['#multilingual'] = TRUE; + return $elements; } /** @@ -775,6 +882,10 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** @var \Drupal\paragraphs\Entity\Paragraph $entity */ $entity = $widget_state['items'][$delta]['entity']; + // Set correct default language for the entity. + if ($this->isTranslating && $language = $form_state->get('langcode')) { + $entity = $entity->getTranslation($language); + } $display = EntityFormDisplay::collectRenderDisplay($entity, 'default'); $bundle_label = $entity->type->entity->label(); $element['entity_form'] = [ @@ -890,55 +1001,59 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi 'layout_selection', 'layout', ]); - $values = $form_state->getValues(); $updated_layout = $form_state->getValue($layout_select_parents) ?? $layout; if (!empty($updated_layout)) { - $updated_layout_instance = $this->layoutPluginManager->createInstance($updated_layout, $layout_plugin_config); - // If the user selects a new layout, - // we provide a way for them to choose - // what to do with items from regions - // that no longer exist. - if ($layout && $updated_layout != $layout) { - $move_items = []; - - $original_layout = $this->layoutPluginManager->createInstance($layout); - $original_definition = $original_layout->getPluginDefinition(); - $original_regions = $original_definition->getRegions(); - - $updated_layout_definition = $updated_layout_instance->getPluginDefinition(); - $updated_regions = $updated_layout_definition->getRegions(); - $updated_regions_options = []; - foreach ($updated_regions as $region_name => $region) { - $updated_regions_options[$region_name] = $region['label']; - } - $updated_regions_options['_disabled'] = $this->t('Disabled'); - foreach ($original_regions as $region_name => $region) { - if (!isset($updated_regions[$region_name]) && $this->hasChildren($entity, $widget_state['items'], $region_name)) { - $move_items[$region_name] = [ - '#type' => 'select', - '#wrapper_attributes' => ['class' => ['container-inline']], - '#title' => $this->t('Move items from the "@region" region to', ['@region' => $region['label']]), - '#options' => $updated_regions_options, + try { + $updated_layout_instance = $this->layoutPluginManager->createInstance($updated_layout, $layout_plugin_config); + // If the user selects a new layout, + // we provide a way for them to choose + // what to do with items from regions + // that no longer exist. + if ($layout && $updated_layout != $layout) { + $move_items = []; + + $original_layout = $this->layoutPluginManager->createInstance($layout); + $original_definition = $original_layout->getPluginDefinition(); + $original_regions = $original_definition->getRegions(); + + $updated_layout_definition = $updated_layout_instance->getPluginDefinition(); + $updated_regions = $updated_layout_definition->getRegions(); + $updated_regions_options = []; + foreach ($updated_regions as $region_name => $region) { + $updated_regions_options[$region_name] = $region['label']; + } + $updated_regions_options['_disabled'] = $this->t('Disabled'); + foreach ($original_regions as $region_name => $region) { + if (!isset($updated_regions[$region_name]) && $this->hasChildren($entity, $widget_state['items'], $region_name)) { + $move_items[$region_name] = [ + '#type' => 'select', + '#wrapper_attributes' => ['class' => ['container-inline']], + '#title' => $this->t('Move items from the "@region" region to', ['@region' => $region['label']]), + '#options' => $updated_regions_options, + ]; + } + } + if (count($move_items)) { + $element['entity_form']['layout_selection']['move_items'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Move orphaned items'), + '#description' => $this->t('The layout you selected has different regions than the previous one.'), + 'items' => $move_items, ]; } } - if (count($move_items)) { - $element['entity_form']['layout_selection']['move_items'] = [ - '#type' => 'fieldset', - '#title' => $this->t('Move orphaned items'), - '#description' => $this->t('The layout you selected has different regions than the previous one.'), - 'items' => $move_items, + if ($layout_plugin = $this->getLayoutPluginForm($updated_layout_instance)) { + $element['entity_form']['layout_plugin_form'] += [ + '#type' => 'details', + '#title' => $this->t('Layout Configuration'), + '#weight' => 999, ]; + $element['entity_form']['layout_plugin_form'] += $layout_plugin->buildConfigurationForm([], $form_state); } } - if ($layout_plugin = $this->getLayoutPluginForm($updated_layout_instance)) { - $element['entity_form']['layout_plugin_form'] += [ - '#type' => 'details', - '#title' => $this->t('Layout Configuration'), - '#weight' => 999, - ]; - $element['entity_form']['layout_plugin_form'] += $layout_plugin->buildConfigurationForm([], $form_state); + catch (\Exception $e) { + watchdog_exception('Layout Paragraphs, updating_layout', $e); } } } @@ -1018,6 +1133,42 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi ], ], ]; + + $hide_untranslatable_fields = $entity->isDefaultTranslationAffectedOnly(); + foreach (Element::children($element['entity_form']) as $field) { + if ($entity->hasField($field)) { + /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */ + $field_definition = $entity->get($field)->getFieldDefinition(); + $translatable = $entity->{$field}->getFieldDefinition()->isTranslatable(); + + // Do a check if we have to add a class to the form element. We need + // those classes (paragraphs-content and paragraphs-behavior) to show + // and hide elements, depending of the active perspective. + // We need them to filter out entity reference revisions fields that + // reference paragraphs, cause otherwise we have problems with showing + // and hiding the right fields in nested paragraphs. + $is_paragraph_field = FALSE; + if ($field_definition->getType() == 'entity_reference_revisions') { + // Check if we are referencing paragraphs. + if ($field_definition->getSetting('target_type') == 'paragraph') { + $is_paragraph_field = TRUE; + } + } + + if (!$translatable && $this->isTranslating && !$is_paragraph_field) { + if ($hide_untranslatable_fields) { + $element['entity_form'][$field]['#access'] = FALSE; + } + else { + $element['entity_form'][$field]['widget']['#after_build'][] = [ + static::class, + 'addTranslatabilityClue', + ]; + } + } + } + } + return $element; } /** @@ -1029,9 +1180,6 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi * The form_state object. * @param array $form * The form array. - * - * @return array - * The entity form element. */ public function removeForm(array &$element, FormStateInterface $form_state, array &$form) { @@ -1144,7 +1292,7 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $this->fieldName, $delta, ], - $element_name); + $element_name); } else { $element_path = array_merge($parents, [ @@ -1170,7 +1318,7 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $this->fieldName, $delta, ], - $element_name); + $element_name); } else { $element_path = array_merge($parents, [ @@ -1213,6 +1361,11 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Form submit handler - adds a new item and opens its edit form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ public function newItemSubmit(array $form, FormStateInterface $form_state) { @@ -1229,39 +1382,50 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $bundle_id = $form_state->getValue($element_parents); } - $entity_type = $this->entityTypeManager->getDefinition('paragraph'); - $bundle_key = $entity_type->getKey('bundle'); - /** @var \Drupal\paragraphs\ParagraphInterface $paragraph_entity */ - $paragraph_entity = $this->entityTypeManager->getStorage('paragraph')->create([ - $bundle_key => $bundle_id, - ]); - $paragraph_entity->setParentEntity($element['#host'], $this->fieldDefinition->getName()); - - $path = array_merge($parents, [ - $this->fieldDefinition->getName(), - 'add_more', - 'actions', - ]); - $this->setLayoutSettings($paragraph_entity, [ - 'region' => $form_state->getValue(array_merge($path, ['_region'])), - 'parent_uuid' => $form_state->getValue(array_merge($path, ['_parent_uuid'])), - ]); - $widget_state['items'][] = [ - 'entity' => $paragraph_entity, - 'weight' => $form_state->getValue(array_merge($path, ['_new_item_weight'])), - 'is_new' => TRUE, - ]; - $widget_state['open_form'] = $widget_state['items_count']; - $widget_state['items_count'] = count($widget_state['items']); + try { + $entity_type = $this->entityTypeManager->getDefinition('paragraph'); + $bundle_key = $entity_type->getKey('bundle'); + /** @var \Drupal\paragraphs\ParagraphInterface $paragraph_entity */ + $paragraph_entity = $this->entityTypeManager->getStorage('paragraph') + ->create([ + $bundle_key => $bundle_id, + ]); + $paragraph_entity->setParentEntity($element['#host'], $this->fieldDefinition->getName()); + + $path = array_merge($parents, [ + $this->fieldDefinition->getName(), + 'add_more', + 'actions', + ]); + $this->setLayoutSettings($paragraph_entity, [ + 'region' => $form_state->getValue(array_merge($path, ['_region'])), + 'parent_uuid' => $form_state->getValue(array_merge($path, ['_parent_uuid'])), + ]); + $widget_state['items'][] = [ + 'entity' => $paragraph_entity, + 'weight' => $form_state->getValue(array_merge($path, ['_new_item_weight'])), + 'is_new' => TRUE, + ]; + $widget_state['open_form'] = $widget_state['items_count']; + $widget_state['items_count'] = count($widget_state['items']); - static::setWidgetState($parents, $this->fieldName, $form_state, $widget_state); - $form_state->setRebuild(); + static::setWidgetState($parents, $this->fieldName, $form_state, $widget_state); + $form_state->setRebuild(); + } + catch (\Exception $e) { + watchdog_exception('Layout Paragraphs, new Item Submit', $e); + } } /** * Form submit handler - opens the edit form for an existing item. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function editItemSubmit($form, $form_state) { + public function editItemSubmit(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); $parents = $element['#element_parents']; @@ -1276,8 +1440,13 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Form submit handler - opens confirm removal form for an item. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function removeItemSubmit($form, $form_state) { + public function removeItemSubmit(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); $parents = $element['#element_parents']; @@ -1292,8 +1461,13 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Form submit handler - removes/deletes an item. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function removeItemConfirmSubmit($form, $form_state) { + public function removeItemConfirmSubmit(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); $element_parents = $element['#parents']; @@ -1316,8 +1490,13 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Form submit handler - cancels item removal and closes confirmation form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function removeItemCancelSubmit($form, $form_state) { + public function removeItemCancelSubmit(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); $parents = $element['#element_parents']; @@ -1332,8 +1511,13 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Form submit handler - saves an item. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function saveItemSubmit($form, $form_state) { + public function saveItemSubmit(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); $parents = $element['#element_parents']; @@ -1342,52 +1526,69 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $item_array_parents = array_splice($element_array_parents, 0, -2); $item_form = NestedArray::getValue($form, $item_array_parents); + /* @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $display */ $display = $item_form['#display']; $widget_state = static::getWidgetState($parents, $this->fieldName, $form_state); // Remove is_new flag since we're saving the entity. unset($widget_state['items'][$delta]['is_new']); - /** @var \Drupal\paragraphs\ParagraphInterface $paragraph_entity */ - $paragraph_entity = $widget_state['items'][$delta]['entity']; + /** @var \Drupal\paragraphs\ParagraphInterface $paragraph */ + $paragraph = $widget_state['items'][$delta]['entity']; + + // Set correct default language for the entity. + if ($this->isTranslating && $language = $form_state->get('langcode')) { + $paragraph = $paragraph->getTranslation($language); + } // Save field values to entity. - $display->extractFormValues($paragraph_entity, $item_form, $form_state); + $display->extractFormValues($paragraph, $item_form, $form_state); // Submit behavior forms. - $paragraphs_type = $paragraph_entity->getParagraphType(); + $paragraphs_type = $paragraph->getParagraphType(); if ($this->currentUser->hasPermission('edit behavior plugin settings')) { foreach ($paragraphs_type->getEnabledBehaviorPlugins() as $plugin_id => $plugin_values) { - if (!empty($item_form['behavior_plugins'][$plugin_id])) { + $plugin_form = isset($item_form['behavior_plugins']) ? $item_form['behavior_plugins'][$plugin_id] : []; + if (!empty($plugin_form) && !empty(Element::children($plugin_form))) { $subform_state = SubformState::createForSubform($item_form['behavior_plugins'][$plugin_id], $form_state->getCompleteForm(), $form_state); - $plugin_values->submitBehaviorForm($paragraph_entity, $item_form['behavior_plugins'][$plugin_id], $subform_state); + $plugin_values->submitBehaviorForm($paragraph, $item_form['behavior_plugins'][$plugin_id], $subform_state); } } } + // Save paragraph back to widget state. + $widget_state['items'][$delta]['entity'] = $paragraph; + // Save layout settings. if (!empty($item_form['layout_selection']['layout'])) { - $layout_settings = $this->getLayoutSettings($paragraph_entity); + $layout_settings = $this->getLayoutSettings($paragraph); $layout = $form_state->getValue($item_form['layout_selection']['layout']['#parents']); $layout_settings['layout'] = $layout; // Save layout config: if (!empty($item_form['layout_plugin_form'])) { - $layout_instance = $this->layoutPluginManager->createInstance($layout); - if ($this->getLayoutPluginForm($layout_instance)) { - $subform_state = SubformState::createForSubform($item_form['layout_plugin_form'], $form_state->getCompleteForm(), $form_state); - $layout_instance->submitConfigurationForm($item_form['layout_plugin_form'], $subform_state); - $layout_settings['config'] = $layout_instance->getConfiguration(); + try { + + $layout_instance = $this->layoutPluginManager->createInstance($layout); + if ($this->getLayoutPluginForm($layout_instance)) { + $subform_state = SubformState::createForSubform($item_form['layout_plugin_form'], $form_state->getCompleteForm(), $form_state); + $layout_instance->submitConfigurationForm($item_form['layout_plugin_form'], $subform_state); + $layout_settings['config'] = $layout_instance->getConfiguration(); + } + + $this->setLayoutSettings($paragraph, $layout_settings); + } + catch (\Exception $e) { + watchdog_exception('Layout Paragraphs, Layout Instance generation', $e); } } - $this->setLayoutSettings($paragraph_entity, $layout_settings); // Handle orphaned items. if (isset($item_form['layout_selection']['move_items'])) { $move_items = $form_state->getValue($item_form['layout_selection']['move_items']['#parents']); if ($move_items && isset($move_items['items'])) { - $parent_uuid = $paragraph_entity->uuid(); + $parent_uuid = $paragraph->uuid(); foreach ($move_items['items'] as $from_region => $to_region) { foreach ($widget_state['items'] as $delta => $item) { $layout_settings = $this->getLayoutSettings($item['entity']); @@ -1419,8 +1620,13 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Form submit handler - cancels editing an item and closes form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function cancelItemSubmit($form, $form_state) { + public function cancelItemSubmit(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); $parents = $element['#element_parents']; @@ -1438,6 +1644,14 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Ajax callback to return the entire ERL element. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The Ajax Response. */ public function elementAjax(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); @@ -1455,6 +1669,14 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Ajax callback to return the entire ERL element. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The Ajax Response. */ public function saveItemAjax(array $form, FormStateInterface $form_state) { $triggering_element = $form_state->getTriggeringElement(); @@ -1504,6 +1726,14 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Ajax callback to return the entire ERL element. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The Ajax Response. */ public function editItemAjax(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); @@ -1530,6 +1760,14 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Ajax callback to remove an item - launches confirmation dialog. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The Ajax Response. */ public function removeItemAjax(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); @@ -1557,9 +1795,16 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi return $response; } - /** * Ajax callback to remove an item - removes item from DOM. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The Ajax Response */ public function removeItemConfirmAjax(array $form, FormStateInterface $form_state) { $element = $form_state->getTriggeringElement(); @@ -1580,6 +1825,14 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Form submit handler - cancels item removal and closes confirmation form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The Ajax Response. */ public function closeDialogAjax(array $form, FormStateInterface $form_state) { @@ -1595,6 +1848,14 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Ajax callback to return a layout plugin configuration form. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse|array + * The Ajax Response. */ public function buildLayoutConfigurationFormAjax(array $form, FormStateInterface $form_state) { @@ -1650,12 +1911,14 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Search $items for children of $parent. * - * @param ParagraphInterface $parent + * @param \Drupal\Paragraphs\ParagraphInterface $parent * The parent paragraph. * @param array $items * An array of items to search. + * @param string $region + * The region string. * - * @return boolean + * @return bool * True if finds children. */ public function hasChildren(ParagraphInterface $parent, array $items, string $region = '') { @@ -1808,6 +2071,14 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Field instance settings form. + * + * @param array $form + * The Form element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The Form array. */ public function settingsForm(array $form, FormStateInterface $form_state) { $entity_type_id = $this->getFieldSetting('target_type'); @@ -1829,7 +2100,6 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi $form['require_layouts'] = [ '#type' => 'checkbox', '#title' => $this->t('Require paragraphs to be added inside a layout'), - '#default_value' => $this->getSetting('nesting_depth'), '#default_value' => $this->getSetting('require_layouts'), ]; return $form; @@ -1888,6 +2158,9 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi /** * Default settings for widget. + * + * @return array + * The default settings array. */ public static function defaultSettings() { $defaults = parent::defaultSettings(); @@ -1932,19 +2205,134 @@ class LayoutParagraphsWidget extends WidgetBase implements ContainerFactoryPlugi * @param \Drupal\Core\Layout\LayoutInterface $layout * The layout plugin. * - * @return \Drupal\Core\Plugin\PluginFormInterface + * @return \Drupal\Core\Plugin\PluginFormInterface|null * The plugin form for the layout. */ protected function getLayoutPluginForm(LayoutInterface $layout) { if ($layout instanceof PluginWithFormsInterface) { - return $this->pluginFormFactory->createInstance($layout, 'configure'); + try { + return $this->pluginFormFactory->createInstance($layout, 'configure'); + } + catch (\Exception $e) { + watchdog_exception('Erl, Layout Configuration', $e); + } } if ($layout instanceof PluginFormInterface) { return $layout; } - return FALSE; + return NULL; + } + + /** + * Determine if widget is in translation. + * + * Initializes $this->isTranslating. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param \Drupal\Core\Entity\ContentEntityInterface $host + * The host entity. + */ + protected function initIsTranslating(FormStateInterface $form_state, ContentEntityInterface $host) { + if ($this->isTranslating != NULL) { + return; + } + $this->isTranslating = FALSE; + if (!$host->isTranslatable()) { + return; + } + if (!$host->getEntityType()->hasKey('default_langcode')) { + return; + } + $default_langcode_key = $host->getEntityType()->getKey('default_langcode'); + if (!$host->hasField($default_langcode_key)) { + return; + } + + if (!empty($form_state->get('content_translation'))) { + // Adding a language through the ContentTranslationController. + $this->isTranslating = TRUE; + } + $langcode = $form_state->get('langcode'); + if ($host->hasTranslation($langcode) && $host->getTranslation($langcode)->get($default_langcode_key)->value == 0) { + // Editing a translation. + $this->isTranslating = TRUE; + } + } + + /** + * Checks if we can allow reference changes. + * + * @return bool + * TRUE if we can allow reference changes, otherwise FALSE. + */ + protected function allowReferenceChanges() { + return !$this->isTranslating; + } + + /** + * After-build callback for adding the translatability clue from the widget. + * + * ContentTranslationHandler::addTranslatabilityClue() adds an + * "(all languages)" suffix to the widget title, replicate that here. + * + * @param array $element + * The Form element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The element containing a translatability clue. + */ + public static function addTranslatabilityClue(array $element, FormStateInterface $form_state) { + static $suffix, $fapi_title_elements; + + // Widgets could have multiple elements with their own titles, so remove the + // suffix if it exists, do not recurse lower than this to avoid going into + // nested paragraphs or similar nested field types. + // Elements which can have a #title attribute according to FAPI Reference. + if (!isset($suffix)) { + $suffix = ' <span class="translation-entity-all-languages">(' . t('all languages') . ')</span>'; + $fapi_title_elements = array_flip([ + 'checkbox', + 'checkboxes', + 'date', + 'details', + 'fieldset', + 'file', + 'item', + 'password', + 'password_confirm', + 'radio', + 'radios', + 'select', + 'textarea', + 'textfield', + 'weight', + ]); + } + + // Update #title attribute for all elements that are allowed to have a + // #title attribute according to the Form API Reference. The reason for this + // check is because some elements have a #title attribute even though it is + // not rendered; for instance, field containers. + if (isset($element['#type']) && isset($fapi_title_elements[$element['#type']]) && isset($element['#title'])) { + $element['#title'] .= $suffix; + } + // If the current element does not have a (valid) title, try child elements. + elseif ($children = Element::children($element)) { + foreach ($children as $delta) { + $element[$delta] = static::addTranslatabilityClue($element[$delta], $form_state); + } + } + // If there are no children, fall back to the current #title attribute if it + // exists. + elseif (isset($element['#title'])) { + $element['#title'] .= $suffix; + } + return $element; } } -- GitLab