You are here

public function InlineEntityFormComplex::formElement in Inline Entity Form 8

Returns the form for a single field widget.

Field widget form elements should be based on the passed-in $element, which contains the base form element properties derived from the field configuration.

The BaseWidget methods will set the weight, field name and delta values for each form element. If there are multiple values for this field, the formElement() method will be called as many times as needed.

Other modules may alter the form element provided by this function using hook_field_widget_form_alter() or hook_field_widget_WIDGET_TYPE_form_alter().

The FAPI element callbacks (such as #process, #element_validate, #value_callback, etc.) used by the widget do not have access to the original $field_definition passed to the widget's constructor. Therefore, if any information is needed from that definition by those callbacks, the widget implementing this method, or a hook_field_widget[_WIDGET_TYPE]_form_alter() implementation, must extract the needed properties from the field definition and set them as ad-hoc $element['#custom'] properties, for later use by its element callbacks.

Parameters

\Drupal\Core\Field\FieldItemListInterface $items: Array of default values for this field.

int $delta: The order of this item in the array of sub-elements (0, 1, 2, etc.).

array $element: A form element array containing basic properties for the widget:

  • #field_parents: The 'parents' space for the field in the form. Most widgets can simply overlook this property. This identifies the location where the field values are placed within $form_state->getValues(), and is used to access processing information for the field through the getWidgetState() and setWidgetState() methods.
  • #title: The sanitized element label for the field, ready for output.
  • #description: The sanitized element description for the field, ready for output.
  • #required: A Boolean indicating whether the element value is required; for required multiple value fields, only the first widget's values are required.
  • #delta: The order of this item in the array of sub-elements; see $delta above.

array $form: The form structure where widgets are being attached to. This might be a full form structure, or a sub-element of a larger form.

\Drupal\Core\Form\FormStateInterface $form_state: The current state of the form.

Return value

array The form elements for a single widget for this field.

Overrides WidgetInterface::formElement

See also

hook_field_widget_form_alter()

hook_field_widget_WIDGET_TYPE_form_alter()

File

src/Plugin/Field/FieldWidget/InlineEntityFormComplex.php, line 203

Class

InlineEntityFormComplex
Complex inline widget.

Namespace

Drupal\inline_entity_form\Plugin\Field\FieldWidget

Code

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
  $settings = $this
    ->getSettings();
  $target_type = $this
    ->getFieldSetting('target_type');

  // Get the entity type labels for the UI strings.
  $labels = $this
    ->getEntityTypeLabels();

  // Build a parents array for this element's values in the form.
  $parents = array_merge($element['#field_parents'], [
    $items
      ->getName(),
    'form',
  ]);

  // Assign a unique identifier to each IEF widget.
  // Since $parents can get quite long, hashing ensures that every id has
  // a consistent and relatively short length while maintaining uniqueness.
  $this
    ->setIefId($this
    ->makeIefId($parents));

  // Get the langcode of the parent entity.
  $parent_langcode = $items
    ->getEntity()
    ->language()
    ->getId();

  // Determine the wrapper ID for the entire element.
  $wrapper = 'inline-entity-form-' . $this
    ->getIefId();
  $element = [
    '#type' => $this
      ->getSetting('collapsible') ? 'details' : 'fieldset',
    '#tree' => TRUE,
    '#description' => $this->fieldDefinition
      ->getDescription(),
    '#prefix' => '<div id="' . $wrapper . '">',
    '#suffix' => '</div>',
    '#ief_id' => $this
      ->getIefId(),
    '#ief_root' => TRUE,
    '#translating' => $this
      ->isTranslating($form_state),
    '#field_title' => $this->fieldDefinition
      ->getLabel(),
    '#after_build' => [
      [
        get_class($this),
        'removeTranslatabilityClue',
      ],
    ],
  ] + $element;
  if ($element['#type'] == 'details') {

    // If there's user input, keep the details open. Otherwise, use settings.
    $element['#open'] = $form_state
      ->getUserInput() ?: !$this
      ->getSetting('collapsed');
  }
  $this
    ->prepareFormState($form_state, $items, $element['#translating']);
  $entities = $form_state
    ->get([
    'inline_entity_form',
    $this
      ->getIefId(),
    'entities',
  ]);

  // Prepare cardinality information.
  $entities_count = count($entities);
  $cardinality = $this->fieldDefinition
    ->getFieldStorageDefinition()
    ->getCardinality();
  $cardinality_reached = $cardinality > 0 && $entities_count == $cardinality;

  // Build the "Multiple value" widget.
  // TODO - does this belong in #element_validate?
  $element['#element_validate'][] = [
    get_class($this),
    'updateRowWeights',
  ];

  // Add the required element marker & validation.
  if ($element['#required']) {
    $element['#element_validate'][] = [
      get_class($this),
      'requiredField',
    ];
  }
  $element['entities'] = [
    '#tree' => TRUE,
    '#theme' => 'inline_entity_form_entity_table',
    '#entity_type' => $target_type,
  ];

  // Get the fields that should be displayed in the table.
  $target_bundles = $this
    ->getTargetBundles();
  $fields = $this->inlineFormHandler
    ->getTableFields($target_bundles);
  $context = [
    'parent_entity_type' => $this->fieldDefinition
      ->getTargetEntityTypeId(),
    'parent_bundle' => $this->fieldDefinition
      ->getTargetBundle(),
    'field_name' => $this->fieldDefinition
      ->getName(),
    'entity_type' => $target_type,
    'allowed_bundles' => $target_bundles,
  ];
  $this->moduleHandler
    ->alter('inline_entity_form_table_fields', $fields, $context);
  $element['entities']['#table_fields'] = $fields;
  $weight_delta = max(ceil($entities_count * 1.2), 50);
  foreach ($entities as $key => $value) {

    // Data used by inline-entity-form-entity-table.html.twig.
    // @see template_preprocess_inline_entity_form_entity_table()

    /** @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = $value['entity'];
    $element['entities'][$key]['#label'] = $this->inlineFormHandler
      ->getEntityLabel($value['entity']);
    $element['entities'][$key]['#entity'] = $value['entity'];
    $element['entities'][$key]['#needs_save'] = $value['needs_save'];

    // Handle row weights.
    $element['entities'][$key]['#weight'] = $value['weight'];

    // First check to see if this entity should be displayed as a form.
    if (!empty($value['form'])) {
      $element['entities'][$key]['title'] = [];
      $element['entities'][$key]['delta'] = [
        '#type' => 'value',
        '#value' => $value['weight'],
      ];

      // Add the appropriate form.
      if (in_array($value['form'], [
        'edit',
        'duplicate',
      ])) {
        $element['entities'][$key]['form'] = [
          '#type' => 'container',
          '#attributes' => [
            'class' => [
              'ief-form',
              'ief-form-row',
            ],
          ],
          'inline_entity_form' => $this
            ->getInlineEntityForm($value['form'], $entity
            ->bundle(), $parent_langcode, $key, array_merge($parents, [
            'inline_entity_form',
            'entities',
            $key,
            'form',
          ]), $value['form'] == 'edit' ? $entity : $entity
            ->createDuplicate()),
        ];
        $element['entities'][$key]['form']['inline_entity_form']['#process'] = [
          [
            '\\Drupal\\inline_entity_form\\Element\\InlineEntityForm',
            'processEntityForm',
          ],
          [
            get_class($this),
            'addIefSubmitCallbacks',
          ],
          [
            get_class($this),
            'buildEntityFormActions',
          ],
        ];
      }
      elseif ($value['form'] == 'remove') {
        $element['entities'][$key]['form'] = [
          '#type' => 'container',
          '#attributes' => [
            'class' => [
              'ief-form',
              'ief-form-row',
            ],
          ],
          // Used by Field API and controller methods to find the relevant
          // values in $form_state.
          '#parents' => array_merge($parents, [
            'entities',
            $key,
            'form',
          ]),
          // Store the entity on the form, later modified in the controller.
          '#entity' => $entity,
          // Identifies the IEF widget to which the form belongs.
          '#ief_id' => $this
            ->getIefId(),
          // Identifies the table row to which the form belongs.
          '#ief_row_delta' => $key,
        ];
        $this
          ->buildRemoveForm($element['entities'][$key]['form']);
      }
    }
    else {
      $row =& $element['entities'][$key];
      $row['title'] = [];
      $row['delta'] = [
        '#type' => 'weight',
        '#delta' => $weight_delta,
        '#default_value' => $value['weight'],
        '#attributes' => [
          'class' => [
            'ief-entity-delta',
          ],
        ],
      ];

      // Add an actions container with edit and delete buttons for the entity.
      $row['actions'] = [
        '#type' => 'container',
        '#attributes' => [
          'class' => [
            'ief-entity-operations',
          ],
        ],
      ];

      // Make sure entity_access is not checked for unsaved entities.
      $entity_id = $entity
        ->id();
      if (empty($entity_id) || $entity
        ->access('update')) {
        $row['actions']['ief_entity_edit'] = [
          '#type' => 'submit',
          '#value' => $this
            ->t('Edit'),
          '#name' => 'ief-' . $this
            ->getIefId() . '-entity-edit-' . $key,
          '#limit_validation_errors' => [],
          '#ajax' => [
            'callback' => 'inline_entity_form_get_element',
            'wrapper' => $wrapper,
          ],
          '#submit' => [
            'inline_entity_form_open_row_form',
          ],
          '#ief_row_delta' => $key,
          '#ief_row_form' => 'edit',
        ];
      }

      // Add the duplicate button, if allowed.
      if ($settings['allow_duplicate'] && !$cardinality_reached && $entity
        ->access('create')) {
        $row['actions']['ief_entity_duplicate'] = [
          '#type' => 'submit',
          '#value' => $this
            ->t('Duplicate'),
          '#name' => 'ief-' . $this
            ->getIefId() . '-entity-duplicate-' . $key,
          '#limit_validation_errors' => [
            array_merge($parents, [
              'actions',
            ]),
          ],
          '#ajax' => [
            'callback' => 'inline_entity_form_get_element',
            'wrapper' => $wrapper,
          ],
          '#submit' => [
            'inline_entity_form_open_row_form',
          ],
          '#ief_row_delta' => $key,
          '#ief_row_form' => 'duplicate',
        ];
      }

      // If 'allow_existing' is on, the default removal operation is unlink
      // and the access check for deleting happens inside the controller
      // removeForm() method.
      if (empty($entity_id) || $settings['allow_existing'] || $entity
        ->access('delete')) {
        $row['actions']['ief_entity_remove'] = [
          '#type' => 'submit',
          '#value' => $this
            ->t('Remove'),
          '#name' => 'ief-' . $this
            ->getIefId() . '-entity-remove-' . $key,
          '#limit_validation_errors' => [],
          '#ajax' => [
            'callback' => 'inline_entity_form_get_element',
            'wrapper' => $wrapper,
          ],
          '#submit' => [
            'inline_entity_form_open_row_form',
          ],
          '#ief_row_delta' => $key,
          '#ief_row_form' => 'remove',
          '#access' => !$element['#translating'],
        ];
      }
    }
  }

  // When in translation, the widget only supports editing (translating)
  // already added entities, so there's no need to show the rest.
  if ($element['#translating']) {
    if (empty($entities)) {

      // There are no entities available for translation, hide the widget.
      $element['#access'] = FALSE;
    }
    return $element;
  }
  if ($cardinality > 1) {

    // Add a visual cue of cardinality count.
    $message = $this
      ->t('You have added @entities_count out of @cardinality_count allowed @label.', [
      '@entities_count' => $entities_count,
      '@cardinality_count' => $cardinality,
      '@label' => $labels['plural'],
    ]);
    $element['cardinality_count'] = [
      '#markup' => '<div class="ief-cardinality-count">' . $message . '</div>',
    ];
  }

  // Do not return the rest of the form if cardinality count has been reached.
  if ($cardinality_reached) {
    return $element;
  }
  $create_bundles = $this
    ->getCreateBundles();
  $create_bundles_count = count($create_bundles);
  $allow_new = $settings['allow_new'] && !empty($create_bundles);
  $hide_cancel = FALSE;

  // If the field is required and empty try to open one of the forms.
  if (empty($entities) && $this->fieldDefinition
    ->isRequired()) {
    if ($settings['allow_existing'] && !$allow_new) {
      $form_state
        ->set([
        'inline_entity_form',
        $this
          ->getIefId(),
        'form',
      ], 'ief_add_existing');
      $hide_cancel = TRUE;
    }
    elseif ($create_bundles_count == 1 && $allow_new && !$settings['allow_existing']) {
      $bundle = reset($target_bundles);

      // The parent entity type and bundle must not be the same as the inline
      // entity type and bundle, to prevent recursion.
      $parent_entity_type = $this->fieldDefinition
        ->getTargetEntityTypeId();
      $parent_bundle = $this->fieldDefinition
        ->getTargetBundle();
      if ($parent_entity_type != $target_type || $parent_bundle != $bundle) {
        $form_state
          ->set([
          'inline_entity_form',
          $this
            ->getIefId(),
          'form',
        ], 'add');
        $form_state
          ->set([
          'inline_entity_form',
          $this
            ->getIefId(),
          'form settings',
        ], [
          'bundle' => $bundle,
        ]);
        $hide_cancel = TRUE;
      }
    }
  }

  // If no form is open, show buttons that open one.
  $open_form = $form_state
    ->get([
    'inline_entity_form',
    $this
      ->getIefId(),
    'form',
  ]);
  if (empty($open_form)) {
    $element['actions'] = [
      '#attributes' => [
        'class' => [
          'container-inline',
        ],
      ],
      '#type' => 'container',
      '#weight' => 100,
    ];

    // The user is allowed to create an entity of at least one bundle.
    if ($allow_new) {

      // Let the user select the bundle, if multiple are available.
      if ($create_bundles_count > 1) {
        $bundles = [];
        foreach ($this->entityTypeBundleInfo
          ->getBundleInfo($target_type) as $bundle_name => $bundle_info) {
          if (in_array($bundle_name, $create_bundles)) {
            $bundles[$bundle_name] = $bundle_info['label'];
          }
        }
        asort($bundles);
        $element['actions']['bundle'] = [
          '#type' => 'select',
          '#options' => $bundles,
        ];
      }
      else {
        $element['actions']['bundle'] = [
          '#type' => 'value',
          '#value' => reset($create_bundles),
        ];
      }
      $element['actions']['ief_add'] = [
        '#type' => 'submit',
        '#value' => $this
          ->t('Add new @type_singular', [
          '@type_singular' => $labels['singular'],
        ]),
        '#name' => 'ief-' . $this
          ->getIefId() . '-add',
        '#limit_validation_errors' => [
          array_merge($parents, [
            'actions',
          ]),
        ],
        '#ajax' => [
          'callback' => 'inline_entity_form_get_element',
          'wrapper' => $wrapper,
        ],
        '#submit' => [
          'inline_entity_form_open_form',
        ],
        '#ief_form' => 'add',
      ];
    }
    if ($settings['allow_existing']) {
      $element['actions']['ief_add_existing'] = [
        '#type' => 'submit',
        '#value' => $this
          ->t('Add existing @type_singular', [
          '@type_singular' => $labels['singular'],
        ]),
        '#name' => 'ief-' . $this
          ->getIefId() . '-add-existing',
        '#limit_validation_errors' => [
          array_merge($parents, [
            'actions',
          ]),
        ],
        '#ajax' => [
          'callback' => 'inline_entity_form_get_element',
          'wrapper' => $wrapper,
        ],
        '#submit' => [
          'inline_entity_form_open_form',
        ],
        '#ief_form' => 'ief_add_existing',
      ];
    }
  }
  else {

    // Make a delta key bigger than all existing ones, without assuming that
    // the keys are strictly consecutive.
    $new_key = $entities ? max(array_keys($entities)) + 1 : 0;

    // There's a form open, show it.
    if ($form_state
      ->get([
      'inline_entity_form',
      $this
        ->getIefId(),
      'form',
    ]) == 'add') {
      $element['form'] = [
        '#type' => 'fieldset',
        '#attributes' => [
          'class' => [
            'ief-form',
            'ief-form-bottom',
          ],
        ],
        'inline_entity_form' => $this
          ->getInlineEntityForm('add', $this
          ->determineBundle($form_state), $parent_langcode, $new_key, array_merge($parents, [
          $new_key,
        ])),
      ];
      $element['form']['inline_entity_form']['#process'] = [
        [
          '\\Drupal\\inline_entity_form\\Element\\InlineEntityForm',
          'processEntityForm',
        ],
        [
          get_class($this),
          'addIefSubmitCallbacks',
        ],
        [
          get_class($this),
          'buildEntityFormActions',
        ],
      ];
    }
    elseif ($form_state
      ->get([
      'inline_entity_form',
      $this
        ->getIefId(),
      'form',
    ]) == 'ief_add_existing') {
      $element['form'] = [
        '#type' => 'fieldset',
        '#attributes' => [
          'class' => [
            'ief-form',
            'ief-form-bottom',
          ],
        ],
        // Identifies the IEF widget to which the form belongs.
        '#ief_id' => $this
          ->getIefId(),
        // Used by Field API and controller methods to find the relevant
        // values in $form_state.
        '#parents' => array_merge($parents, [
          $new_key,
        ]),
        '#entity_type' => $target_type,
        '#ief_labels' => $this
          ->getEntityTypeLabels(),
        '#match_operator' => $this
          ->getSetting('match_operator'),
      ];
      $element['form'] += inline_entity_form_reference_form($element['form'], $form_state);
    }

    // Pre-opened forms can't be closed in order to force the user to
    // add / reference an entity.
    if ($hide_cancel) {
      if ($open_form == 'add') {
        $process_element =& $element['form']['inline_entity_form'];
      }
      elseif ($open_form == 'ief_add_existing') {
        $process_element =& $element['form'];
      }
      $process_element['#process'][] = [
        get_class($this),
        'hideCancel',
      ];
    }
  }
  return $element;
}