3 namespace Drupal\inline_entity_form\Plugin\Field\FieldWidget;
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
7 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
8 use Drupal\Core\Entity\EntityTypeManagerInterface;
9 use Drupal\Core\Extension\ModuleHandlerInterface;
10 use Drupal\Core\Field\FieldDefinitionInterface;
11 use Drupal\Core\Field\FieldItemListInterface;
12 use Drupal\Core\Form\FormStateInterface;
13 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
14 use Drupal\Core\Render\Element;
15 use Drupal\inline_entity_form\TranslationHelper;
16 use Symfony\Component\DependencyInjection\ContainerInterface;
19 * Complex inline widget.
22 * id = "inline_entity_form_complex",
23 * label = @Translation("Inline entity form - Complex"),
27 * multiple_values = true
30 class InlineEntityFormComplex extends InlineEntityFormBase implements ContainerFactoryPluginInterface {
33 * Module handler service.
35 * @var \Drupal\Core\Extension\ModuleHandlerInterface
37 protected $moduleHandler;
40 * Constructs a InlineEntityFormComplex object.
42 * @param array $plugin_id
43 * The plugin_id for the widget.
44 * @param mixed $plugin_definition
45 * The plugin implementation definition.
46 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
47 * The definition of the field to which the widget is associated.
48 * @param array $settings
49 * The widget settings.
50 * @param array $third_party_settings
51 * Any third party settings.
52 * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
53 * The entity type bundle info.
54 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
55 * The entity type manager.
56 * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
57 * The entity display repository.
58 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
59 * Module handler service.
61 public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository, ModuleHandlerInterface $module_handler) {
62 parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $entity_type_bundle_info, $entity_type_manager, $entity_display_repository);
63 $this->moduleHandler = $module_handler;
69 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
73 $configuration['field_definition'],
74 $configuration['settings'],
75 $configuration['third_party_settings'],
76 $container->get('entity_type.bundle.info'),
77 $container->get('entity_type.manager'),
78 $container->get('entity_display.repository'),
79 $container->get('module_handler')
86 public static function defaultSettings() {
87 $defaults = parent::defaultSettings();
90 'allow_existing' => FALSE,
91 'match_operator' => 'CONTAINS',
92 'allow_duplicate' => FALSE,
101 public function settingsForm(array $form, FormStateInterface $form_state) {
102 $element = parent::settingsForm($form, $form_state);
104 $labels = $this->getEntityTypeLabels();
105 $states_prefix = 'fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings]';
106 $element['allow_new'] = [
107 '#type' => 'checkbox',
108 '#title' => $this->t('Allow users to add new @label.', ['@label' => $labels['plural']]),
109 '#default_value' => $this->getSetting('allow_new'),
111 $element['allow_existing'] = [
112 '#type' => 'checkbox',
113 '#title' => $this->t('Allow users to add existing @label.', ['@label' => $labels['plural']]),
114 '#default_value' => $this->getSetting('allow_existing'),
116 $element['match_operator'] = [
118 '#title' => $this->t('Autocomplete matching'),
119 '#default_value' => $this->getSetting('match_operator'),
120 '#options' => $this->getMatchOperatorOptions(),
121 '#description' => $this->t('Select the method used to collect autocomplete suggestions. Note that <em>Contains</em> can cause performance issues on sites with thousands of nodes.'),
124 ':input[name="' . $states_prefix . '[allow_existing]"]' => ['checked' => TRUE],
128 $element['allow_duplicate'] = [
129 '#type' => 'checkbox',
130 '#title' => $this->t('Allow users to duplicate @label.', ['@label' => $labels['plural']]),
131 '#default_value' => $this->getSetting('allow_duplicate'),
140 public function settingsSummary() {
141 $summary = parent::settingsSummary();
142 $labels = $this->getEntityTypeLabels();
144 if ($this->getSetting('allow_new')) {
145 $summary[] = $this->t('New @label can be added.', ['@label' => $labels['plural']]);
148 $summary[] = $this->t('New @label can not be created.', ['@label' => $labels['plural']]);
151 $match_operator_options = $this->getMatchOperatorOptions();
152 if ($this->getSetting('allow_existing')) {
153 $summary[] = $this->t('Existing @label can be referenced and are matched with the %operator operator.', [
154 '@label' => $labels['plural'],
155 '%operator' => $match_operator_options[$this->getSetting('match_operator')],
159 $summary[] = $this->t('Existing @label can not be referenced.', ['@label' => $labels['plural']]);
162 if ($this->getSetting('allow_duplicate')) {
163 $summary[] = $this->t('@label can be duplicated.', ['@label' => $labels['plural']]);
166 $summary[] = $this->t('@label can not be duplicated.', ['@label' => $labels['plural']]);
173 * Returns the options for the match operator.
178 protected function getMatchOperatorOptions() {
180 'STARTS_WITH' => $this->t('Starts with'),
181 'CONTAINS' => $this->t('Contains'),
188 public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
189 $settings = $this->getSettings();
190 $target_type = $this->getFieldSetting('target_type');
191 // Get the entity type labels for the UI strings.
192 $labels = $this->getEntityTypeLabels();
194 // Build a parents array for this element's values in the form.
195 $parents = array_merge($element['#field_parents'], [
200 // Assign a unique identifier to each IEF widget.
201 // Since $parents can get quite long, sha1() ensures that every id has
202 // a consistent and relatively short length while maintaining uniqueness.
203 $this->setIefId(sha1(implode('-', $parents)));
205 // Get the langcode of the parent entity.
206 $parent_langcode = $items->getEntity()->language()->getId();
208 // Determine the wrapper ID for the entire element.
209 $wrapper = 'inline-entity-form-' . $this->getIefId();
212 '#type' => $this->getSetting('collapsible') ? 'details' : 'fieldset',
214 '#description' => $this->fieldDefinition->getDescription(),
215 '#prefix' => '<div id="' . $wrapper . '">',
216 '#suffix' => '</div>',
217 '#ief_id' => $this->getIefId(),
219 '#translating' => $this->isTranslating($form_state),
220 '#field_title' => $this->fieldDefinition->getLabel(),
222 [get_class($this), 'removeTranslatabilityClue'],
225 if ($element['#type'] == 'details') {
226 $element['#open'] = !$this->getSetting('collapsed');
229 $element['#attached']['library'][] = 'inline_entity_form/widget';
231 $this->prepareFormState($form_state, $items, $element['#translating']);
232 $entities = $form_state->get(['inline_entity_form', $this->getIefId(), 'entities']);
234 // Prepare cardinality information.
235 $entities_count = count($entities);
236 $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
237 $cardinality_reached = ($cardinality > 0 && $entities_count == $cardinality);
239 // Build the "Multiple value" widget.
240 // TODO - does this belong in #element_validate?
241 $element['#element_validate'][] = [get_class($this), 'updateRowWeights'];
242 // Add the required element marker & validation.
243 if ($element['#required']) {
244 $element['#element_validate'][] = [get_class($this), 'requiredField'];
247 $element['entities'] = [
249 '#theme' => 'inline_entity_form_entity_table',
250 '#entity_type' => $target_type,
253 // Get the fields that should be displayed in the table.
254 $target_bundles = $this->getTargetBundles();
255 $fields = $this->inlineFormHandler->getTableFields($target_bundles);
257 'parent_entity_type' => $this->fieldDefinition->getTargetEntityTypeId(),
258 'parent_bundle' => $this->fieldDefinition->getTargetBundle(),
259 'field_name' => $this->fieldDefinition->getName(),
260 'entity_type' => $target_type,
261 'allowed_bundles' => $target_bundles,
263 $this->moduleHandler->alter('inline_entity_form_table_fields', $fields, $context);
264 $element['entities']['#table_fields'] = $fields;
266 $weight_delta = max(ceil($entities_count * 1.2), 50);
267 foreach ($entities as $key => $value) {
268 // Data used by theme_inline_entity_form_entity_table().
269 /** @var \Drupal\Core\Entity\EntityInterface $entity */
270 $entity = $value['entity'];
271 $element['entities'][$key]['#label'] = $this->inlineFormHandler->getEntityLabel($value['entity']);
272 $element['entities'][$key]['#entity'] = $value['entity'];
273 $element['entities'][$key]['#needs_save'] = $value['needs_save'];
275 // Handle row weights.
276 $element['entities'][$key]['#weight'] = $value['weight'];
278 // First check to see if this entity should be displayed as a form.
279 if (!empty($value['form'])) {
280 $element['entities'][$key]['title'] = [];
281 $element['entities'][$key]['delta'] = [
283 '#value' => $value['weight'],
286 // Add the appropriate form.
287 if (in_array($value['form'], ['edit', 'duplicate'])) {
288 $element['entities'][$key]['form'] = [
289 '#type' => 'container',
290 '#attributes' => ['class' => ['ief-form', 'ief-form-row']],
291 'inline_entity_form' => $this->getInlineEntityForm(
296 array_merge($parents, ['inline_entity_form', 'entities', $key, 'form']),
297 $value['form'] == 'edit' ? $entity : $entity->createDuplicate()
301 $element['entities'][$key]['form']['inline_entity_form']['#process'] = [
302 ['\Drupal\inline_entity_form\Element\InlineEntityForm', 'processEntityForm'],
303 [get_class($this), 'addIefSubmitCallbacks'],
304 [get_class($this), 'buildEntityFormActions'],
307 elseif ($value['form'] == 'remove') {
308 $element['entities'][$key]['form'] = [
309 '#type' => 'container',
310 '#attributes' => ['class' => ['ief-form', 'ief-form-row']],
311 // Used by Field API and controller methods to find the relevant
312 // values in $form_state.
313 '#parents' => array_merge($parents, ['entities', $key, 'form']),
314 // Store the entity on the form, later modified in the controller.
315 '#entity' => $entity,
316 // Identifies the IEF widget to which the form belongs.
317 '#ief_id' => $this->getIefId(),
318 // Identifies the table row to which the form belongs.
319 '#ief_row_delta' => $key,
321 $this->buildRemoveForm($element['entities'][$key]['form']);
325 $row = &$element['entities'][$key];
329 '#delta' => $weight_delta,
330 '#default_value' => $value['weight'],
331 '#attributes' => ['class' => ['ief-entity-delta']],
333 // Add an actions container with edit and delete buttons for the entity.
335 '#type' => 'container',
336 '#attributes' => ['class' => ['ief-entity-operations']],
339 // Make sure entity_access is not checked for unsaved entities.
340 $entity_id = $entity->id();
341 if (empty($entity_id) || $entity->access('update')) {
342 $row['actions']['ief_entity_edit'] = [
344 '#value' => $this->t('Edit'),
345 '#name' => 'ief-' . $this->getIefId() . '-entity-edit-' . $key,
346 '#limit_validation_errors' => [],
348 'callback' => 'inline_entity_form_get_element',
349 'wrapper' => $wrapper,
351 '#submit' => ['inline_entity_form_open_row_form'],
352 '#ief_row_delta' => $key,
353 '#ief_row_form' => 'edit',
357 // Add the duplicate button, if allowed.
358 if ($settings['allow_duplicate'] && !$cardinality_reached && $entity->access('create')) {
359 $row['actions']['ief_entity_duplicate'] = [
361 '#value' => $this->t('Duplicate'),
362 '#name' => 'ief-' . $this->getIefId() . '-entity-duplicate-' . $key,
363 '#limit_validation_errors' => [array_merge($parents, ['actions'])],
365 'callback' => 'inline_entity_form_get_element',
366 'wrapper' => $wrapper,
368 '#submit' => ['inline_entity_form_open_row_form'],
369 '#ief_row_delta' => $key,
370 '#ief_row_form' => 'duplicate',
374 // If 'allow_existing' is on, the default removal operation is unlink
375 // and the access check for deleting happens inside the controller
376 // removeForm() method.
377 if (empty($entity_id) || $settings['allow_existing'] || $entity->access('delete')) {
378 $row['actions']['ief_entity_remove'] = [
380 '#value' => $this->t('Remove'),
381 '#name' => 'ief-' . $this->getIefId() . '-entity-remove-' . $key,
382 '#limit_validation_errors' => [],
384 'callback' => 'inline_entity_form_get_element',
385 'wrapper' => $wrapper,
387 '#submit' => ['inline_entity_form_open_row_form'],
388 '#ief_row_delta' => $key,
389 '#ief_row_form' => 'remove',
390 '#access' => !$element['#translating'],
396 // When in translation, the widget only supports editing (translating)
397 // already added entities, so there's no need to show the rest.
398 if ($element['#translating']) {
399 if (empty($entities)) {
400 // There are no entities available for translation, hide the widget.
401 $element['#access'] = FALSE;
406 if ($cardinality > 1) {
407 // Add a visual cue of cardinality count.
408 $message = $this->t('You have added @entities_count out of @cardinality_count allowed @label.', [
409 '@entities_count' => $entities_count,
410 '@cardinality_count' => $cardinality,
411 '@label' => $labels['plural'],
413 $element['cardinality_count'] = [
414 '#markup' => '<div class="ief-cardinality-count">' . $message . '</div>',
417 // Do not return the rest of the form if cardinality count has been reached.
418 if ($cardinality_reached) {
422 $create_bundles = $this->getCreateBundles();
423 $create_bundles_count = count($create_bundles);
424 $allow_new = $settings['allow_new'] && !empty($create_bundles);
425 $hide_cancel = FALSE;
426 // If the field is required and empty try to open one of the forms.
427 if (empty($entities) && $this->fieldDefinition->isRequired()) {
428 if ($settings['allow_existing'] && !$allow_new) {
429 $form_state->set(['inline_entity_form', $this->getIefId(), 'form'], 'ief_add_existing');
432 elseif ($create_bundles_count == 1 && $allow_new && !$settings['allow_existing']) {
433 $bundle = reset($target_bundles);
435 // The parent entity type and bundle must not be the same as the inline
436 // entity type and bundle, to prevent recursion.
437 $parent_entity_type = $this->fieldDefinition->getTargetEntityTypeId();
438 $parent_bundle = $this->fieldDefinition->getTargetBundle();
439 if ($parent_entity_type != $target_type || $parent_bundle != $bundle) {
440 $form_state->set(['inline_entity_form', $this->getIefId(), 'form'], 'add');
441 $form_state->set(['inline_entity_form', $this->getIefId(), 'form settings'], [
449 // If no form is open, show buttons that open one.
450 $open_form = $form_state->get(['inline_entity_form', $this->getIefId(), 'form']);
452 if (empty($open_form)) {
453 $element['actions'] = [
454 '#attributes' => ['class' => ['container-inline']],
455 '#type' => 'container',
459 // The user is allowed to create an entity of at least one bundle.
461 // Let the user select the bundle, if multiple are available.
462 if ($create_bundles_count > 1) {
464 foreach ($this->entityTypeBundleInfo->getBundleInfo($target_type) as $bundle_name => $bundle_info) {
465 if (in_array($bundle_name, $create_bundles)) {
466 $bundles[$bundle_name] = $bundle_info['label'];
471 $element['actions']['bundle'] = [
473 '#options' => $bundles,
477 $element['actions']['bundle'] = [
479 '#value' => reset($create_bundles),
483 $element['actions']['ief_add'] = [
485 '#value' => $this->t('Add new @type_singular', ['@type_singular' => $labels['singular']]),
486 '#name' => 'ief-' . $this->getIefId() . '-add',
487 '#limit_validation_errors' => [array_merge($parents, ['actions'])],
489 'callback' => 'inline_entity_form_get_element',
490 'wrapper' => $wrapper,
492 '#submit' => ['inline_entity_form_open_form'],
493 '#ief_form' => 'add',
497 if ($settings['allow_existing']) {
498 $element['actions']['ief_add_existing'] = [
500 '#value' => $this->t('Add existing @type_singular', ['@type_singular' => $labels['singular']]),
501 '#name' => 'ief-' . $this->getIefId() . '-add-existing',
502 '#limit_validation_errors' => [array_merge($parents, ['actions'])],
504 'callback' => 'inline_entity_form_get_element',
505 'wrapper' => $wrapper,
507 '#submit' => ['inline_entity_form_open_form'],
508 '#ief_form' => 'ief_add_existing',
513 // There's a form open, show it.
514 if ($form_state->get(['inline_entity_form', $this->getIefId(), 'form']) == 'add') {
516 '#type' => 'fieldset',
517 '#attributes' => ['class' => ['ief-form', 'ief-form-bottom']],
518 'inline_entity_form' => $this->getInlineEntityForm(
520 $this->determineBundle($form_state),
523 array_merge($parents, ['inline_entity_form'])
526 $element['form']['inline_entity_form']['#process'] = [
527 ['\Drupal\inline_entity_form\Element\InlineEntityForm', 'processEntityForm'],
528 [get_class($this), 'addIefSubmitCallbacks'],
529 [get_class($this), 'buildEntityFormActions'],
532 elseif ($form_state->get(['inline_entity_form', $this->getIefId(), 'form']) == 'ief_add_existing') {
534 '#type' => 'fieldset',
535 '#attributes' => ['class' => ['ief-form', 'ief-form-bottom']],
536 // Identifies the IEF widget to which the form belongs.
537 '#ief_id' => $this->getIefId(),
538 // Used by Field API and controller methods to find the relevant
539 // values in $form_state.
540 '#parents' => array_merge($parents),
541 '#entity_type' => $target_type,
542 '#ief_labels' => $this->getEntityTypeLabels(),
543 '#match_operator' => $this->getSetting('match_operator'),
546 $element['form'] += inline_entity_form_reference_form($element['form'], $form_state);
549 // Pre-opened forms can't be closed in order to force the user to
550 // add / reference an entity.
552 if ($open_form == 'add') {
553 $process_element = &$element['form']['inline_entity_form'];
555 elseif ($open_form == 'ief_add_existing') {
556 $process_element = &$element['form'];
558 $process_element['#process'][] = [get_class($this), 'hideCancel'];
561 // No entities have been added. Remove the outer fieldset to reduce
562 // visual noise caused by having two titles.
563 if (empty($entities)) {
564 $element['#type'] = 'container';
574 public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
575 if ($this->isDefaultValueWidget($form_state)) {
576 $items->filterEmptyItems();
579 $triggering_element = $form_state->getTriggeringElement();
580 if (empty($triggering_element['#ief_submit_trigger'])) {
584 $field_name = $this->fieldDefinition->getName();
585 $parents = array_merge($form['#parents'], [$field_name, 'form']);
586 $ief_id = sha1(implode('-', $parents));
587 $this->setIefId($ief_id);
588 $widget_state = &$form_state->get(['inline_entity_form', $ief_id]);
589 foreach ($widget_state['entities'] as $key => $value) {
590 $changed = TranslationHelper::updateEntityLangcode($value['entity'], $form_state);
592 $widget_state['entities'][$key]['entity'] = $value['entity'];
593 $widget_state['entities'][$key]['needs_save'] = TRUE;
597 $values = $widget_state['entities'];
598 // If the inline entity form is still open, then its entity hasn't
599 // been transferred to the IEF form state yet.
600 if (empty($values) && !empty($widget_state['form'])) {
601 // @todo Do the same for reference forms.
602 if ($widget_state['form'] == 'add') {
603 $element = NestedArray::getValue($form, [$field_name, 'widget', 'form']);
604 $entity = $element['inline_entity_form']['#entity'];
605 $values[] = ['entity' => $entity];
608 // Sort values by weight.
609 uasort($values, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
610 // Let the widget massage the submitted values.
611 $values = $this->massageFormValues($values, $form, $form_state);
612 // Assign the values and remove the empty ones.
613 $items->setValue($values);
614 $items->filterEmptyItems();
618 * Adds actions to the inline entity form.
620 * @param array $element
621 * Form array structure.
623 public static function buildEntityFormActions($element) {
624 // Build a delta suffix that's appended to button #name keys for uniqueness.
625 $delta = $element['#ief_id'];
626 if ($element['#op'] == 'add') {
627 $save_label = t('Create @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]);
629 elseif ($element['#op'] == 'duplicate') {
630 $save_label = t('Duplicate @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]);
633 $delta .= '-' . $element['#ief_row_delta'];
634 $save_label = t('Update @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]);
637 // Add action submit elements.
638 $element['actions'] = [
639 '#type' => 'container',
642 $element['actions']['ief_' . $element['#op'] . '_save'] = [
644 '#value' => $save_label,
645 '#name' => 'ief-' . $element['#op'] . '-submit-' . $delta,
646 '#limit_validation_errors' => [$element['#parents']],
647 '#attributes' => ['class' => ['ief-entity-submit']],
649 'callback' => 'inline_entity_form_get_element',
650 'wrapper' => 'inline-entity-form-' . $element['#ief_id'],
653 $element['actions']['ief_' . $element['#op'] . '_cancel'] = [
655 '#value' => t('Cancel'),
656 '#name' => 'ief-' . $element['#op'] . '-cancel-' . $delta,
657 '#limit_validation_errors' => [],
659 'callback' => 'inline_entity_form_get_element',
660 'wrapper' => 'inline-entity-form-' . $element['#ief_id'],
664 // Add submit handlers depending on operation.
665 if ($element['#op'] == 'add') {
666 static::addSubmitCallbacks($element['actions']['ief_add_save']);
667 $element['actions']['ief_add_cancel']['#submit'] = [
668 [get_called_class(), 'closeChildForms'],
669 [get_called_class(), 'closeForm'],
670 'inline_entity_form_cleanup_form_state',
674 $element['actions']['ief_' . $element['#op'] . '_save']['#ief_row_delta'] = $element['#ief_row_delta'];
675 $element['actions']['ief_' . $element['#op'] . '_cancel']['#ief_row_delta'] = $element['#ief_row_delta'];
677 static::addSubmitCallbacks($element['actions']['ief_' . $element['#op'] . '_save']);
678 $element['actions']['ief_' . $element['#op'] . '_save']['#submit'][] = [get_called_class(), 'submitCloseRow'];
679 $element['actions']['ief_' . $element['#op'] . '_cancel']['#submit'] = [
680 [get_called_class(), 'closeChildForms'],
681 [get_called_class(), 'submitCloseRow'],
682 'inline_entity_form_cleanup_row_form_state',
690 * Hides cancel button.
692 * @param array $element
693 * Form array structure.
695 public static function hideCancel($element) {
696 // @todo Name both buttons the same and simplify this logic.
697 if (isset($element['actions']['ief_add_cancel'])) {
698 $element['actions']['ief_add_cancel']['#access'] = FALSE;
700 elseif (isset($element['actions']['ief_reference_cancel'])) {
701 $element['actions']['ief_reference_cancel']['#access'] = FALSE;
708 * Builds remove form.
711 * Form array structure.
713 protected function buildRemoveForm(&$form) {
714 /** @var \Drupal\Core\Entity\EntityInterface $entity */
715 $entity = $form['#entity'];
716 $entity_id = $entity->id();
717 $entity_label = $this->inlineFormHandler->getEntityLabel($entity);
718 $labels = $this->getEntityTypeLabels();
721 $message = $this->t('Are you sure you want to remove %label?', ['%label' => $entity_label]);
724 $message = $this->t('Are you sure you want to remove this %entity_type?', ['%entity_type' => $labels['singular']]);
728 '#theme_wrappers' => ['container'],
729 '#markup' => $message,
732 if (!empty($entity_id) && $this->getSetting('allow_existing') && $entity->access('delete')) {
734 '#type' => 'checkbox',
735 '#title' => $this->t('Delete this @type_singular from the system.', ['@type_singular' => $labels['singular']]),
739 // Build a deta suffix that's appended to button #name keys for uniqueness.
740 $delta = $form['#ief_id'] . '-' . $form['#ief_row_delta'];
742 // Add actions to the form.
744 '#type' => 'container',
747 $form['actions']['ief_remove_confirm'] = [
749 '#value' => $this->t('Remove'),
750 '#name' => 'ief-remove-confirm-' . $delta,
751 '#limit_validation_errors' => [$form['#parents']],
753 'callback' => 'inline_entity_form_get_element',
754 'wrapper' => 'inline-entity-form-' . $form['#ief_id'],
756 '#allow_existing' => $this->getSetting('allow_existing'),
757 '#submit' => [[get_class($this), 'submitConfirmRemove']],
758 '#ief_row_delta' => $form['#ief_row_delta'],
760 $form['actions']['ief_remove_cancel'] = [
762 '#value' => $this->t('Cancel'),
763 '#name' => 'ief-remove-cancel-' . $delta,
764 '#limit_validation_errors' => [],
766 'callback' => 'inline_entity_form_get_element',
767 'wrapper' => 'inline-entity-form-' . $form['#ief_id'],
769 '#submit' => [[get_class($this), 'submitCloseRow']],
770 '#ief_row_delta' => $form['#ief_row_delta'],
775 * Button #submit callback: Closes a row form in the IEF widget.
778 * The complete parent form.
780 * The form state of the parent form.
782 * @see inline_entity_form_open_row_form()
784 public static function submitCloseRow($form, FormStateInterface $form_state) {
785 $element = inline_entity_form_get_element($form, $form_state);
786 $ief_id = $element['#ief_id'];
787 $delta = $form_state->getTriggeringElement()['#ief_row_delta'];
789 $form_state->setRebuild();
790 $form_state->set(['inline_entity_form', $ief_id, 'entities', $delta, 'form'], NULL);
795 * Remove form submit callback.
797 * The row is identified by #ief_row_delta stored on the triggering
799 * This isn't an #element_validate callback to avoid processing the
800 * remove form when the main form is submitted.
803 * The complete parent form.
805 * The form state of the parent form.
807 public static function submitConfirmRemove($form, FormStateInterface $form_state) {
808 $element = inline_entity_form_get_element($form, $form_state);
809 $remove_button = $form_state->getTriggeringElement();
810 $delta = $remove_button['#ief_row_delta'];
812 /** @var \Drupal\Core\Field\FieldDefinitionInterface $instance */
813 $instance = $form_state->get(['inline_entity_form', $element['#ief_id'], 'instance']);
815 /** @var \Drupal\Core\Entity\EntityInterface $entity */
816 $entity = $element['entities'][$delta]['form']['#entity'];
817 $entity_id = $entity->id();
819 $form_values = NestedArray::getValue($form_state->getValues(), $element['entities'][$delta]['form']['#parents']);
820 $form_state->setRebuild();
822 $widget_state = $form_state->get(['inline_entity_form', $element['#ief_id']]);
823 // This entity hasn't been saved yet, we can just unlink it.
824 if (empty($entity_id) || ($remove_button['#allow_existing'] && empty($form_values['delete']))) {
825 unset($widget_state['entities'][$delta]);
828 $widget_state['delete'][] = $entity;
829 unset($widget_state['entities'][$delta]);
831 $form_state->set(['inline_entity_form', $element['#ief_id']], $widget_state);
835 * Determines bundle to be used when creating entity.
837 * @param \Drupal\Core\Form\FormStateInterface $form_state
838 * Current form state.
841 * Bundle machine name.
843 * @TODO - Figure out if can be simplified.
845 protected function determineBundle(FormStateInterface $form_state) {
846 $ief_settings = $form_state->get(['inline_entity_form', $this->getIefId()]);
847 if (!empty($ief_settings['form settings']['bundle'])) {
848 return $ief_settings['form settings']['bundle'];
850 elseif (!empty($ief_settings['bundle'])) {
851 return $ief_settings['bundle'];
854 $target_bundles = $this->getTargetBundles();
855 return reset($target_bundles);
860 * Updates entity weights based on their weights in the widget.
862 public static function updateRowWeights($element, FormStateInterface $form_state, $form) {
863 $ief_id = $element['#ief_id'];
865 // Loop over the submitted delta values and update the weight of the entities
866 // in the form state.
867 foreach (Element::children($element['entities']) as $key) {
868 $form_state->set(['inline_entity_form', $ief_id, 'entities', $key, 'weight'], $element['entities'][$key]['delta']['#value']);
873 * IEF widget #element_validate callback: Required field validation.
875 public static function requiredField($element, FormStateInterface $form_state, $form) {
876 $ief_id = $element['#ief_id'];
877 $children = $form_state->get(['inline_entity_form', $ief_id, 'entities']);
878 $has_children = !empty($children);
879 $form = $form_state->get(['inline_entity_form', $ief_id, 'form']);
880 $form_open = !empty($form);
881 // If the add new / add existing form is open, its validation / submission
882 // will do the job instead (either by preventing the parent form submission
883 // or by adding a new referenced entity).
884 if (!$has_children && !$form_open) {
885 /** @var \Drupal\Core\Field\FieldDefinitionInterface $instance */
886 $instance = $form_state->get(['inline_entity_form', $ief_id, 'instance']);
887 $form_state->setError($element, t('@name field is required.', ['@name' => $instance->getLabel()]));
892 * Button #submit callback: Closes a form in the IEF widget.
895 * The complete parent form.
897 * The form state of the parent form.
899 * @see inline_entity_form_open_form()
901 public static function closeForm($form, FormStateInterface $form_state) {
902 $element = inline_entity_form_get_element($form, $form_state);
903 $ief_id = $element['#ief_id'];
905 $form_state->setRebuild();
906 $form_state->set(['inline_entity_form', $ief_id, 'form'], NULL);
910 * Add common submit callback functions and mark element as a IEF trigger.
914 public static function addSubmitCallbacks(&$element) {
915 $element['#submit'] = [
916 ['\Drupal\inline_entity_form\ElementSubmit', 'trigger'],
917 ['\Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormComplex', 'closeForm'],
919 $element['#ief_submit_trigger'] = TRUE;
923 * Button #submit callback: Closes all open child forms in the IEF widget.
925 * Used to ensure that forms in nested IEF widgets are properly closed
926 * when a parent IEF's form gets submitted or cancelled.
929 * The IEF Form element.
930 * @param \Drupal\Core\Form\FormStateInterface $form_state
931 * The form state of the parent form.
933 public static function closeChildForms($form, FormStateInterface &$form_state) {
934 $element = inline_entity_form_get_element($form, $form_state);
935 inline_entity_form_close_all_forms($element, $form_state);