Version 1
[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 InlineEntityFormBase 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
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     ];
93
94     return $defaults;
95   }
96
97   /**
98    * {@inheritdoc}
99    */
100   public function settingsForm(array $form, FormStateInterface $form_state) {
101     $element = parent::settingsForm($form, $form_state);
102
103     $labels = $this->getEntityTypeLabels();
104     $states_prefix = 'fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings]';
105     $element['allow_new'] = [
106       '#type' => 'checkbox',
107       '#title' => $this->t('Allow users to add new @label.', ['@label' => $labels['plural']]),
108       '#default_value' => $this->getSetting('allow_new'),
109     ];
110     $element['allow_existing'] = [
111       '#type' => 'checkbox',
112       '#title' => $this->t('Allow users to add existing @label.', ['@label' => $labels['plural']]),
113       '#default_value' => $this->getSetting('allow_existing'),
114     ];
115     $element['match_operator'] = [
116       '#type' => 'select',
117       '#title' => $this->t('Autocomplete matching'),
118       '#default_value' => $this->getSetting('match_operator'),
119       '#options' => $this->getMatchOperatorOptions(),
120       '#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.'),
121       '#states' => [
122         'visible' => [
123           ':input[name="' . $states_prefix . '[allow_existing]"]' => ['checked' => TRUE],
124         ],
125       ],
126     ];
127
128     return $element;
129   }
130
131   /**
132    * {@inheritdoc}
133    */
134   public function settingsSummary() {
135     $summary = parent::settingsSummary();
136     $labels = $this->getEntityTypeLabels();
137
138     if ($this->getSetting('allow_new')) {
139       $summary[] = $this->t('New @label can be added.', ['@label' => $labels['plural']]);
140     }
141     else {
142       $summary[] = $this->t('New @label can not be created.', ['@label' => $labels['plural']]);
143     }
144
145     $match_operator_options = $this->getMatchOperatorOptions();
146     if ($this->getSetting('allow_existing')) {
147       $summary[] = $this->t('Existing @label can be referenced and are matched with the %operator operator.', [
148         '@label' => $labels['plural'],
149         '%operator' => $match_operator_options[$this->getSetting('match_operator')],
150       ]);
151     }
152     else {
153       $summary[] = $this->t('Existing @label can not be referenced.', ['@label' => $labels['plural']]);
154     }
155
156     return $summary;
157   }
158
159   /**
160    * Returns the options for the match operator.
161    *
162    * @return array
163    *   List of options.
164    */
165   protected function getMatchOperatorOptions() {
166     return [
167       'STARTS_WITH' => $this->t('Starts with'),
168       'CONTAINS' => $this->t('Contains'),
169     ];
170   }
171
172   /**
173    * {@inheritdoc}
174    */
175   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
176     $settings = $this->getSettings();
177     $target_type = $this->getFieldSetting('target_type');
178     // Get the entity type labels for the UI strings.
179     $labels = $this->getEntityTypeLabels();
180
181     // Build a parents array for this element's values in the form.
182     $parents = array_merge($element['#field_parents'], [
183       $items->getName(),
184       'form',
185     ]);
186
187     // Assign a unique identifier to each IEF widget.
188     // Since $parents can get quite long, sha1() ensures that every id has
189     // a consistent and relatively short length while maintaining uniqueness.
190     $this->setIefId(sha1(implode('-', $parents)));
191
192     // Get the langcode of the parent entity.
193     $parent_langcode = $items->getEntity()->language()->getId();
194
195     // Determine the wrapper ID for the entire element.
196     $wrapper = 'inline-entity-form-' . $this->getIefId();
197
198     $element = [
199       '#type' => 'fieldset',
200       '#tree' => TRUE,
201       '#description' => $this->fieldDefinition->getDescription(),
202       '#prefix' => '<div id="' . $wrapper . '">',
203       '#suffix' => '</div>',
204       '#ief_id' => $this->getIefId(),
205       '#ief_root' => TRUE,
206       '#translating' => $this->isTranslating($form_state),
207       '#field_title' => $this->fieldDefinition->getLabel(),
208       '#after_build' => [
209         [get_class($this), 'removeTranslatabilityClue'],
210       ],
211     ] + $element;
212
213     $element['#attached']['library'][] = 'inline_entity_form/widget';
214
215     $this->prepareFormState($form_state, $items, $element['#translating']);
216     $entities = $form_state->get(['inline_entity_form', $this->getIefId(), 'entities']);
217
218     // Build the "Multiple value" widget.
219     // TODO - does this belong in #element_validate?
220     $element['#element_validate'][] = [get_class($this), 'updateRowWeights'];
221     // Add the required element marker & validation.
222     if ($element['#required']) {
223       $element['#element_validate'][] = [get_class($this), 'requiredField'];
224     }
225
226     $element['entities'] = [
227       '#tree' => TRUE,
228       '#theme' => 'inline_entity_form_entity_table',
229       '#entity_type' => $target_type,
230     ];
231
232     // Get the fields that should be displayed in the table.
233     $target_bundles = $this->getTargetBundles();
234     $fields = $this->inlineFormHandler->getTableFields($target_bundles);
235     $context = [
236       'parent_entity_type' => $this->fieldDefinition->getTargetEntityTypeId(),
237       'parent_bundle' => $this->fieldDefinition->getTargetBundle(),
238       'field_name' => $this->fieldDefinition->getName(),
239       'entity_type' => $target_type,
240       'allowed_bundles' => $target_bundles,
241     ];
242     $this->moduleHandler->alter('inline_entity_form_table_fields', $fields, $context);
243     $element['entities']['#table_fields'] = $fields;
244
245     $weight_delta = max(ceil(count($entities) * 1.2), 50);
246     foreach ($entities as $key => $value) {
247       // Data used by theme_inline_entity_form_entity_table().
248       /** @var \Drupal\Core\Entity\EntityInterface $entity */
249       $entity = $value['entity'];
250       $element['entities'][$key]['#label'] = $this->inlineFormHandler->getEntityLabel($value['entity']);
251       $element['entities'][$key]['#entity'] = $value['entity'];
252       $element['entities'][$key]['#needs_save'] = $value['needs_save'];
253
254       // Handle row weights.
255       $element['entities'][$key]['#weight'] = $value['weight'];
256
257       // First check to see if this entity should be displayed as a form.
258       if (!empty($value['form'])) {
259         $element['entities'][$key]['title'] = [];
260         $element['entities'][$key]['delta'] = [
261           '#type' => 'value',
262           '#value' => $value['weight'],
263         ];
264
265         // Add the appropriate form.
266         if ($value['form'] == 'edit') {
267           $element['entities'][$key]['form'] = [
268             '#type' => 'container',
269             '#attributes' => ['class' => ['ief-form', 'ief-form-row']],
270             'inline_entity_form' => $this->getInlineEntityForm(
271               $value['form'],
272               $entity->bundle(),
273               $parent_langcode,
274               $key,
275               array_merge($parents,  ['inline_entity_form', 'entities', $key, 'form']),
276               $entity
277             ),
278           ];
279
280           $element['entities'][$key]['form']['inline_entity_form']['#process'] = [
281             ['\Drupal\inline_entity_form\Element\InlineEntityForm', 'processEntityForm'],
282             [get_class($this), 'addIefSubmitCallbacks'],
283             [get_class($this), 'buildEntityFormActions'],
284           ];
285         }
286         elseif ($value['form'] == 'remove') {
287           $element['entities'][$key]['form'] = [
288             '#type' => 'container',
289             '#attributes' => ['class' => ['ief-form', 'ief-form-row']],
290             // Used by Field API and controller methods to find the relevant
291             // values in $form_state.
292             '#parents' => array_merge($parents, ['entities', $key, 'form']),
293             // Store the entity on the form, later modified in the controller.
294             '#entity' => $entity,
295             // Identifies the IEF widget to which the form belongs.
296             '#ief_id' => $this->getIefId(),
297             // Identifies the table row to which the form belongs.
298             '#ief_row_delta' => $key,
299           ];
300           $this->buildRemoveForm($element['entities'][$key]['form']);
301         }
302       }
303       else {
304         $row = &$element['entities'][$key];
305         $row['title'] = [];
306         $row['delta'] = [
307           '#type' => 'weight',
308           '#delta' => $weight_delta,
309           '#default_value' => $value['weight'],
310           '#attributes' => ['class' => ['ief-entity-delta']],
311         ];
312         // Add an actions container with edit and delete buttons for the entity.
313         $row['actions'] = [
314           '#type' => 'container',
315           '#attributes' => ['class' => ['ief-entity-operations']],
316         ];
317
318         // Make sure entity_access is not checked for unsaved entities.
319         $entity_id = $entity->id();
320         if (empty($entity_id) || $entity->access('update')) {
321           $row['actions']['ief_entity_edit'] = [
322             '#type' => 'submit',
323             '#value' => $this->t('Edit'),
324             '#name' => 'ief-' . $this->getIefId() . '-entity-edit-' . $key,
325             '#limit_validation_errors' => [],
326             '#ajax' => [
327               'callback' => 'inline_entity_form_get_element',
328               'wrapper' => $wrapper,
329             ],
330             '#submit' => ['inline_entity_form_open_row_form'],
331             '#ief_row_delta' => $key,
332             '#ief_row_form' => 'edit',
333           ];
334         }
335
336         // If 'allow_existing' is on, the default removal operation is unlink
337         // and the access check for deleting happens inside the controller
338         // removeForm() method.
339         if (empty($entity_id) || $settings['allow_existing'] || $entity->access('delete')) {
340           $row['actions']['ief_entity_remove'] = [
341             '#type' => 'submit',
342             '#value' => $this->t('Remove'),
343             '#name' => 'ief-' . $this->getIefId() . '-entity-remove-' . $key,
344             '#limit_validation_errors' => [],
345             '#ajax' => [
346               'callback' => 'inline_entity_form_get_element',
347               'wrapper' => $wrapper,
348             ],
349             '#submit' => ['inline_entity_form_open_row_form'],
350             '#ief_row_delta' => $key,
351             '#ief_row_form' => 'remove',
352             '#access' => !$element['#translating'],
353           ];
354         }
355       }
356     }
357
358     // When in translation, the widget only supports editing (translating)
359     // already added entities, so there's no need to show the rest.
360     if ($element['#translating']) {
361       if (empty($entities)) {
362         // There are no entities available for translation, hide the widget.
363         $element['#access'] = FALSE;
364       }
365       return $element;
366     }
367
368     $entities_count = count($entities);
369     $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
370     if ($cardinality > 1) {
371       // Add a visual cue of cardinality count.
372       $message = $this->t('You have added @entities_count out of @cardinality_count allowed @label.', [
373         '@entities_count' => $entities_count,
374         '@cardinality_count' => $cardinality,
375         '@label' => $labels['plural'],
376       ]);
377       $element['cardinality_count'] = [
378         '#markup' => '<div class="ief-cardinality-count">' . $message . '</div>',
379       ];
380     }
381     // Do not return the rest of the form if cardinality count has been reached.
382     if ($cardinality > 0 && $entities_count == $cardinality) {
383       return $element;
384     }
385
386     $create_bundles = $this->getCreateBundles();
387     $create_bundles_count = count($create_bundles);
388     $allow_new = $settings['allow_new'] && !empty($create_bundles);
389     $hide_cancel = FALSE;
390     // If the field is required and empty try to open one of the forms.
391     if (empty($entities) && $this->fieldDefinition->isRequired()) {
392       if ($settings['allow_existing'] && !$allow_new) {
393         $form_state->set(['inline_entity_form', $this->getIefId(), 'form'], 'ief_add_existing');
394         $hide_cancel = TRUE;
395       }
396       elseif ($create_bundles_count == 1 && $allow_new && !$settings['allow_existing']) {
397         $bundle = reset($target_bundles);
398
399         // The parent entity type and bundle must not be the same as the inline
400         // entity type and bundle, to prevent recursion.
401         $parent_entity_type = $this->fieldDefinition->getTargetEntityTypeId();
402         $parent_bundle =  $this->fieldDefinition->getTargetBundle();
403         if ($parent_entity_type != $target_type || $parent_bundle != $bundle) {
404           $form_state->set(['inline_entity_form', $this->getIefId(), 'form'], 'add');
405           $form_state->set(['inline_entity_form', $this->getIefId(), 'form settings'], [
406             'bundle' => $bundle,
407           ]);
408           $hide_cancel = TRUE;
409         }
410       }
411     }
412
413     // If no form is open, show buttons that open one.
414     $open_form = $form_state->get(['inline_entity_form', $this->getIefId(), 'form']);
415
416     if (empty($open_form)) {
417       $element['actions'] = [
418         '#attributes' => ['class' => ['container-inline']],
419         '#type' => 'container',
420         '#weight' => 100,
421       ];
422
423       // The user is allowed to create an entity of at least one bundle.
424       if ($allow_new) {
425         // Let the user select the bundle, if multiple are available.
426         if ($create_bundles_count > 1) {
427           $bundles = [];
428           foreach ($this->entityTypeBundleInfo->getBundleInfo($target_type) as $bundle_name => $bundle_info) {
429             if (in_array($bundle_name, $create_bundles)) {
430               $bundles[$bundle_name] = $bundle_info['label'];
431             }
432           }
433           asort($bundles);
434
435           $element['actions']['bundle'] = [
436             '#type' => 'select',
437             '#options' => $bundles,
438           ];
439         }
440         else {
441           $element['actions']['bundle'] = [
442             '#type' => 'value',
443             '#value' => reset($create_bundles),
444           ];
445         }
446
447         $element['actions']['ief_add'] = [
448           '#type' => 'submit',
449           '#value' => $this->t('Add new @type_singular', ['@type_singular' => $labels['singular']]),
450           '#name' => 'ief-' . $this->getIefId() . '-add',
451           '#limit_validation_errors' => [array_merge($parents, ['actions'])],
452           '#ajax' => [
453             'callback' => 'inline_entity_form_get_element',
454             'wrapper' => $wrapper,
455           ],
456           '#submit' => ['inline_entity_form_open_form'],
457           '#ief_form' => 'add',
458         ];
459       }
460
461       if ($settings['allow_existing']) {
462         $element['actions']['ief_add_existing'] = [
463           '#type' => 'submit',
464           '#value' => $this->t('Add existing @type_singular', ['@type_singular' => $labels['singular']]),
465           '#name' => 'ief-' . $this->getIefId() . '-add-existing',
466           '#limit_validation_errors' => [array_merge($parents, ['actions'])],
467           '#ajax' => [
468             'callback' => 'inline_entity_form_get_element',
469             'wrapper' => $wrapper,
470           ],
471           '#submit' => ['inline_entity_form_open_form'],
472           '#ief_form' => 'ief_add_existing',
473         ];
474       }
475     }
476     else {
477       // There's a form open, show it.
478       if ($form_state->get(['inline_entity_form', $this->getIefId(), 'form']) == 'add') {
479         $element['form'] = [
480           '#type' => 'fieldset',
481           '#attributes' => ['class' => ['ief-form', 'ief-form-bottom']],
482           'inline_entity_form' => $this->getInlineEntityForm(
483             'add',
484             $this->determineBundle($form_state),
485             $parent_langcode,
486             NULL,
487             array_merge($parents, ['inline_entity_form'])
488           )
489         ];
490         $element['form']['inline_entity_form']['#process'] = [
491           ['\Drupal\inline_entity_form\Element\InlineEntityForm', 'processEntityForm'],
492           [get_class($this), 'addIefSubmitCallbacks'],
493           [get_class($this), 'buildEntityFormActions'],
494         ];
495       }
496       elseif ($form_state->get(['inline_entity_form', $this->getIefId(), 'form']) == 'ief_add_existing') {
497         $element['form'] = [
498           '#type' => 'fieldset',
499           '#attributes' => ['class' => ['ief-form', 'ief-form-bottom']],
500           // Identifies the IEF widget to which the form belongs.
501           '#ief_id' => $this->getIefId(),
502           // Used by Field API and controller methods to find the relevant
503           // values in $form_state.
504           '#parents' => array_merge($parents),
505           // Pass the current entity type.
506           '#entity_type' => $target_type,
507           // Pass the widget specific labels.
508           '#ief_labels' => $this->getEntityTypeLabels(),
509         ];
510
511         $element['form'] += inline_entity_form_reference_form($element['form'], $form_state);
512       }
513
514       // Pre-opened forms can't be closed in order to force the user to
515       // add / reference an entity.
516       if ($hide_cancel) {
517         if ($open_form == 'add') {
518           $process_element = &$element['form']['inline_entity_form'];
519         }
520         elseif ($open_form == 'ief_add_existing') {
521           $process_element = &$element['form'];
522         }
523         $process_element['#process'][] = [get_class($this), 'hideCancel'];
524       }
525
526       // No entities have been added. Remove the outer fieldset to reduce
527       // visual noise caused by having two titles.
528       if (empty($entities)) {
529         $element['#type'] = 'container';
530       }
531     }
532
533     return $element;
534   }
535
536   /**
537    * {@inheritdoc}
538    */
539   public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
540     if ($this->isDefaultValueWidget($form_state)) {
541       $items->filterEmptyItems();
542       return;
543     }
544     $triggering_element = $form_state->getTriggeringElement();
545     if (empty($triggering_element['#ief_submit_trigger'])) {
546       return;
547     }
548
549     $field_name = $this->fieldDefinition->getName();
550     $parents = array_merge($form['#parents'], [$field_name, 'form']);
551     $ief_id = sha1(implode('-', $parents));
552     $this->setIefId($ief_id);
553     $widget_state = &$form_state->get(['inline_entity_form', $ief_id]);
554     foreach ($widget_state['entities'] as $key => $value) {
555       $changed = TranslationHelper::updateEntityLangcode($value['entity'], $form_state);
556       if ($changed) {
557         $widget_state['entities'][$key]['entity'] = $value['entity'];
558         $widget_state['entities'][$key]['needs_save'] = TRUE;
559       }
560     }
561
562     $values = $widget_state['entities'];
563     // If the inline entity form is still open, then its entity hasn't
564     // been transferred to the IEF form state yet.
565     if (empty($values) && !empty($widget_state['form'])) {
566       // @todo Do the same for reference forms.
567       if ($widget_state['form'] == 'add') {
568         $element = NestedArray::getValue($form, [$field_name, 'widget', 'form']);
569         $entity = $element['inline_entity_form']['#entity'];
570         $values[] = ['entity' => $entity];
571       }
572     }
573     // Sort values by weight.
574     uasort($values, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
575     // Let the widget massage the submitted values.
576     $values = $this->massageFormValues($values, $form, $form_state);
577     // Assign the values and remove the empty ones.
578     $items->setValue($values);
579     $items->filterEmptyItems();
580   }
581
582   /**
583    * Adds actions to the inline entity form.
584    *
585    * @param array $element
586    *   Form array structure.
587    */
588   public static function buildEntityFormActions($element) {
589     // Build a delta suffix that's appended to button #name keys for uniqueness.
590     $delta = $element['#ief_id'];
591     if ($element['#op'] == 'add') {
592       $save_label = t('Create @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]);
593     }
594     else {
595       $delta .= '-' . $element['#ief_row_delta'];
596       $save_label = t('Update @type_singular', ['@type_singular' => $element['#ief_labels']['singular']]);
597     }
598
599     // Add action submit elements.
600     $element['actions'] = [
601       '#type' => 'container',
602       '#weight' => 100,
603     ];
604     $element['actions']['ief_' . $element['#op'] . '_save'] = [
605       '#type' => 'submit',
606       '#value' => $save_label,
607       '#name' => 'ief-' . $element['#op'] . '-submit-' . $delta,
608       '#limit_validation_errors' => [$element['#parents']],
609       '#attributes' => ['class' => ['ief-entity-submit']],
610       '#ajax' => [
611         'callback' => 'inline_entity_form_get_element',
612         'wrapper' => 'inline-entity-form-' . $element['#ief_id'],
613       ],
614     ];
615     $element['actions']['ief_' . $element['#op'] . '_cancel'] = [
616       '#type' => 'submit',
617       '#value' => t('Cancel'),
618       '#name' => 'ief-' . $element['#op'] . '-cancel-' . $delta,
619       '#limit_validation_errors' => [],
620       '#ajax' => [
621         'callback' => 'inline_entity_form_get_element',
622         'wrapper' => 'inline-entity-form-' . $element['#ief_id'],
623       ],
624     ];
625
626     // Add submit handlers depending on operation.
627     if ($element['#op'] == 'add') {
628       static::addSubmitCallbacks($element['actions']['ief_add_save']);
629       $element['actions']['ief_add_cancel']['#submit'] = [
630         [get_called_class(), 'closeChildForms'],
631         [get_called_class(), 'closeForm'],
632         'inline_entity_form_cleanup_form_state',
633       ];
634     }
635     else {
636       $element['actions']['ief_edit_save']['#ief_row_delta'] = $element['#ief_row_delta'];
637       $element['actions']['ief_edit_cancel']['#ief_row_delta'] = $element['#ief_row_delta'];
638
639       static::addSubmitCallbacks($element['actions']['ief_edit_save']);
640       $element['actions']['ief_edit_save']['#submit'][] = [get_called_class(), 'submitCloseRow'];
641       $element['actions']['ief_edit_cancel']['#submit'] = [
642         [get_called_class(), 'closeChildForms'],
643         [get_called_class(), 'submitCloseRow'],
644         'inline_entity_form_cleanup_row_form_state',
645       ];
646     }
647
648     return $element;
649   }
650
651   /**
652    * Hides cancel button.
653    *
654    * @param array $element
655    *   Form array structure.
656    */
657   public static function hideCancel($element) {
658     // @todo Name both buttons the same and simplify this logic.
659     if (isset($element['actions']['ief_add_cancel'])) {
660       $element['actions']['ief_add_cancel']['#access'] = FALSE;
661     }
662     elseif (isset($element['actions']['ief_reference_cancel'])) {
663       $element['actions']['ief_reference_cancel']['#access'] = FALSE;
664     }
665
666     return $element;
667   }
668
669   /**
670    * Builds remove form.
671    *
672    * @param array $form
673    *   Form array structure.
674    */
675   protected function buildRemoveForm(&$form) {
676     /** @var \Drupal\Core\Entity\EntityInterface $entity */
677     $entity = $form['#entity'];
678     $entity_id = $entity->id();
679     $entity_label = $this->inlineFormHandler->getEntityLabel($entity);
680     $labels = $this->getEntityTypeLabels();
681
682     if ($entity_label) {
683       $message = $this->t('Are you sure you want to remove %label?', ['%label' => $entity_label]);
684     }
685     else {
686       $message = $this->t('Are you sure you want to remove this %entity_type?', ['%entity_type' => $labels['singular']]);
687     }
688
689     $form['message'] = [
690       '#theme_wrappers' => ['container'],
691       '#markup' => $message,
692     ];
693
694     if (!empty($entity_id) && $this->getSetting('allow_existing') && $entity->access('delete')) {
695       $form['delete'] = [
696         '#type' => 'checkbox',
697         '#title' => $this->t('Delete this @type_singular from the system.', ['@type_singular' => $labels['singular']]),
698       ];
699     }
700
701     // Build a deta suffix that's appended to button #name keys for uniqueness.
702     $delta = $form['#ief_id'] . '-' . $form['#ief_row_delta'];
703
704     // Add actions to the form.
705     $form['actions'] = [
706       '#type' => 'container',
707       '#weight' => 100,
708     ];
709     $form['actions']['ief_remove_confirm'] = [
710       '#type' => 'submit',
711       '#value' => $this->t('Remove'),
712       '#name' => 'ief-remove-confirm-' . $delta,
713       '#limit_validation_errors' => [$form['#parents']],
714       '#ajax' => [
715         'callback' => 'inline_entity_form_get_element',
716         'wrapper' => 'inline-entity-form-' . $form['#ief_id'],
717       ],
718       '#allow_existing' => $this->getSetting('allow_existing'),
719       '#submit' => [[get_class($this), 'submitConfirmRemove']],
720       '#ief_row_delta' => $form['#ief_row_delta'],
721     ];
722     $form['actions']['ief_remove_cancel'] = [
723       '#type' => 'submit',
724       '#value' => $this->t('Cancel'),
725       '#name' => 'ief-remove-cancel-' . $delta,
726       '#limit_validation_errors' => [],
727       '#ajax' => [
728         'callback' => 'inline_entity_form_get_element',
729         'wrapper' => 'inline-entity-form-' . $form['#ief_id'],
730       ],
731       '#submit' => [[get_class($this), 'submitCloseRow']],
732       '#ief_row_delta' => $form['#ief_row_delta'],
733     ];
734   }
735
736   /**
737    * Button #submit callback: Closes a row form in the IEF widget.
738    *
739    * @param $form
740    *   The complete parent form.
741    * @param $form_state
742    *   The form state of the parent form.
743    *
744    * @see inline_entity_form_open_row_form().
745    */
746   public static function submitCloseRow($form, FormStateInterface $form_state) {
747     $element = inline_entity_form_get_element($form, $form_state);
748     $ief_id = $element['#ief_id'];
749     $delta = $form_state->getTriggeringElement()['#ief_row_delta'];
750
751     $form_state->setRebuild();
752     $form_state->set(['inline_entity_form', $ief_id, 'entities', $delta, 'form'], NULL);
753   }
754
755
756   /**
757    * Remove form submit callback.
758    *
759    * The row is identified by #ief_row_delta stored on the triggering
760    * element.
761    * This isn't an #element_validate callback to avoid processing the
762    * remove form when the main form is submitted.
763    *
764    * @param $form
765    *   The complete parent form.
766    * @param $form_state
767    *   The form state of the parent form.
768    */
769   public static function submitConfirmRemove($form, FormStateInterface $form_state) {
770     $element = inline_entity_form_get_element($form, $form_state);
771     $remove_button = $form_state->getTriggeringElement();
772     $delta = $remove_button['#ief_row_delta'];
773
774     /** @var \Drupal\Core\Field\FieldDefinitionInterface $instance */
775     $instance = $form_state->get(['inline_entity_form', $element['#ief_id'], 'instance']);
776
777     /** @var \Drupal\Core\Entity\EntityInterface $entity */
778     $entity = $element['entities'][$delta]['form']['#entity'];
779     $entity_id = $entity->id();
780
781     $form_values = NestedArray::getValue($form_state->getValues(), $element['entities'][$delta]['form']['#parents']);
782     $form_state->setRebuild();
783
784     $widget_state = $form_state->get(['inline_entity_form', $element['#ief_id']]);
785     // This entity hasn't been saved yet, we can just unlink it.
786     if (empty($entity_id) || ($remove_button['#allow_existing'] && empty($form_values['delete']))) {
787       unset($widget_state['entities'][$delta]);
788     }
789     else {
790       $widget_state['delete'][] = $entity;
791       unset($widget_state['entities'][$delta]);
792     }
793     $form_state->set(['inline_entity_form', $element['#ief_id']], $widget_state);
794   }
795
796   /**
797    * Determines bundle to be used when creating entity.
798    *
799    * @param FormStateInterface $form_state
800    *   Current form state.
801    *
802    * @return string
803    *   Bundle machine name.
804    *
805    * @TODO - Figure out if can be simplified.
806    */
807   protected function determineBundle(FormStateInterface $form_state) {
808     $ief_settings = $form_state->get(['inline_entity_form', $this->getIefId()]);
809     if (!empty($ief_settings['form settings']['bundle'])) {
810       return $ief_settings['form settings']['bundle'];
811     }
812     elseif (!empty($ief_settings['bundle'])) {
813       return $ief_settings['bundle'];
814     }
815     else {
816       $target_bundles = $this->getTargetBundles();
817       return reset($target_bundles);
818     }
819   }
820
821   /**
822    * Updates entity weights based on their weights in the widget.
823    */
824   public static function updateRowWeights($element, FormStateInterface $form_state, $form) {
825     $ief_id = $element['#ief_id'];
826
827     // Loop over the submitted delta values and update the weight of the entities
828     // in the form state.
829     foreach (Element::children($element['entities']) as $key) {
830       $form_state->set(['inline_entity_form', $ief_id, 'entities', $key, 'weight'], $element['entities'][$key]['delta']['#value']);
831     }
832   }
833
834   /**
835    * IEF widget #element_validate callback: Required field validation.
836    */
837   public static function requiredField($element, FormStateInterface $form_state, $form) {
838     $ief_id = $element['#ief_id'];
839     $children = $form_state->get(['inline_entity_form', $ief_id, 'entities']);
840     $has_children = !empty($children);
841     $form = $form_state->get(['inline_entity_form', $ief_id, 'form']);
842     $form_open = !empty($form);
843     // If the add new / add existing form is open, its validation / submission
844     // will do the job instead (either by preventing the parent form submission
845     // or by adding a new referenced entity).
846     if (!$has_children && !$form_open) {
847       /** @var \Drupal\Core\Field\FieldDefinitionInterface $instance */
848       $instance = $form_state->get(['inline_entity_form', $ief_id, 'instance']);
849       $form_state->setError($element, t('@name field is required.', ['@name' => $instance->getLabel()]));
850     }
851   }
852
853   /**
854    * Button #submit callback: Closes a form in the IEF widget.
855    *
856    * @param $form
857    *   The complete parent form.
858    * @param $form_state
859    *   The form state of the parent form.
860    *
861    * @see inline_entity_form_open_form().
862    */
863   public static function closeForm($form, FormStateInterface $form_state) {
864     $element = inline_entity_form_get_element($form, $form_state);
865     $ief_id = $element['#ief_id'];
866
867     $form_state->setRebuild();
868     $form_state->set(['inline_entity_form', $ief_id, 'form'], NULL);
869   }
870
871   /**
872    * Add common submit callback functions and mark element as a IEF trigger.
873    *
874    * @param $element
875    */
876   public static function addSubmitCallbacks(&$element) {
877     $element['#submit'] = [
878       ['\Drupal\inline_entity_form\ElementSubmit', 'trigger'],
879       ['\Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormComplex', 'closeForm'],
880     ];
881     $element['#ief_submit_trigger']  = TRUE;
882   }
883
884   /**
885    * Button #submit callback:  Closes all open child forms in the IEF widget.
886    *
887    * Used to ensure that forms in nested IEF widgets are properly closed
888    * when a parent IEF's form gets submitted or cancelled.
889    *
890    * @param $form
891    *   The IEF Form element.
892    * @param FormStateInterface $form_state
893    *   The form state of the parent form.
894    */
895   public static function closeChildForms($form, FormStateInterface &$form_state) {
896     $element = inline_entity_form_get_element($form, $form_state);
897     inline_entity_form_close_all_forms($element, $form_state);
898   }
899
900 }