Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / inline_entity_form / inline_entity_form.module
1 <?php
2
3 /**
4  * @file
5  * Provides a widget for inline management (creation, modification, removal) of
6  * referenced entities. The primary use case is the parent -> children one
7  * (for example, order -> line items), where the child entities are never
8  * managed outside the parent form.
9  */
10
11 use Drupal\Component\Utility\NestedArray;
12 use Drupal\Core\Form\FormStateInterface;
13 use Drupal\Core\Render\Element;
14 use Drupal\inline_entity_form\ElementSubmit;
15 use Drupal\inline_entity_form\WidgetSubmit;
16 use Drupal\inline_entity_form\Form\EntityInlineForm;
17 use Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormComplex;
18
19 /**
20  * Implements hook_entity_type_build().
21  */
22 function inline_entity_form_entity_type_build(array &$entity_types) {
23   /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
24   if (isset($entity_types['node']) && !$entity_types['node']->getHandlerClass('inline_form')) {
25     $entity_types['node']->setHandlerClass('inline_form', '\Drupal\inline_entity_form\Form\NodeInlineForm');
26   }
27
28   foreach ($entity_types as &$entity_type) {
29     if (!$entity_type->hasHandlerClass('inline_form')) {
30       $entity_type->setHandlerClass('inline_form', '\Drupal\inline_entity_form\Form\EntityInlineForm');
31     }
32   }
33 }
34
35 /**
36  * Implements hook_form_alter().
37  */
38 function inline_entity_form_form_alter(&$form, FormStateInterface $form_state, $form_id) {
39   // Attach the IEF handlers only if the current form has an IEF widget.
40   $widget_state = $form_state->get('inline_entity_form');
41   if (!is_null($widget_state)) {
42     ElementSubmit::attach($form, $form_state);
43     WidgetSubmit::attach($form, $form_state);
44   }
45 }
46
47 /**
48  * Implements hook_theme().
49  */
50 function inline_entity_form_theme() {
51   return [
52     'inline_entity_form_entity_table' => [
53       'render element' => 'form',
54       'function' => 'theme_inline_entity_form_entity_table',
55     ],
56   ];
57 }
58
59 /**
60  * Provides the form for adding existing entities through an autocomplete field.
61  *
62  * @param array $reference_form
63  *   The form array that will receive the form.
64  * @param \Drupal\Core\Form\FormStateInterface $form_state
65  *   The form state of the parent form.
66  *
67  * @return array
68  *   The form array containing the embedded form.
69  */
70 function inline_entity_form_reference_form($reference_form, &$form_state) {
71   $labels = $reference_form['#ief_labels'];
72   $ief_id = $reference_form['#ief_id'];
73   /** @var \Drupal\field\Entity\FieldConfig $instance */
74   $instance = $form_state->get(['inline_entity_form', $ief_id, 'instance']);
75   $selection_settings = [
76     'match_operator' => $reference_form['#match_operator'],
77   ] + $instance->getSetting('handler_settings');
78
79   $reference_form['#title'] = t('Add existing @type_singular', ['@type_singular' => $labels['singular']]);
80
81   $reference_form['entity_id'] = [
82     '#type' => 'entity_autocomplete',
83     // @todo Use bundle defined singular/plural labels as soon as
84     //   https://www.drupal.org/node/2765065 is committed.
85     // @see https://www.drupal.org/node/2765065
86     '#title' => t('@label', ['@label' => ucfirst($labels['singular'])]),
87     '#target_type' => $instance->getSetting('target_type'),
88     '#selection_handler' => $instance->getSetting('handler'),
89     '#selection_settings' => $selection_settings,
90     '#required' => TRUE,
91     '#maxlength' => 255,
92   ];
93   // Add the actions
94   $reference_form['actions'] = [
95     '#type' => 'container',
96     '#weight' => 100,
97   ];
98   $reference_form['actions']['ief_reference_save'] = [
99     '#type' => 'submit',
100     '#value' => t('Add @type_singular', ['@type_singular' => $labels['singular']]),
101     '#name' => 'ief-reference-submit-' . $reference_form['#ief_id'],
102     '#limit_validation_errors' => [$reference_form['#parents']],
103     '#attributes' => ['class' => ['ief-entity-submit']],
104     '#ajax' => [
105       'callback' => 'inline_entity_form_get_element',
106       'wrapper' => 'inline-entity-form-' . $reference_form['#ief_id'],
107     ],
108   ];
109   InlineEntityFormComplex::addSubmitCallbacks($reference_form['actions']['ief_reference_save']);
110   $reference_form['actions']['ief_reference_cancel'] = [
111     '#type' => 'submit',
112     '#value' => t('Cancel'),
113     '#name' => 'ief-reference-cancel-' . $reference_form['#ief_id'],
114     '#limit_validation_errors' => [],
115     '#ajax' => [
116       'callback' => 'inline_entity_form_get_element',
117       'wrapper' => 'inline-entity-form-' . $reference_form['#ief_id'],
118     ],
119     '#submit' => [['\Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormComplex', 'closeForm']],
120   ];
121
122   $reference_form['#element_validate'][] = 'inline_entity_form_reference_form_validate';
123   $reference_form['#ief_element_submit'][] = 'inline_entity_form_reference_form_submit';
124
125   // Allow other modules to alter the form.
126   \Drupal::moduleHandler()->alter('inline_entity_form_reference_form', $reference_form, $form_state);
127
128   return $reference_form;
129 }
130
131 /**
132  * Validates the form for adding existing entities.
133  *
134  * @param array $reference_form
135  *   The reference entity form.
136  * @param \Drupal\Core\Form\FormStateInterface $form_state
137  *   The form state of the parent form.
138  */
139 function inline_entity_form_reference_form_validate(&$reference_form, FormStateInterface $form_state) {
140   $form_values = NestedArray::getValue($form_state->getValues(), $reference_form['#parents']);
141   if (empty($form_values['entity_id'])) {
142     // The entity_id element is required, the value is empty only if
143     // the form was cancelled.
144     return;
145   }
146   $ief_id = $reference_form['#ief_id'];
147   $labels = $reference_form['#ief_labels'];
148   $storage = \Drupal::entityTypeManager()->getStorage($reference_form['#entity_type']);
149   $entity = $storage->load($form_values['entity_id']);
150
151   // Check if the entity is already referenced by the field.
152   if (!empty($entity)) {
153     foreach ($form_state->get(['inline_entity_form', $ief_id, 'entities']) as $key => $value) {
154       if ($value['entity'] && $value['entity']->id() == $entity->id()) {
155         $form_state->setError($reference_form['entity_id'], t('The selected @label has already been added.', ['@label' => $labels['singular']]));
156         break;
157       }
158     }
159   }
160 }
161
162 /**
163  * Submits the form for adding existing entities.
164  *
165  * Adds the specified entity to the IEF form state.
166  *
167  * @param array $reference_form
168  *   The reference entity form.
169  * @param \Drupal\Core\Form\FormStateInterface $form_state
170  *   The form state of the parent form.
171  */
172 function inline_entity_form_reference_form_submit($reference_form, FormStateInterface $form_state) {
173   $ief_id = $reference_form['#ief_id'];
174   $form_values = NestedArray::getValue($form_state->getValues(), $reference_form['#parents']);
175   $storage = \Drupal::entityTypeManager()->getStorage($reference_form['#entity_type']);
176   $entity = $storage->load($form_values['entity_id']);
177   $entities = &$form_state->get(['inline_entity_form', $ief_id, 'entities']);
178   // Determine the correct weight of the new element.
179   $weight = 0;
180   if ($entities) {
181     $weight = max(array_keys($entities)) + 1;
182   }
183
184   $entities[] = [
185     'entity' => $entity,
186     'weight' => $weight,
187     'form' => NULL,
188     'needs_save' => FALSE,
189   ];
190   $form_state->set(['inline_entity_form', $ief_id, 'entities'], $entities);
191 }
192
193 /**
194  * Button #submit callback: Opens a form in the IEF widget.
195  *
196  * The form is shown below the entity table, at the bottom of the widget.
197  *
198  * @param $form
199  *   The complete parent form.
200  * @param $form_state
201  *   The form state of the parent form.
202  */
203 function inline_entity_form_open_form($form, FormStateInterface $form_state) {
204   $element = inline_entity_form_get_element($form, $form_state);
205   $ief_id = $element['#ief_id'];
206   $form_state->setRebuild();
207
208   // Get the current form values.
209   $parents = array_merge($element['#field_parents'], [$element['#field_name']]);
210   $form_values = NestedArray::getValue($form_state->getUserInput(), $parents);
211
212   $triggering_element = $form_state->getTriggeringElement();
213   $form_state->set(['inline_entity_form', $ief_id, 'form'], $triggering_element['#ief_form']);
214   if (!empty($form_values['actions']['bundle'])) {
215     $form_state->set(['inline_entity_form', $ief_id, 'form settings'], [
216       'bundle' => $form_values['actions']['bundle'],
217     ]);
218   }
219 }
220
221 /**
222  * Button #submit callback: Cleans up form state for a closed entity form.
223  *
224  * @param $form
225  *   The complete parent form.
226  * @param $form_state
227  *   The form state of the parent form.
228  */
229 function inline_entity_form_cleanup_form_state($form, FormStateInterface $form_state) {
230   $element = inline_entity_form_get_element($form, $form_state);
231   EntityInlineForm::submitCleanFormState($element['form']['inline_entity_form'], $form_state);
232 }
233
234 /**
235  * Button #submit callback: Opens a row form in the IEF widget.
236  *
237  * The row is identified by #ief_row_delta stored on the triggering
238  * element.
239  *
240  * @param $form
241  *   The complete parent form.
242  * @param $form_state
243  *   The form state of the parent form.
244  */
245 function inline_entity_form_open_row_form($form, FormStateInterface $form_state) {
246   $element = inline_entity_form_get_element($form, $form_state);
247   $ief_id = $element['#ief_id'];
248   $delta = $form_state->getTriggeringElement()['#ief_row_delta'];
249
250   $form_state->setRebuild();
251   $form_state->set(['inline_entity_form', $ief_id, 'entities', $delta, 'form'], $form_state->getTriggeringElement()['#ief_row_form']);
252 }
253
254
255 /**
256  * Closes all open IEF forms.
257  *
258  * Recurses and closes open forms in nested IEF widgets as well.
259  *
260  * @param $elements
261  *   An array of form elements containing entity forms.
262  * @param $form_state
263  *   The form state of the parent form.
264  */
265 function inline_entity_form_close_all_forms($elements, FormStateInterface $form_state) {
266   // Recurse through all children.
267   foreach (Element::children($elements) as $key) {
268     if (!empty($elements[$key])) {
269       inline_entity_form_close_all_forms($elements[$key], $form_state);
270     }
271   }
272
273   if (!empty($elements['#ief_id'])) {
274     $ief_id = $elements['#ief_id'];
275     // Close the main form.
276     $form_state->set(['inline_entity_form', $ief_id, 'form'], NULL);
277     // Close the row forms.
278     $entities = $form_state->get(['inline_entity_form', $ief_id, 'entities']);
279     foreach ($entities as $key => $value) {
280       $entities[$key]['form'] = NULL;
281     }
282     $form_state->set(['inline_entity_form', $ief_id, 'entities'], $entities);
283   }
284 }
285
286 /**
287  * Button #submit callback: Cleans up form state for a closed entity row form.
288  *
289  * @param $form
290  *   The complete parent form.
291  * @param $form_state
292  *   The form state of the parent form.
293  */
294 function inline_entity_form_cleanup_row_form_state($form, FormStateInterface $form_state) {
295   $element = inline_entity_form_get_element($form, $form_state);
296   $delta = $form_state->getTriggeringElement()['#ief_row_delta'];
297   $entity_form = $element['entities'][$delta]['form']['inline_entity_form'];
298   EntityInlineForm::submitCleanFormState($entity_form, $form_state);
299 }
300
301 /**
302  * Returns an IEF widget nearest to the triggering element.
303  */
304 function inline_entity_form_get_element($form, FormStateInterface $form_state) {
305   $element = [];
306   $triggering_element = $form_state->getTriggeringElement();
307
308   // Remove the action and the actions container.
309   $array_parents = array_slice($triggering_element['#array_parents'], 0, -2);
310
311   while (!isset($element['#ief_root'])) {
312     $element = NestedArray::getValue($form, $array_parents);
313     array_pop($array_parents);
314   }
315
316   return $element;
317 }
318
319 /**
320  * Themes the table showing existing entity references in the widget.
321  *
322  * @param array $variables
323  *   Contains the form element data from $element['entities'].
324  */
325 function theme_inline_entity_form_entity_table($variables) {
326   $renderer = \Drupal::service('renderer');
327   $form = $variables['form'];
328   $entity_type = $form['#entity_type'];
329
330   $fields = $form['#table_fields'];
331   $has_tabledrag = \Drupal::entityTypeManager()->getHandler($entity_type, 'inline_form')->isTableDragEnabled($form);
332
333   // Sort the fields by weight.
334   uasort($fields, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
335
336   $header = [];
337   if ($has_tabledrag) {
338     $header[] = ['data' => '', 'class' => ['ief-tabledrag-header']];
339     $header[] = ['data' => t('Sort order'), 'class' => ['ief-sort-order-header']];
340   }
341   // Add header columns for each field.
342   $first = TRUE;
343   foreach ($fields as $field_name => $field) {
344     $column = ['data' => $field['label'], 'class' => ['inline-entity-form-' . $entity_type . '-' . $field_name]];
345     // The first column gets a special class.
346     if ($first) {
347       $column['class'][] = 'ief-first-column-header';
348       $first = FALSE;
349     }
350     $header[] = $column;
351   }
352   $header[] = t('Operations');
353
354   // Build an array of entity rows for the table.
355   $rows = [];
356   foreach (Element::children($form) as $key) {
357     /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
358     $entity = $form[$key]['#entity'];
359     $row_classes = ['ief-row-entity'];
360     $cells = [];
361     if ($has_tabledrag) {
362       $cells[] = ['data' => '', 'class' => ['ief-tabledrag-handle']];
363       $cells[] = $renderer->render($form[$key]['delta']);
364       $row_classes[] = 'draggable';
365     }
366     // Add a special class to rows that have a form underneath, to allow
367     // for additional styling.
368     if (!empty($form[$key]['form'])) {
369       $row_classes[] = 'ief-row-entity-form';
370     }
371
372     foreach ($fields as $field_name => $field) {
373       if ($field['type'] == 'label') {
374         $data = $variables['form'][$key]['#label'];
375       }
376       elseif ($field['type'] == 'field' && $entity->hasField($field_name)) {
377         $display_options = ['label' => 'hidden'];
378         if (isset($field['display_options'])) {
379           $display_options += $field['display_options'];
380         }
381         $data = $entity->get($field_name)->view($display_options);
382       }
383       elseif ($field['type'] == 'callback') {
384         $arguments = [
385           'entity' => $entity,
386           'variables' => $variables,
387         ];
388         if (isset($field['callback_arguments'])) {
389           $arguments = array_merge($arguments, $field['callback_arguments']);
390         }
391
392         $data = call_user_func_array($field['callback'], $arguments);
393       }
394       else {
395         $data = t('N/A');
396       }
397
398       $cells[] = ['data' => $data, 'class' => ['inline-entity-form-' . $entity_type . '-' . $field_name]];
399     }
400
401     // Add the buttons belonging to the "Operations" column.
402     $cells[] = $renderer->render($form[$key]['actions']);
403     // Create the row.
404     $rows[] = ['data' => $cells, 'class' => $row_classes];
405     // If the current entity array specifies a form, output it in the next row.
406     if (!empty($form[$key]['form'])) {
407       $row = [
408         ['data' => $renderer->render($form[$key]['form']), 'colspan' => count($fields) + 1],
409       ];
410       $rows[] = ['data' => $row, 'class' => ['ief-row-form'], 'no_striping' => TRUE];
411     }
412   }
413
414   if (!empty($rows)) {
415     $tabledrag = [];
416     if ($has_tabledrag) {
417       $tabledrag = [
418         [
419           'action' => 'order',
420           'relationship' => 'sibling',
421           'group' => 'ief-entity-delta',
422         ],
423       ];
424     }
425
426     $table = [
427       '#type' => 'table',
428       '#header' => $header,
429       '#rows' => $rows,
430       '#attributes' => [
431         'id' => 'ief-entity-table-' . $form['#id'],
432         'class' => ['ief-entity-table'],
433       ],
434       '#tabledrag' => $tabledrag,
435     ];
436
437     return $renderer->render($table);
438   }
439 }