Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / inline_entity_form / src / Plugin / Field / FieldWidget / InlineEntityFormComplex.php
1 <?php
2
3 namespace Drupal\inline_entity_form\Plugin\Field\FieldWidget;
4
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;
17
18 /**
19  * Complex inline widget.
20  *
21  * @FieldWidget(
22  *   id = "inline_entity_form_complex",
23  *   label = @Translation("Inline entity form - Complex"),
24  *   field_types = {
25  *     "entity_reference"
26  *   },
27  *   multiple_values = true
28  * )
29  */
30 class InlineEntityFormComplex extends InlineEntityFormBase implements ContainerFactoryPluginInterface {
31
32   /**
33    * Module handler service.
34    *
35    * @var \Drupal\Core\Extension\ModuleHandlerInterface
36    */
37   protected $moduleHandler;
38
39   /**
40    * Constructs a InlineEntityFormComplex object.
41    *
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.
60    */
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;
64   }
65
66   /**
67    * {@inheritdoc}
68    */
69   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
70     return new static(
71       $plugin_id,
72       $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')
80     );
81   }
82
83   /**
84    * {@inheritdoc}
85    */
86   public static function defaultSettings() {
87     $defaults = parent::defaultSettings();
88     $defaults += [
89       'allow_new' => TRUE,
90       'allow_existing' => FALSE,
91       'match_operator' => 'CONTAINS',
92       'allow_duplicate' => FALSE,
93     ];
94
95     return $defaults;
96   }
97
98   /**
99    * {@inheritdoc}
100    */
101   public function settingsForm(array $form, FormStateInterface $form_state) {
102     $element = parent::settingsForm($form, $form_state);
103
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'),
110     ];
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'),
115     ];
116     $element['match_operator'] = [
117       '#type' => 'select',
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.'),
122       '#states' => [
123         'visible' => [
124           ':input[name="' . $states_prefix . '[allow_existing]"]' => ['checked' => TRUE],
125         ],
126       ],
127     ];
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'),
132     ];
133
134     return $element;
135   }
136
137   /**
138    * {@inheritdoc}
139    */
140   public function settingsSummary() {
141     $summary = parent::settingsSummary();
142     $labels = $this->getEntityTypeLabels();
143
144     if ($this->getSetting('allow_new')) {
145       $summary[] = $this->t('New @label can be added.', ['@label' => $labels['plural']]);
146     }
147     else {
148       $summary[] = $this->t('New @label can not be created.', ['@label' => $labels['plural']]);
149     }
150
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')],
156       ]);
157     }
158     else {
159       $summary[] = $this->t('Existing @label can not be referenced.', ['@label' => $labels['plural']]);
160     }
161
162     if ($this->getSetting('allow_duplicate')) {
163       $summary[] = $this->t('@label can be duplicated.', ['@label' => $labels['plural']]);
164     }
165     else {
166       $summary[] = $this->t('@label can not be duplicated.', ['@label' => $labels['plural']]);
167     }
168
169     return $summary;
170   }
171
172   /**
173    * Returns the options for the match operator.
174    *
175    * @return array
176    *   List of options.
177    */
178   protected function getMatchOperatorOptions() {
179     return [
180       'STARTS_WITH' => $this->t('Starts with'),
181       'CONTAINS' => $this->t('Contains'),
182     ];
183   }
184
185   /**
186    * {@inheritdoc}
187    */
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();
193
194     // Build a parents array for this element's values in the form.
195     $parents = array_merge($element['#field_parents'], [
196       $items->getName(),
197       'form',
198     ]);
199
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)));
204
205     // Get the langcode of the parent entity.
206     $parent_langcode = $items->getEntity()->language()->getId();
207
208     // Determine the wrapper ID for the entire element.
209     $wrapper = 'inline-entity-form-' . $this->getIefId();
210
211     $element = [
212       '#type' => $this->getSetting('collapsible') ? 'details' : 'fieldset',
213       '#tree' => TRUE,
214       '#description' => $this->fieldDefinition->getDescription(),
215       '#prefix' => '<div id="' . $wrapper . '">',
216       '#suffix' => '</div>',
217       '#ief_id' => $this->getIefId(),
218       '#ief_root' => TRUE,
219       '#translating' => $this->isTranslating($form_state),
220       '#field_title' => $this->fieldDefinition->getLabel(),
221       '#after_build' => [
222         [get_class($this), 'removeTranslatabilityClue'],
223       ],
224     ] + $element;
225     if ($element['#type'] == 'details') {
226       $element['#open'] = !$this->getSetting('collapsed');
227     }
228
229     $element['#attached']['library'][] = 'inline_entity_form/widget';
230
231     $this->prepareFormState($form_state, $items, $element['#translating']);
232     $entities = $form_state->get(['inline_entity_form', $this->getIefId(), 'entities']);
233
234     // Prepare cardinality information.
235     $entities_count = count($entities);
236     $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
237     $cardinality_reached = ($cardinality > 0 && $entities_count == $cardinality);
238
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'];
245     }
246
247     $element['entities'] = [
248       '#tree' => TRUE,
249       '#theme' => 'inline_entity_form_entity_table',
250       '#entity_type' => $target_type,
251     ];
252
253     // Get the fields that should be displayed in the table.
254     $target_bundles = $this->getTargetBundles();
255     $fields = $this->inlineFormHandler->getTableFields($target_bundles);
256     $context = [
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,
262     ];
263     $this->moduleHandler->alter('inline_entity_form_table_fields', $fields, $context);
264     $element['entities']['#table_fields'] = $fields;
265
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'];
274
275       // Handle row weights.
276       $element['entities'][$key]['#weight'] = $value['weight'];
277
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'] = [
282           '#type' => 'value',
283           '#value' => $value['weight'],
284         ];
285
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(
292               $value['form'],
293               $entity->bundle(),
294               $parent_langcode,
295               $key,
296               array_merge($parents, ['inline_entity_form', 'entities', $key, 'form']),
297               $value['form'] == 'edit' ? $entity : $entity->createDuplicate()
298             ),
299           ];
300
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'],
305           ];
306         }
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,
320           ];
321           $this->buildRemoveForm($element['entities'][$key]['form']);
322         }
323       }
324       else {
325         $row = &$element['entities'][$key];
326         $row['title'] = [];
327         $row['delta'] = [
328           '#type' => 'weight',
329           '#delta' => $weight_delta,
330           '#default_value' => $value['weight'],
331           '#attributes' => ['class' => ['ief-entity-delta']],
332         ];
333         // Add an actions container with edit and delete buttons for the entity.
334         $row['actions'] = [
335           '#type' => 'container',
336           '#attributes' => ['class' => ['ief-entity-operations']],
337         ];
338
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'] = [
343             '#type' => 'submit',
344             '#value' => $this->t('Edit'),
345             '#name' => 'ief-' . $this->getIefId() . '-entity-edit-' . $key,
346             '#limit_validation_errors' => [],
347             '#ajax' => [
348               'callback' => 'inline_entity_form_get_element',
349               'wrapper' => $wrapper,
350             ],
351             '#submit' => ['inline_entity_form_open_row_form'],
352             '#ief_row_delta' => $key,
353             '#ief_row_form' => 'edit',
354           ];
355         }
356
357         // Add the duplicate button, if allowed.
358         if ($settings['allow_duplicate'] && !$cardinality_reached && $entity->access('create')) {
359           $row['actions']['ief_entity_duplicate'] = [
360             '#type' => 'submit',
361             '#value' => $this->t('Duplicate'),
362             '#name' => 'ief-' . $this->getIefId() . '-entity-duplicate-' . $key,
363             '#limit_validation_errors' => [array_merge($parents, ['actions'])],
364             '#ajax' => [
365               'callback' => 'inline_entity_form_get_element',
366               'wrapper' => $wrapper,
367             ],
368             '#submit' => ['inline_entity_form_open_row_form'],
369             '#ief_row_delta' => $key,
370             '#ief_row_form' => 'duplicate',
371           ];
372         }
373
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'] = [
379             '#type' => 'submit',
380             '#value' => $this->t('Remove'),
381             '#name' => 'ief-' . $this->getIefId() . '-entity-remove-' . $key,
382             '#limit_validation_errors' => [],
383             '#ajax' => [
384               'callback' => 'inline_entity_form_get_element',
385               'wrapper' => $wrapper,
386             ],
387             '#submit' => ['inline_entity_form_open_row_form'],
388             '#ief_row_delta' => $key,
389             '#ief_row_form' => 'remove',
390             '#access' => !$element['#translating'],
391           ];
392         }
393       }
394     }
395
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;
402       }
403       return $element;
404     }
405
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'],
412       ]);
413       $element['cardinality_count'] = [
414         '#markup' => '<div class="ief-cardinality-count">' . $message . '</div>',
415       ];
416     }
417     // Do not return the rest of the form if cardinality count has been reached.
418     if ($cardinality_reached) {
419       return $element;
420     }
421
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');
430         $hide_cancel = TRUE;
431       }
432       elseif ($create_bundles_count == 1 && $allow_new && !$settings['allow_existing']) {
433         $bundle = reset($target_bundles);
434
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'], [
442             'bundle' => $bundle,
443           ]);
444           $hide_cancel = TRUE;
445         }
446       }
447     }
448
449     // If no form is open, show buttons that open one.
450     $open_form = $form_state->get(['inline_entity_form', $this->getIefId(), 'form']);
451
452     if (empty($open_form)) {
453       $element['actions'] = [
454         '#attributes' => ['class' => ['container-inline']],
455         '#type' => 'container',
456         '#weight' => 100,
457       ];
458
459       // The user is allowed to create an entity of at least one bundle.
460       if ($allow_new) {
461         // Let the user select the bundle, if multiple are available.
462         if ($create_bundles_count > 1) {
463           $bundles = [];
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'];
467             }
468           }
469           asort($bundles);
470
471           $element['actions']['bundle'] = [
472             '#type' => 'select',
473             '#options' => $bundles,
474           ];
475         }
476         else {
477           $element['actions']['bundle'] = [
478             '#type' => 'value',
479             '#value' => reset($create_bundles),
480           ];
481         }
482
483         $element['actions']['ief_add'] = [
484           '#type' => 'submit',
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'])],
488           '#ajax' => [
489             'callback' => 'inline_entity_form_get_element',
490             'wrapper' => $wrapper,
491           ],
492           '#submit' => ['inline_entity_form_open_form'],
493           '#ief_form' => 'add',
494         ];
495       }
496
497       if ($settings['allow_existing']) {
498         $element['actions']['ief_add_existing'] = [
499           '#type' => 'submit',
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'])],
503           '#ajax' => [
504             'callback' => 'inline_entity_form_get_element',
505             'wrapper' => $wrapper,
506           ],
507           '#submit' => ['inline_entity_form_open_form'],
508           '#ief_form' => 'ief_add_existing',
509         ];
510       }
511     }
512     else {
513       // There's a form open, show it.
514       if ($form_state->get(['inline_entity_form', $this->getIefId(), 'form']) == 'add') {
515         $element['form'] = [
516           '#type' => 'fieldset',
517           '#attributes' => ['class' => ['ief-form', 'ief-form-bottom']],
518           'inline_entity_form' => $this->getInlineEntityForm(
519             'add',
520             $this->determineBundle($form_state),
521             $parent_langcode,
522             NULL,
523             array_merge($parents, ['inline_entity_form'])
524           )
525         ];
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'],
530         ];
531       }
532       elseif ($form_state->get(['inline_entity_form', $this->getIefId(), 'form']) == 'ief_add_existing') {
533         $element['form'] = [
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'),
544         ];
545
546         $element['form'] += inline_entity_form_reference_form($element['form'], $form_state);
547       }
548
549       // Pre-opened forms can't be closed in order to force the user to
550       // add / reference an entity.
551       if ($hide_cancel) {
552         if ($open_form == 'add') {
553           $process_element = &$element['form']['inline_entity_form'];
554         }
555         elseif ($open_form == 'ief_add_existing') {
556           $process_element = &$element['form'];
557         }
558         $process_element['#process'][] = [get_class($this), 'hideCancel'];
559       }
560
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';
565       }
566     }
567
568     return $element;
569   }
570
571   /**
572    * {@inheritdoc}
573    */
574   public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
575     if ($this->isDefaultValueWidget($form_state)) {
576       $items->filterEmptyItems();
577       return;
578     }
579     $triggering_element = $form_state->getTriggeringElement();
580     if (empty($triggering_element['#ief_submit_trigger'])) {
581       return;
582     }
583
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);
591       if ($changed) {
592         $widget_state['entities'][$key]['entity'] = $value['entity'];
593         $widget_state['entities'][$key]['needs_save'] = TRUE;
594       }
595     }
596
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];
606       }
607     }
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();
615   }
616
617   /**
618    * Adds actions to the inline entity form.
619    *
620    * @param array $element
621    *   Form array structure.
622    */
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']]);
628     }
629     elseif ($element['#op'] == 'duplicate') {
630       $save_label = t('Duplicate @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]);
631     }
632     else {
633       $delta .= '-' . $element['#ief_row_delta'];
634       $save_label = t('Update @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]);
635     }
636
637     // Add action submit elements.
638     $element['actions'] = [
639       '#type' => 'container',
640       '#weight' => 100,
641     ];
642     $element['actions']['ief_' . $element['#op'] . '_save'] = [
643       '#type' => 'submit',
644       '#value' => $save_label,
645       '#name' => 'ief-' . $element['#op'] . '-submit-' . $delta,
646       '#limit_validation_errors' => [$element['#parents']],
647       '#attributes' => ['class' => ['ief-entity-submit']],
648       '#ajax' => [
649         'callback' => 'inline_entity_form_get_element',
650         'wrapper' => 'inline-entity-form-' . $element['#ief_id'],
651       ],
652     ];
653     $element['actions']['ief_' . $element['#op'] . '_cancel'] = [
654       '#type' => 'submit',
655       '#value' => t('Cancel'),
656       '#name' => 'ief-' . $element['#op'] . '-cancel-' . $delta,
657       '#limit_validation_errors' => [],
658       '#ajax' => [
659         'callback' => 'inline_entity_form_get_element',
660         'wrapper' => 'inline-entity-form-' . $element['#ief_id'],
661       ],
662     ];
663
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',
671       ];
672     }
673     else {
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'];
676
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',
683       ];
684     }
685
686     return $element;
687   }
688
689   /**
690    * Hides cancel button.
691    *
692    * @param array $element
693    *   Form array structure.
694    */
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;
699     }
700     elseif (isset($element['actions']['ief_reference_cancel'])) {
701       $element['actions']['ief_reference_cancel']['#access'] = FALSE;
702     }
703
704     return $element;
705   }
706
707   /**
708    * Builds remove form.
709    *
710    * @param array $form
711    *   Form array structure.
712    */
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();
719
720     if ($entity_label) {
721       $message = $this->t('Are you sure you want to remove %label?', ['%label' => $entity_label]);
722     }
723     else {
724       $message = $this->t('Are you sure you want to remove this %entity_type?', ['%entity_type' => $labels['singular']]);
725     }
726
727     $form['message'] = [
728       '#theme_wrappers' => ['container'],
729       '#markup' => $message,
730     ];
731
732     if (!empty($entity_id) && $this->getSetting('allow_existing') && $entity->access('delete')) {
733       $form['delete'] = [
734         '#type' => 'checkbox',
735         '#title' => $this->t('Delete this @type_singular from the system.', ['@type_singular' => $labels['singular']]),
736       ];
737     }
738
739     // Build a deta suffix that's appended to button #name keys for uniqueness.
740     $delta = $form['#ief_id'] . '-' . $form['#ief_row_delta'];
741
742     // Add actions to the form.
743     $form['actions'] = [
744       '#type' => 'container',
745       '#weight' => 100,
746     ];
747     $form['actions']['ief_remove_confirm'] = [
748       '#type' => 'submit',
749       '#value' => $this->t('Remove'),
750       '#name' => 'ief-remove-confirm-' . $delta,
751       '#limit_validation_errors' => [$form['#parents']],
752       '#ajax' => [
753         'callback' => 'inline_entity_form_get_element',
754         'wrapper' => 'inline-entity-form-' . $form['#ief_id'],
755       ],
756       '#allow_existing' => $this->getSetting('allow_existing'),
757       '#submit' => [[get_class($this), 'submitConfirmRemove']],
758       '#ief_row_delta' => $form['#ief_row_delta'],
759     ];
760     $form['actions']['ief_remove_cancel'] = [
761       '#type' => 'submit',
762       '#value' => $this->t('Cancel'),
763       '#name' => 'ief-remove-cancel-' . $delta,
764       '#limit_validation_errors' => [],
765       '#ajax' => [
766         'callback' => 'inline_entity_form_get_element',
767         'wrapper' => 'inline-entity-form-' . $form['#ief_id'],
768       ],
769       '#submit' => [[get_class($this), 'submitCloseRow']],
770       '#ief_row_delta' => $form['#ief_row_delta'],
771     ];
772   }
773
774   /**
775    * Button #submit callback: Closes a row form in the IEF widget.
776    *
777    * @param $form
778    *   The complete parent form.
779    * @param $form_state
780    *   The form state of the parent form.
781    *
782    * @see inline_entity_form_open_row_form()
783    */
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'];
788
789     $form_state->setRebuild();
790     $form_state->set(['inline_entity_form', $ief_id, 'entities', $delta, 'form'], NULL);
791   }
792
793
794   /**
795    * Remove form submit callback.
796    *
797    * The row is identified by #ief_row_delta stored on the triggering
798    * element.
799    * This isn't an #element_validate callback to avoid processing the
800    * remove form when the main form is submitted.
801    *
802    * @param $form
803    *   The complete parent form.
804    * @param $form_state
805    *   The form state of the parent form.
806    */
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'];
811
812     /** @var \Drupal\Core\Field\FieldDefinitionInterface $instance */
813     $instance = $form_state->get(['inline_entity_form', $element['#ief_id'], 'instance']);
814
815     /** @var \Drupal\Core\Entity\EntityInterface $entity */
816     $entity = $element['entities'][$delta]['form']['#entity'];
817     $entity_id = $entity->id();
818
819     $form_values = NestedArray::getValue($form_state->getValues(), $element['entities'][$delta]['form']['#parents']);
820     $form_state->setRebuild();
821
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]);
826     }
827     else {
828       $widget_state['delete'][] = $entity;
829       unset($widget_state['entities'][$delta]);
830     }
831     $form_state->set(['inline_entity_form', $element['#ief_id']], $widget_state);
832   }
833
834   /**
835    * Determines bundle to be used when creating entity.
836    *
837    * @param \Drupal\Core\Form\FormStateInterface $form_state
838    *   Current form state.
839    *
840    * @return string
841    *   Bundle machine name.
842    *
843    * @TODO - Figure out if can be simplified.
844    */
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'];
849     }
850     elseif (!empty($ief_settings['bundle'])) {
851       return $ief_settings['bundle'];
852     }
853     else {
854       $target_bundles = $this->getTargetBundles();
855       return reset($target_bundles);
856     }
857   }
858
859   /**
860    * Updates entity weights based on their weights in the widget.
861    */
862   public static function updateRowWeights($element, FormStateInterface $form_state, $form) {
863     $ief_id = $element['#ief_id'];
864
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']);
869     }
870   }
871
872   /**
873    * IEF widget #element_validate callback: Required field validation.
874    */
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()]));
888     }
889   }
890
891   /**
892    * Button #submit callback: Closes a form in the IEF widget.
893    *
894    * @param $form
895    *   The complete parent form.
896    * @param $form_state
897    *   The form state of the parent form.
898    *
899    * @see inline_entity_form_open_form()
900    */
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'];
904
905     $form_state->setRebuild();
906     $form_state->set(['inline_entity_form', $ief_id, 'form'], NULL);
907   }
908
909   /**
910    * Add common submit callback functions and mark element as a IEF trigger.
911    *
912    * @param $element
913    */
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'],
918     ];
919     $element['#ief_submit_trigger']  = TRUE;
920   }
921
922   /**
923    * Button #submit callback:  Closes all open child forms in the IEF widget.
924    *
925    * Used to ensure that forms in nested IEF widgets are properly closed
926    * when a parent IEF's form gets submitted or cancelled.
927    *
928    * @param $form
929    *   The IEF Form element.
930    * @param \Drupal\Core\Form\FormStateInterface $form_state
931    *   The form state of the parent form.
932    */
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);
936   }
937
938 }