You are here

protected function EntityReferenceLayoutWidget::formMultipleElements in Entity Reference with Layout 8

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/EntityReferenceLayoutWidget.php, line 239

Class

EntityReferenceLayoutWidget
Entity Reference with Layout field widget.

Namespace

Drupal\entity_reference_layout\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 = Html::getId(implode('-', $parents) . $this->fieldName . '-wrapper');
  $this->itemFormWrapperId = Html::getId(implode('-', $parents) . $this->fieldName . '-form');
  $handler_settings = $items
    ->getSetting('handler_settings');
  $layout_bundles = $handler_settings['layout_bundles'] ?? [];
  $target_bundles = $handler_settings['target_bundles'] ?? [];
  if (!empty($handler_settings['negate'])) {
    $target_bundles_options = array_keys($handler_settings['target_bundles_drag_drop']);
    $target_bundles = array_diff($target_bundles_options, $target_bundles);
  }
  $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 (empty($widget_state['items'])) {
    $widget_state['items'] = [];
    foreach ($items as $delta => $item) {
      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' => $item->entity,
        'layout' => $item->layout,
        'config' => $item->config,
        'options' => $item->options,
        'new_region' => NULL,
        'parent_weight' => NULL,
      ];
    }
  }

  // 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;
      }
      $duplicate = $item['entity']
        ->createDuplicate();

      /** @var \Drupal\Core\Entity\EntityInterface $duplicate */
      $duplicate
        ->set('langcode', $langcode);
      $widget_state['items'][$delta]['entity'] = $duplicate;
    }
  }
  static::setWidgetState($parents, $this->fieldName, $form_state, $widget_state);
  $elements = parent::formMultipleElements($items, $form, $form_state);
  if (isset($elements['#prefix'])) {
    unset($elements['#prefix']);
  }
  if (isset($elements['#suffix'])) {
    unset($elements['#suffix']);
  }
  $elements += [
    '#title' => $title,
    '#description' => $description,
  ];
  $elements['#theme'] = 'entity_reference_layout_widget';
  $elements['#id'] = $this->wrapperId;

  // 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');
    foreach ($layout_bundles as $bundle_id) {
      $elements['add_more']['actions']['section'] = [
        '#type' => 'submit',
        '#bundle_id' => $bundle_id,
        '#host' => $items
          ->getEntity(),
        '#value' => $this
          ->t('Add @label', [
          '@label' => $bundle_info[$bundle_id]['label'],
        ]),
        '#modal_label' => $this
          ->t('Add new @label', [
          '@label' => $bundle_info[$bundle_id]['label'],
        ]),
        '#name' => implode('_', $parents) . '_add_' . $bundle_id,
        '#submit' => [
          [
            $this,
            'newItemSubmit',
          ],
        ],
        '#attributes' => [
          'class' => [
            'erl-add-section',
          ],
        ],
        '#limit_validation_errors' => [
          array_merge($parents, [
            $this->fieldName,
          ]),
        ],
        '#ajax' => [
          'callback' => [
            $this,
            'elementAjax',
          ],
          'wrapper' => $this->wrapperId,
        ],
        '#element_parents' => $parents,
      ];
    }

    // Add other paragraph types.
    $options = [];
    try {
      $types = [];
      $bundle_ids = array_diff($target_bundles, $layout_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);
        $path = '';

        // Get the icon and pass to Javascript.
        if (method_exists($type, 'getIconFile')) {
          try {

            /** @var \Drupal\file\FileInterface $icon */
            if ($icon = $type
              ->getIconFile()) {
              $path = $icon
                ->toUrl();
            }
          } catch (\Exception $e) {
            watchdog_exception('Erl, Paragraph Type Icon Generation', $e);
          }
        }
        $options[$bundle_id] = $bundle_info[$bundle_id]['label'];
        $types[] = [
          'id' => $bundle_id,
          'name' => $bundle_info[$bundle_id]['label'],
          'image' => $path,
        ];
      }
    } catch (\Exception $e) {
      watchdog_exception('Erl, add Paragraph Type', $e);
    }
    $elements['add_more']['actions']['type'] = [
      '#title' => $this
        ->t('Choose type'),
      '#type' => 'select',
      '#options' => $options,
      '#attributes' => [
        'class' => [
          'erl-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,
        ]),
      ],
      '#attributes' => [
        'class' => [
          'erl-add-item',
        ],
      ],
      '#ajax' => [
        'callback' => [
          $this,
          'elementAjax',
        ],
        'wrapper' => $this->wrapperId,
      ],
      '#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' => [
          'erl-new-item-region',
        ],
      ],
    ];
    $elements['add_more']['actions']['_parent_weight'] = [
      '#type' => 'hidden',
      '#attributes' => [
        'class' => [
          'erl-new-item-parent',
        ],
      ],
    ];

    // Template for javascript behaviors.
    $elements['add_more']['menu'] = [
      '#type' => 'inline_template',
      '#template' => '
        <div class="erl-add-more-menu hidden">
          <h4 class="visually-hidden">Add Item</h4>
          <div class="erl-add-more-menu__search hidden">
            <input type="text" placeholder="{{ search_text }}" />
          </div>
          <div class="erl-add-more-menu__group">
            {% for type in types %}
              <div class="erl-add-more-menu__item">
                <a data-type="{{ type.id }}" href="#{{ type.id }}">
                {% if type.image %}
                <img src="{{ type.image }}" alt ="" />
                {% endif %}
                <div>{{ type.name }}</div>
                </a>
              </div>
            {% endfor %}
          </div>
        </div>',
      '#context' => [
        'types' => $types,
        'search_text' => $this
          ->t('Search'),
      ],
    ];
  }
  else {

    // Add the #isTranslating attribute, if in a translation context.
    $elements['add_more'] = [
      'actions' => [
        '#isTranslating' => TRUE,
      ],
    ];
  }
  return $elements;
}