You are here

public function EntityReferenceLayoutWidget::formElement in Entity Reference with Layout 8

Builds the widget form array for an individual item.

Overrides WidgetInterface::formElement

File

src/Plugin/Field/FieldWidget/EntityReferenceLayoutWidget.php, line 497

Class

EntityReferenceLayoutWidget
Entity Reference with Layout field widget.

Namespace

Drupal\entity_reference_layout\Plugin\Field\FieldWidget

Code

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);
  $handler_settings = $items
    ->getSetting('handler_settings');
  $layout_bundles = $handler_settings['layout_bundles'] ?? [];
  if (empty($widget_state['items'][$delta]['entity'])) {
    return [];
  }

  // Flatten layouts array for use with radio buttons.
  $available_layouts = [];
  foreach ($handler_settings['allowed_layouts'] as $group) {
    foreach ($group as $layout_id => $layout_name) {
      $available_layouts[$layout_id] = $layout_name;
    }
  }

  /** @var \Drupal\paragraphs\ParagraphInterface $entity */
  $entity = !empty($widget_state['items'][$delta]) ? $widget_state['items'][$delta]['entity'] : NULL;
  $options = !empty($widget_state['items'][$delta]['options']) ? $widget_state['items'][$delta]['options'] : [];
  $config = !empty($widget_state['items'][$delta]['config']) ? $widget_state['items'][$delta]['config'] : [];
  $layout_path = array_merge($parents, [
    $this->fieldName,
    $delta,
    'entity_form',
    'layout_selection',
    'layout',
  ]);
  if (!($layout = $form_state
    ->getValue($layout_path))) {
    $layout = !empty($widget_state['items'][$delta]['layout']) ? $widget_state['items'][$delta]['layout'] : NULL;
  }
  $element = [
    '#type' => 'container',
    '#delta' => $delta,
    '#entity' => $entity,
    '#layout' => !empty($items[$delta]->layout) ? $items[$delta]->layout : '',
    '#region' => !empty($items[$delta]->region) ? $items[$delta]->region : '',
    '#layout_options' => $items[$delta]->options ?? [],
    '#attributes' => [
      'class' => [
        'erl-item',
        'erl-item--' . ($entity
          ->isPublished() ? 'published' : 'unpublished'),
      ],
      'id' => [
        $this->fieldName . '--item-' . $delta,
      ],
    ],
    'region' => [
      '#type' => 'hidden',
      '#attributes' => [
        'class' => [
          'erl-region',
        ],
      ],
      '#default_value' => !empty($items[$delta]->region) ? $items[$delta]->region : '',
    ],
    // These properties aren't modified by the main form,
    // but are modified when a user edits a specific item.
    'entity' => [
      '#type' => 'value',
      '#value' => $entity,
    ],
    'config' => [
      '#type' => 'value',
      '#value' => $config,
    ],
    'options' => [
      '#type' => 'value',
      '#value' => $options,
    ],
    'layout' => [
      '#type' => 'value',
      '#value' => $layout,
    ],
    '#process' => [],
  ];

  // Add Edit and Remove button, if the current user has appropriate
  // permissions.
  if ($this->currentUser
    ->hasPermission('manage entity reference layout sections')) {
    $element['actions'] = [
      '#type' => 'container',
      '#weight' => -1000,
      '#attributes' => [
        'class' => [
          'erl-actions',
        ],
      ],
      'edit' => [
        '#type' => 'submit',
        '#name' => 'edit_' . $this->fieldName . '_' . $delta,
        '#value' => $this
          ->t('Edit'),
        '#attributes' => [
          'class' => [
            'erl-edit',
          ],
        ],
        '#limit_validation_errors' => [
          array_merge($parents, [
            $this->fieldName,
          ]),
        ],
        '#submit' => [
          [
            $this,
            'editItemSubmit',
          ],
        ],
        '#delta' => $delta,
        '#ajax' => [
          'callback' => [
            $this,
            'elementAjax',
          ],
          'wrapper' => $this->wrapperId,
          'progress' => 'none',
        ],
        '#element_parents' => $parents,
      ],
      'remove' => [
        '#type' => 'submit',
        '#name' => 'remove_' . $this->fieldName . '_' . $delta,
        '#value' => $this
          ->t('Remove'),
        '#attributes' => [
          'class' => [
            'erl-remove',
          ],
        ],
        '#limit_validation_errors' => [
          array_merge($parents, [
            $this->fieldName,
          ]),
        ],
        '#submit' => [
          [
            $this,
            'removeItemSubmit',
          ],
        ],
        '#delta' => $delta,
        '#ajax' => [
          'callback' => [
            $this,
            'elementAjax',
          ],
          'wrapper' => $this->wrapperId,
          'progress' => 'none',
        ],
        '#element_parents' => $parents,
      ],
    ];
  }

  // If this is a new entity, pass the region and parent
  // item's weight to the theme.
  if (!empty($widget_state['items'][$delta]['is_new'])) {
    $element['#is_new'] = TRUE;
    $element['#new_region'] = $widget_state['items'][$delta]['new_region'];
    $element['#parent_weight'] = $widget_state['items'][$delta]['parent_weight'];
    $element['#attributes']['class'][] = 'js-hide';
  }

  // 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;
  }
  $element += [
    'preview' => $preview,
  ];

  // Add remove confirmation form if we're removing.
  if (isset($widget_state['remove_item']) && $widget_state['remove_item'] === $delta) {
    $element['remove_form'] = [
      '#prefix' => '<div class="erl-form">',
      '#suffix' => '</div>',
      '#type' => 'container',
      '#attributes' => [
        'data-dialog-title' => [
          $this
            ->t('Confirm removal'),
        ],
      ],
      'message' => [
        '#type' => 'markup',
        '#markup' => $this
          ->t('Are you sure you want to permanently remove this <b>@type?</b><br />This action cannot be undone.', [
          '@type' => $entity->type->entity
            ->label(),
        ]),
      ],
      'actions' => [
        '#type' => 'container',
        'confirm' => [
          '#type' => 'submit',
          '#value' => $this
            ->t('Remove'),
          '#delta' => $delta,
          '#submit' => [
            [
              $this,
              'removeItemConfirmSubmit',
            ],
          ],
          '#ajax' => [
            'callback' => [
              $this,
              'elementAjax',
            ],
            'wrapper' => $this->wrapperId,
          ],
          '#element_parents' => $parents,
        ],
        'cancel' => [
          '#type' => 'submit',
          '#value' => $this
            ->t('Cancel'),
          '#delta' => $delta,
          '#submit' => [
            [
              $this,
              'removeItemCancelSubmit',
            ],
          ],
          '#attributes' => [
            'class' => [
              'erl-cancel',
              'button--danger',
            ],
          ],
          '#ajax' => [
            'callback' => [
              $this,
              'elementAjax',
            ],
            'wrapper' => $this->wrapperId,
          ],
          '#element_parents' => $parents,
        ],
      ],
      '#weight' => 1000,
      '#delta' => $delta,
    ];
  }

  // Add edit form if open.
  if (isset($widget_state['open_form']) && $widget_state['open_form'] === $delta) {
    $display = EntityFormDisplay::collectRenderDisplay($entity, 'default');
    $bundle_label = $entity->type->entity
      ->label();
    $element['entity_form'] = [
      '#prefix' => '<div class="erl-form entity-type-' . $entity
        ->bundle() . '">',
      '#suffix' => '</div>',
      '#type' => 'container',
      '#parents' => array_merge($parents, [
        $this->fieldName,
        $delta,
        'entity_form',
      ]),
      '#weight' => 1000,
      '#delta' => $delta,
      '#display' => $display,
      '#attributes' => [
        'data-dialog-title' => [
          $entity
            ->id() ? $this
            ->t('Edit @type', [
            '@type' => $bundle_label,
          ]) : $this
            ->t('Create new @type', [
            '@type' => $bundle_label,
          ]),
        ],
      ],
    ];

    // Support for Field Group module based on Paragraphs module.
    // @todo Remove as part of https://www.drupal.org/node/2640056
    if (\Drupal::moduleHandler()
      ->moduleExists('field_group')) {
      $context = [
        'entity_type' => $entity
          ->getEntityTypeId(),
        'bundle' => $entity
          ->bundle(),
        'entity' => $entity,
        'context' => 'form',
        'display_context' => 'form',
        'mode' => $display
          ->getMode(),
      ];
      field_group_attach_groups($element['entity_form'], $context);
      if (function_exists('field_group_form_pre_render')) {
        $element['entity_form']['#pre_render'][] = 'field_group_form_pre_render';
      }
      if (function_exists('field_group_form_process')) {
        $element['entity_form']['#process'][] = 'field_group_form_process';
      }
    }
    $display
      ->buildForm($entity, $element['entity_form'], $form_state);

    // Add the layout plugin form if applicable.
    if (in_array($entity
      ->bundle(), $layout_bundles)) {
      $element['entity_form']['layout_selection'] = [
        '#type' => 'container',
        'layout' => [
          '#weight' => -100,
          '#type' => 'radios',
          '#title' => $this
            ->t('Select a layout:'),
          '#options' => $available_layouts,
          '#default_value' => $layout,
          '#attributes' => [
            'class' => [
              'erl-layout-select',
            ],
          ],
          '#required' => TRUE,
          '#after_build' => [
            [
              get_class($this),
              'processLayoutOptions',
            ],
          ],
        ],
        'update' => [
          '#type' => 'submit',
          '#value' => $this
            ->t('Update'),
          '#name' => 'update_layout',
          '#delta' => $element['#delta'],
          '#limit_validation_errors' => [
            array_merge($parents, [
              $this->fieldName,
              $delta,
              'entity_form',
              'layout_selection',
            ]),
          ],
          '#submit' => [
            [
              $this,
              'editItemSubmit',
            ],
          ],
          '#attributes' => [
            'class' => [
              'js-hide',
            ],
          ],
          '#element_parents' => $parents,
        ],
      ];

      // Switching layouts should change the layout plugin options form
      // with Ajax for users with adequate permissions.
      $element['entity_form']['layout_selection']['layout']['#ajax'] = [
        'event' => 'change',
        'callback' => [
          $this,
          'buildLayoutConfigurationFormAjax',
        ],
        'trigger_as' => [
          'name' => 'update_layout',
        ],
        'wrapper' => 'layout-config',
        'progress' => 'none',
      ];
      $element['entity_form']['layout_selection']['update']['#ajax'] = [
        'callback' => [
          $this,
          'buildLayoutConfigurationFormAjax',
        ],
        'wrapper' => 'layout-config',
        'progress' => 'none',
      ];
      $element['entity_form']['layout_plugin_form'] = [
        '#prefix' => '<div id="layout-config">',
        '#suffix' => '</div>',
        '#access' => $this->currentUser
          ->hasPermission('manage entity reference layout sections'),
      ];

      // Add the layout configuration form if applicable.
      if (!empty($layout)) {
        try {
          $layout_instance = $this->layoutPluginManager
            ->createInstance($layout, $config);
          if ($layout_plugin = $this
            ->getLayoutPluginForm($layout_instance)) {
            $element['entity_form']['layout_plugin_form'] += [
              '#type' => 'details',
              '#title' => $this
                ->t('Layout Configuration'),
            ];
            $element['entity_form']['layout_plugin_form'] += $layout_plugin
              ->buildConfigurationForm([], $form_state);
          }
        } catch (\Exception $e) {
          watchdog_exception('Erl, add the layout configuration form', $e);
        }
      }

      // Add the additional options form if applicable.
      // This is deprecated and included only for backwards compatibility.
      if ($this
        ->getSetting('always_show_options_form')) {

        // Other layout options.
        $element['entity_form']['options'] = [
          '#type' => 'details',
          '#title' => $this
            ->t('Basic Layout Options'),
          '#description' => $this
            ->t('Classes will be applied to the container for this field item.'),
          '#open' => FALSE,
        ];
        $element['entity_form']['options']['container_classes'] = [
          '#type' => 'textfield',
          '#title' => $this
            ->t('Custom Classes for Layout Container'),
          '#description' => $this
            ->t('Classes will be applied to the container for this field item.'),
          '#size' => 50,
          '#default_value' => $options['options']['container_classes'] ?? '',
          '#placeholder' => $this
            ->t('CSS Classes'),
        ];
        $element['entity_form']['options']['bg_color'] = [
          '#type' => 'textfield',
          '#title' => $this
            ->t('Background Color for Layout Container'),
          '#description' => $this
            ->t('Background will be applied to the layout container.'),
          '#size' => 10,
          '#default_value' => $options['options']['bg_color'] ?? '',
          '#placeholder' => $this
            ->t('Hex Code'),
        ];
      }
    }
    $paragraphs_type = $entity
      ->getParagraphType();

    // @todo: Check translation functionality.
    if ($paragraphs_type && $this->currentUser
      ->hasPermission('edit behavior plugin settings') && (!$this->isTranslating || !$entity
      ->isDefaultTranslationAffectedOnly()) && ($behavior_plugins = $paragraphs_type
      ->getEnabledBehaviorPlugins())) {
      $element['entity_form']['behavior_plugins'] = [
        '#type' => 'details',
        '#title' => $this
          ->t('Behaviors'),
        '#element_validate' => [
          [
            $this,
            'validateBehaviors',
          ],
        ],
        '#entity' => $entity,
      ];
      $has_behavior_form_options = FALSE;

      /* @var \Drupal\paragraphs\ParagraphsBehaviorInterface $plugin */
      foreach ($behavior_plugins as $plugin_id => $plugin) {
        $element['entity_form']['behavior_plugins'][$plugin_id] = [
          '#type' => 'container',
        ];
        $subform_state = SubformState::createForSubform($element['entity_form']['behavior_plugins'][$plugin_id], $form, $form_state);
        $plugin_form = $plugin
          ->buildBehaviorForm($entity, $element['entity_form']['behavior_plugins'][$plugin_id], $subform_state);
        if (!empty(Element::children($plugin_form))) {
          $element['entity_form']['behavior_plugins'][$plugin_id] = $plugin_form;
          $has_behavior_form_options = TRUE;
        }
      }

      // No behaviors were added, remove the behavior form.
      if (!$has_behavior_form_options) {
        unset($element['entity_form']['behavior_plugins']);
      }
    }

    // Add save, cancel, etc.
    $element['entity_form'] += [
      'actions' => [
        '#weight' => 1000,
        '#type' => 'container',
        '#attributes' => [
          'class' => [
            'erl-item-form-actions',
          ],
        ],
        'save_item' => [
          '#type' => 'submit',
          '#name' => 'save',
          '#value' => $this
            ->t('Save'),
          '#delta' => $element['#delta'],
          '#limit_validation_errors' => [
            array_merge($parents, [
              $this->fieldName,
            ]),
          ],
          '#submit' => [
            [
              $this,
              'saveItemSubmit',
            ],
          ],
          '#ajax' => [
            'callback' => [
              $this,
              'elementAjax',
            ],
            'wrapper' => $this->wrapperId,
            'progress' => 'none',
          ],
          '#element_parents' => $parents,
        ],
        'cancel' => [
          '#type' => 'submit',
          '#name' => 'cancel',
          '#value' => $this
            ->t('Cancel'),
          '#limit_validation_errors' => [],
          '#delta' => $element['#delta'],
          '#submit' => [
            [
              $this,
              'cancelItemSubmit',
            ],
          ],
          '#attributes' => [
            'class' => [
              'erl-cancel',
              'button--danger',
            ],
          ],
          '#ajax' => [
            'callback' => [
              $this,
              'elementAjax',
            ],
            'wrapper' => $this->wrapperId,
            'progress' => 'none',
          ],
          '#element_parents' => $parents,
        ],
      ],
    ];
    $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;
}