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.
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;
20 * Implements hook_entity_type_build().
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');
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');
36 * Implements hook_form_alter().
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);
48 * Implements hook_theme().
50 function inline_entity_form_theme() {
52 'inline_entity_form_entity_table' => [
53 'render element' => 'form',
54 'function' => 'theme_inline_entity_form_entity_table',
60 * Provides the form for adding existing entities through an autocomplete field.
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.
68 * The form array containing the embedded form.
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']);
76 $reference_form['#title'] = t('Add existing @type_singular', ['@type_singular' => $labels['singular']]);
77 $reference_form['entity_id'] = [
78 '#type' => 'entity_autocomplete',
79 '#title' => t('@label', ['@label' => ucwords($labels['singular'])]),
80 '#target_type' => $instance->getSetting('target_type'),
81 '#selection_handler' => $instance->getSetting('handler'),
82 '#selection_settings' => $instance->getSetting('handler_settings'),
87 $reference_form['actions'] = [
88 '#type' => 'container',
91 $reference_form['actions']['ief_reference_save'] = [
93 '#value' => t('Add @type_singular', ['@type_singular' => $labels['singular']]),
94 '#name' => 'ief-reference-submit-' . $reference_form['#ief_id'],
95 '#limit_validation_errors' => [$reference_form['#parents']],
96 '#attributes' => ['class' => ['ief-entity-submit']],
98 'callback' => 'inline_entity_form_get_element',
99 'wrapper' => 'inline-entity-form-' . $reference_form['#ief_id'],
102 InlineEntityFormComplex::addSubmitCallbacks($reference_form['actions']['ief_reference_save']);
103 $reference_form['actions']['ief_reference_cancel'] = [
105 '#value' => t('Cancel'),
106 '#name' => 'ief-reference-cancel-' . $reference_form['#ief_id'],
107 '#limit_validation_errors' => [],
109 'callback' => 'inline_entity_form_get_element',
110 'wrapper' => 'inline-entity-form-' . $reference_form['#ief_id'],
112 '#submit' => [['\Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormComplex', 'closeForm']],
115 $reference_form['#element_validate'][] = 'inline_entity_form_reference_form_validate';
116 $reference_form['#ief_element_submit'][] = 'inline_entity_form_reference_form_submit';
118 // Allow other modules to alter the form.
119 \Drupal::moduleHandler()->alter('inline_entity_form_reference_form', $reference_form, $form_state);
121 return $reference_form;
125 * Validates the form for adding existing entities.
127 * @param array $reference_form
128 * The reference entity form.
129 * @param \Drupal\Core\Form\FormStateInterface $form_state
130 * The form state of the parent form.
132 function inline_entity_form_reference_form_validate(&$reference_form, FormStateInterface $form_state) {
133 $form_values = NestedArray::getValue($form_state->getValues(), $reference_form['#parents']);
134 if (empty($form_values['entity_id'])) {
135 // The entity_id element is required, the value is empty only if
136 // the form was cancelled.
139 $ief_id = $reference_form['#ief_id'];
140 $labels = $reference_form['#ief_labels'];
141 $storage = \Drupal::entityTypeManager()->getStorage($reference_form['#entity_type']);
142 $entity = $storage->load($form_values['entity_id']);
144 // Check if the entity is already referenced by the field.
145 if (!empty($entity)) {
146 foreach ($form_state->get(['inline_entity_form', $ief_id, 'entities']) as $key => $value) {
147 if ($value['entity'] && $value['entity']->id() == $entity->id()) {
148 $form_state->setError($reference_form['entity_id'], t('The selected @label has already been added.', ['@label' => $labels['singular']]));
156 * Submits the form for adding existing entities.
158 * Adds the specified entity to the IEF form state.
160 * @param array $reference_form
161 * The reference entity form.
162 * @param \Drupal\Core\Form\FormStateInterface $form_state
163 * The form state of the parent form.
165 function inline_entity_form_reference_form_submit($reference_form, FormStateInterface $form_state) {
166 $ief_id = $reference_form['#ief_id'];
167 $form_values = NestedArray::getValue($form_state->getValues(), $reference_form['#parents']);
168 $storage = \Drupal::entityTypeManager()->getStorage($reference_form['#entity_type']);
169 $entity = $storage->load($form_values['entity_id']);
170 $entities = &$form_state->get(['inline_entity_form', $ief_id, 'entities']);
171 // Determine the correct weight of the new element.
174 $weight = max(array_keys($entities)) + 1;
181 'needs_save' => FALSE,
183 $form_state->set(['inline_entity_form', $ief_id, 'entities'], $entities);
187 * Button #submit callback: Opens a form in the IEF widget.
189 * The form is shown below the entity table, at the bottom of the widget.
192 * The complete parent form.
194 * The form state of the parent form.
196 function inline_entity_form_open_form($form, FormStateInterface $form_state) {
197 $element = inline_entity_form_get_element($form, $form_state);
198 $ief_id = $element['#ief_id'];
199 $form_state->setRebuild();
201 // Get the current form values.
202 $parents = array_merge($element['#field_parents'], [$element['#field_name']]);
203 $form_values = NestedArray::getValue($form_state->getUserInput(), $parents);
205 $triggering_element = $form_state->getTriggeringElement();
206 $form_state->set(['inline_entity_form', $ief_id, 'form'], $triggering_element['#ief_form']);
207 if (!empty($form_values['actions']['bundle'])) {
208 $form_state->set(['inline_entity_form', $ief_id, 'form settings'], [
209 'bundle' => $form_values['actions']['bundle'],
215 * Button #submit callback: Cleans up form state for a closed entity form.
218 * The complete parent form.
220 * The form state of the parent form.
222 function inline_entity_form_cleanup_form_state($form, FormStateInterface $form_state) {
223 $element = inline_entity_form_get_element($form, $form_state);
224 EntityInlineForm::submitCleanFormState($element['form']['inline_entity_form'], $form_state);
228 * Button #submit callback: Opens a row form in the IEF widget.
230 * The row is identified by #ief_row_delta stored on the triggering
234 * The complete parent form.
236 * The form state of the parent form.
238 function inline_entity_form_open_row_form($form, FormStateInterface $form_state) {
239 $element = inline_entity_form_get_element($form, $form_state);
240 $ief_id = $element['#ief_id'];
241 $delta = $form_state->getTriggeringElement()['#ief_row_delta'];
243 $form_state->setRebuild();
244 $form_state->set(['inline_entity_form', $ief_id, 'entities', $delta, 'form'], $form_state->getTriggeringElement()['#ief_row_form']);
249 * Closes all open IEF forms.
251 * Recurses and closes open forms in nested IEF widgets as well.
254 * An array of form elements containing entity forms.
256 * The form state of the parent form.
258 function inline_entity_form_close_all_forms($elements, FormStateInterface $form_state) {
259 // Recurse through all children.
260 foreach (Element::children($elements) as $key) {
261 if (!empty($elements[$key])) {
262 inline_entity_form_close_all_forms($elements[$key], $form_state);
266 if (!empty($elements['#ief_id'])) {
267 $ief_id = $elements['#ief_id'];
268 // Close the main form.
269 $form_state->set(['inline_entity_form', $ief_id, 'form'], NULL);
270 // Close the row forms.
271 $entities = $form_state->get(['inline_entity_form', $ief_id, 'entities']);
272 foreach ($entities as $key => $value) {
273 $entities[$key]['form'] = NULL;
275 $form_state->set(['inline_entity_form', $ief_id, 'entities'], $entities);
280 * Button #submit callback: Cleans up form state for a closed entity row form.
283 * The complete parent form.
285 * The form state of the parent form.
287 function inline_entity_form_cleanup_row_form_state($form, FormStateInterface $form_state) {
288 $element = inline_entity_form_get_element($form, $form_state);
289 $delta = $form_state->getTriggeringElement()['#ief_row_delta'];
290 $entity_form = $element['entities'][$delta]['form']['inline_entity_form'];
291 EntityInlineForm::submitCleanFormState($entity_form, $form_state);
295 * Returns an IEF widget nearest to the triggering element.
297 function inline_entity_form_get_element($form, FormStateInterface $form_state) {
299 $triggering_element = $form_state->getTriggeringElement();
301 // Remove the action and the actions container.
302 $array_parents = array_slice($triggering_element['#array_parents'], 0, -2);
304 while (!isset($element['#ief_root'])) {
305 $element = NestedArray::getValue($form, $array_parents);
306 array_pop($array_parents);
313 * Themes the table showing existing entity references in the widget.
315 * @param array $variables
316 * Contains the form element data from $element['entities'].
318 function theme_inline_entity_form_entity_table($variables) {
319 $renderer = \Drupal::service('renderer');
320 $form = $variables['form'];
321 $entity_type = $form['#entity_type'];
323 $fields = $form['#table_fields'];
324 $has_tabledrag = \Drupal::entityTypeManager()->getHandler($entity_type, 'inline_form')->isTableDragEnabled($form);
326 // Sort the fields by weight.
327 uasort($fields, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
330 if ($has_tabledrag) {
331 $header[] = ['data' => '', 'class' => ['ief-tabledrag-header']];
332 $header[] = ['data' => t('Sort order'), 'class' => ['ief-sort-order-header']];
334 // Add header columns for each field.
336 foreach ($fields as $field_name => $field) {
337 $column = ['data' => $field['label']];
338 // The first column gets a special class.
340 $column['class'] = ['ief-first-column-header'];
345 $header[] = t('Operations');
347 // Build an array of entity rows for the table.
349 foreach (Element::children($form) as $key) {
350 /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
351 $entity = $form[$key]['#entity'];
352 $row_classes = ['ief-row-entity'];
354 if ($has_tabledrag) {
355 $cells[] = ['data' => '', 'class' => ['ief-tabledrag-handle']];
356 $cells[] = $renderer->render($form[$key]['delta']);
357 $row_classes[] = 'draggable';
359 // Add a special class to rows that have a form underneath, to allow
360 // for additional styling.
361 if (!empty($form[$key]['form'])) {
362 $row_classes[] = 'ief-row-entity-form';
365 foreach ($fields as $field_name => $field) {
367 if ($field['type'] == 'label') {
368 $data = $variables['form'][$key]['#label'];
370 elseif ($field['type'] == 'field' && $entity->hasField($field_name)) {
371 $display_options = ['label' => 'hidden'];
372 if (isset($field['display_options'])) {
373 $display_options += $field['display_options'];
375 $data = $entity->get($field_name)->view($display_options);
377 elseif ($field['type'] == 'callback') {
380 'variables' => $variables,
382 if (isset($field['callback_arguments'])) {
383 $arguments = array_merge($arguments, $field['callback_arguments']);
386 $data = call_user_func_array($field['callback'], $arguments);
389 $cells[] = ['data' => $data, 'class' => ['inline-entity-form-' . $entity_type . '-' . $field_name]];
392 // Add the buttons belonging to the "Operations" column.
393 $cells[] = $renderer->render($form[$key]['actions']);
395 $rows[] = ['data' => $cells, 'class' => $row_classes];
396 // If the current entity array specifies a form, output it in the next row.
397 if (!empty($form[$key]['form'])) {
399 ['data' => $renderer->render($form[$key]['form']), 'colspan' => count($fields) + 1],
401 $rows[] = ['data' => $row, 'class' => ['ief-row-form'], 'no_striping' => TRUE];
407 if ($has_tabledrag) {
411 'relationship' => 'sibling',
412 'group' => 'ief-entity-delta',
419 '#header' => $header,
422 'id' => 'ief-entity-table-' . $form['#id'],
423 'class' => ['ief-entity-table'],
425 '#tabledrag' => $tabledrag,
428 return $renderer->render($table);