You are here

protected function LayoutParagraphsWidget::formMultipleElements in Layout Paragraphs 1.0.x

Builds the main widget form array container/wrapper.

Form elements for individual items are built by formElement().

Overrides WidgetBase::formMultipleElements

File

src/Plugin/Field/FieldWidget/LayoutParagraphsWidget.php, line 498

Class

LayoutParagraphsWidget
Entity Reference with Layout field widget.

Namespace

Drupal\layout_paragraphs\Plugin\Field\FieldWidget

Code

protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
  $parents = $form['#parents'];
  $widget_state = static::getWidgetState($parents, $this->fieldName, $form_state);
  $this->wrapperId = trim(Html::getId(implode('-', $parents) . '-' . $this->fieldName . '-wrapper'), '-');
  $this->itemFormWrapperId = trim(Html::getId(implode('-', $parents) . '-' . $this->fieldName . '-form'), '-');
  $target_bundles = array_keys($this
    ->getAllowedTypes());
  $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'] = [];
    $widget_state['open_form'] = FALSE;
    $widget_state['remove_item'] = FALSE;

    /** @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,
        ];
      }
    }
  }

  // Handle asymmetric translation if field is translatable
  // by duplicating items for enabled languages.
  if ($items
    ->getFieldDefinition()
    ->isTranslatable()) {
    $langcode = $this->languageManager
      ->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
      ->getId();
    foreach ($widget_state['items'] as $delta => $item) {
      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;
    }
  }
  static::setWidgetState($parents, $this->fieldName, $form_state, $widget_state);
  $elements = [
    '#field_name' => $this->fieldName,
    '#required' => $this->fieldDefinition
      ->isRequired(),
    '#title' => $title,
    '#description' => $description,
    '#attributes' => [
      'class' => [
        'layout-paragraphs-field',
      ],
    ],
    '#type' => 'fieldset',
    '#parents' => $form['#parents'],
    '#id' => $this->wrapperId,
  ];
  for ($delta = 0; $delta < $widget_state['items_count']; $delta++) {
    $elements[$delta] = $this
      ->formSingleElement($items, $delta, [], $form, $form_state);
  }
  $elements['#after_build'][] = [
    $this,
    'buildLayouts',
  ];

  // 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, 'getIconUrl')) {
        $path = $type
          ->getIconUrl();
      }
      $options[$bundle_id] = $bundle_info[$bundle_id]['label'];
      $types[$has_layout ? 'layout' : 'content'][] = [
        'id' => $bundle_id,
        'name' => $bundle_info[$bundle_id]['label'],
        'image' => $path,
        'title' => $this
          ->t('Create new @name', [
          '@name' => $bundle_info[$bundle_id]['label'],
        ]),
      ];
    }
    $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',
        ],
      ],
      '#element_validate' => [
        [
          $this,
          'newItemValidate',
        ],
      ],
      '#limit_validation_errors' => [
        array_merge($parents, [
          $this->fieldName,
          'add_more',
        ]),
      ],
      '#attributes' => [
        'class' => [
          'layout-paragraphs-add-item',
        ],
      ],
      '#ajax' => [
        'callback' => [
          $this,
          'editItemAjax',
        ],
      ],
      '#name' => trim(implode('_', $parents) . '_' . $this->fieldName . '_add_item', '_'),
      '#element_parents' => $parents,
    ];

    // When adding a new element, the widget needs a way to track
    // (a) where in the DOM the new element should be added, and
    // (b) which method to use the insert the new element
    // (i.e. before, after, append).
    $elements['add_more']['actions']['dom_id'] = [
      '#type' => 'hidden',
      '#attributes' => [
        'class' => [
          'dom-id',
        ],
      ],
    ];
    $elements['add_more']['actions']['insert_method'] = [
      '#type' => 'hidden',
      '#attributes' => [
        'class' => [
          'insert-method',
        ],
      ],
    ];

    // 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">
            <input type="text" placeholder="{{ search_text }}" />
          </div>
          <div class="layout-paragraphs-add-more-menu__group">
            {% if types.layout %}
            <div class="layout-paragraphs-add-more-menu__group--layout">
            {% endif %}
            {% for type in types.layout %}
              <div class="layout-paragraphs-add-more-menu__item paragraph-type-{{type.id}} layout-paragraph">
                <a data-type="{{ type.id }}" href="#{{ type.id }}" title="{{ type.title }}">
                {% if type.image %}
                <img src="{{ type.image }}" alt ="" />
                {% endif %}
                <div>{{ type.name }}</div>
                </a>
              </div>
            {% endfor %}
            {% if types.layout %}
            </div>
            {% endif %}
            {% if types.content %}
            <div class="layout-paragraphs-add-more-menu__group--content">
            {% endif %}
            {% for type in types.content %}
              <div class="layout-paragraphs-add-more-menu__item paragraph-type-{{type.id}}">
                <a data-type="{{ type.id }}" href="#{{ type.id }}" title="{{ type.title }}">
                {% if type.image %}
                <img src="{{ type.image }}" alt ="" />
                {% endif %}
                <div>{{ type.name }}</div>
                </a>
              </div>
            {% endfor %}
            {% if types.content %}
            </div>
            {% endif %}
          </div>
        </div>',
      '#context' => [
        'types' => $types,
        'search_text' => $this
          ->t('Search'),
      ],
    ];
  }
  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,
      ],
    ];
  }

  // Add the paragraph edit form if editing.
  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'),
    '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>',
    ],
    'items' => [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'layout-paragraphs-disabled-items__items',
        ],
      ],
    ],
  ];

  // Pass widget instance settings to JS.
  $elements['#attached']['drupalSettings']['layoutParagraphsWidgets'][$this->wrapperId] = [
    'wrapperId' => $this->wrapperId,
    'maxDepth' => $this
      ->getSetting('nesting_depth'),
    'requireLayouts' => $this
      ->getSetting('require_layouts'),
    'isTranslating' => $elements["add_more"]["actions"]["#isTranslating"] ?? NULL,
    'cardinality' => $this->fieldDefinition
      ->getFieldStorageDefinition()
      ->getCardinality(),
    'itemsCount' => $this
      ->activeItemsCount($widget_state['items']),
  ];

  // Add layout_paragraphs_widget library.
  $elements['#attached']['library'][] = 'layout_paragraphs/layout_paragraphs_widget';
  return $elements;
}