inline_entity_form.module in Inline Entity Form 8
Same filename and directory in other branches
Provides a widget for inline management (creation, modification, removal) of referenced entities. The primary use case is the parent -> children one (for example, order -> line items), where the child entities are never managed outside the parent form.
File
inline_entity_form.moduleView source
<?php
/**
* @file
* Provides a widget for inline management (creation, modification, removal) of
* referenced entities. The primary use case is the parent -> children one
* (for example, order -> line items), where the child entities are never
* managed outside the parent form.
*/
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\inline_entity_form\ElementSubmit;
use Drupal\inline_entity_form\WidgetSubmit;
use Drupal\inline_entity_form\Form\EntityInlineForm;
use Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormComplex;
use Drupal\inline_entity_form\MigrationHelper;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
/**
* Implements hook_entity_type_build().
*/
function inline_entity_form_entity_type_build(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
if (isset($entity_types['node']) && !$entity_types['node']
->getHandlerClass('inline_form')) {
$entity_types['node']
->setHandlerClass('inline_form', '\\Drupal\\inline_entity_form\\Form\\NodeInlineForm');
}
foreach ($entity_types as &$entity_type) {
if (!$entity_type
->hasHandlerClass('inline_form')) {
$entity_type
->setHandlerClass('inline_form', '\\Drupal\\inline_entity_form\\Form\\EntityInlineForm');
}
}
}
/**
* Implements hook_form_alter().
*/
function inline_entity_form_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Attach the IEF handlers only if the current form has an IEF widget.
$widget_state = $form_state
->get('inline_entity_form');
if (!is_null($widget_state)) {
ElementSubmit::attach($form, $form_state);
WidgetSubmit::attach($form, $form_state);
}
}
/**
* Implements hook_theme().
*/
function inline_entity_form_theme() {
return [
'inline_entity_form_entity_table' => [
'render element' => 'form',
],
];
}
/**
* Provides the form for adding existing entities through an autocomplete field.
*
* @param array $reference_form
* The form array that will receive the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the parent form.
*
* @return array
* The form array containing the embedded form.
*/
function inline_entity_form_reference_form($reference_form, FormStateInterface &$form_state) {
$labels = $reference_form['#ief_labels'];
$ief_id = $reference_form['#ief_id'];
/** @var \Drupal\field\Entity\FieldConfig $instance */
$instance = $form_state
->get([
'inline_entity_form',
$ief_id,
'instance',
]);
$selection_settings = [
'match_operator' => $reference_form['#match_operator'],
] + $instance
->getSetting('handler_settings');
$reference_form['#title'] = t('Add existing @type_singular', [
'@type_singular' => $labels['singular'],
]);
$reference_form['entity_id'] = [
'#type' => 'entity_autocomplete',
// @todo Use bundle defined singular/plural labels as soon as
// https://www.drupal.org/node/2765065 is committed.
// @see https://www.drupal.org/node/2765065
'#title' => t('@label', [
'@label' => ucfirst($labels['singular']),
]),
'#target_type' => $instance
->getSetting('target_type'),
'#selection_handler' => $instance
->getSetting('handler'),
'#selection_settings' => $selection_settings,
'#required' => TRUE,
'#maxlength' => 255,
];
// Add the actions
$reference_form['actions'] = [
'#type' => 'container',
'#weight' => 100,
];
$reference_form['actions']['ief_reference_save'] = [
'#type' => 'submit',
'#value' => t('Add @type_singular', [
'@type_singular' => $labels['singular'],
]),
'#name' => 'ief-reference-submit-' . $reference_form['#ief_id'],
'#limit_validation_errors' => [
$reference_form['#parents'],
],
'#attributes' => [
'class' => [
'ief-entity-submit',
],
],
'#ajax' => [
'callback' => 'inline_entity_form_get_element',
'wrapper' => 'inline-entity-form-' . $reference_form['#ief_id'],
],
];
InlineEntityFormComplex::addSubmitCallbacks($reference_form['actions']['ief_reference_save']);
$reference_form['actions']['ief_reference_cancel'] = [
'#type' => 'submit',
'#value' => t('Cancel'),
'#name' => 'ief-reference-cancel-' . $reference_form['#ief_id'],
'#limit_validation_errors' => [],
'#ajax' => [
'callback' => 'inline_entity_form_get_element',
'wrapper' => 'inline-entity-form-' . $reference_form['#ief_id'],
],
'#submit' => [
[
'\\Drupal\\inline_entity_form\\Plugin\\Field\\FieldWidget\\InlineEntityFormComplex',
'closeForm',
],
],
];
$reference_form['#element_validate'][] = 'inline_entity_form_reference_form_validate';
$reference_form['#ief_element_submit'][] = 'inline_entity_form_reference_form_submit';
// Allow other modules to alter the form.
\Drupal::moduleHandler()
->alter('inline_entity_form_reference_form', $reference_form, $form_state);
return $reference_form;
}
/**
* Validates the form for adding existing entities.
*
* @param array $reference_form
* The reference entity form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the parent form.
*/
function inline_entity_form_reference_form_validate(&$reference_form, FormStateInterface $form_state) {
$form_values = NestedArray::getValue($form_state
->getValues(), $reference_form['#parents']);
if (empty($form_values['entity_id'])) {
// The entity_id element is required, the value is empty only if
// the form was cancelled.
return;
}
$ief_id = $reference_form['#ief_id'];
$labels = $reference_form['#ief_labels'];
$storage = \Drupal::entityTypeManager()
->getStorage($reference_form['#entity_type']);
$entity = $storage
->load($form_values['entity_id']);
// Check if the entity is already referenced by the field.
if (!empty($entity)) {
foreach ($form_state
->get([
'inline_entity_form',
$ief_id,
'entities',
]) as $key => $value) {
if ($value['entity'] && $value['entity']
->id() == $entity
->id()) {
$form_state
->setError($reference_form['entity_id'], t('The selected @label has already been added.', [
'@label' => $labels['singular'],
]));
break;
}
}
}
}
/**
* Submits the form for adding existing entities.
*
* Adds the specified entity to the IEF form state.
*
* @param array $reference_form
* The reference entity form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the parent form.
*/
function inline_entity_form_reference_form_submit($reference_form, FormStateInterface $form_state) {
$ief_id = $reference_form['#ief_id'];
$form_values = NestedArray::getValue($form_state
->getValues(), $reference_form['#parents']);
$storage = \Drupal::entityTypeManager()
->getStorage($reference_form['#entity_type']);
$entity = $storage
->load($form_values['entity_id']);
$entities =& $form_state
->get([
'inline_entity_form',
$ief_id,
'entities',
]);
// Determine the correct weight of the new element.
$weight = 0;
if ($entities) {
$weight = max(array_keys($entities)) + 1;
}
$entities[] = [
'entity' => $entity,
'weight' => $weight,
'form' => NULL,
'needs_save' => FALSE,
];
$form_state
->set([
'inline_entity_form',
$ief_id,
'entities',
], $entities);
}
/**
* Button #submit callback: Opens a form in the IEF widget.
*
* The form is shown below the entity table, at the bottom of the widget.
*
* @param $form
* The complete parent form.
* @param $form_state
* The form state of the parent form.
*/
function inline_entity_form_open_form($form, FormStateInterface $form_state) {
$element = inline_entity_form_get_element($form, $form_state);
$ief_id = $element['#ief_id'];
$form_state
->setRebuild();
// Get the current form values.
$parents = array_merge($element['#field_parents'], [
$element['#field_name'],
]);
$form_values = NestedArray::getValue($form_state
->getUserInput(), $parents);
$triggering_element = $form_state
->getTriggeringElement();
$form_state
->set([
'inline_entity_form',
$ief_id,
'form',
], $triggering_element['#ief_form']);
if (!empty($form_values['actions']['bundle'])) {
$form_state
->set([
'inline_entity_form',
$ief_id,
'form settings',
], [
'bundle' => $form_values['actions']['bundle'],
]);
}
}
/**
* Button #submit callback: Cleans up form state for a closed entity form.
*
* @param $form
* The complete parent form.
* @param $form_state
* The form state of the parent form.
*/
function inline_entity_form_cleanup_form_state($form, FormStateInterface $form_state) {
$element = inline_entity_form_get_element($form, $form_state);
EntityInlineForm::submitCleanFormState($element['form']['inline_entity_form'], $form_state);
}
/**
* Button #submit callback: Opens a row form in the IEF widget.
*
* The row is identified by #ief_row_delta stored on the triggering
* element.
*
* @param $form
* The complete parent form.
* @param $form_state
* The form state of the parent form.
*/
function inline_entity_form_open_row_form($form, FormStateInterface $form_state) {
$element = inline_entity_form_get_element($form, $form_state);
$ief_id = $element['#ief_id'];
$delta = $form_state
->getTriggeringElement()['#ief_row_delta'];
$form_state
->setRebuild();
$form_state
->set([
'inline_entity_form',
$ief_id,
'entities',
$delta,
'form',
], $form_state
->getTriggeringElement()['#ief_row_form']);
}
/**
* Closes all open IEF forms.
*
* Recurses and closes open forms in nested IEF widgets as well.
*
* @param $elements
* An array of form elements containing entity forms.
* @param $form_state
* The form state of the parent form.
*/
function inline_entity_form_close_all_forms($elements, FormStateInterface $form_state) {
// Recurse through all children.
foreach (Element::children($elements) as $key) {
if (!empty($elements[$key])) {
inline_entity_form_close_all_forms($elements[$key], $form_state);
}
}
if (!empty($elements['#ief_id'])) {
$ief_id = $elements['#ief_id'];
// Close the main form.
$form_state
->set([
'inline_entity_form',
$ief_id,
'form',
], NULL);
// Close the row forms.
$entities = $form_state
->get([
'inline_entity_form',
$ief_id,
'entities',
]);
foreach ($entities as $key => $value) {
$entities[$key]['form'] = NULL;
}
$form_state
->set([
'inline_entity_form',
$ief_id,
'entities',
], $entities);
}
}
/**
* Button #submit callback: Cleans up form state for a closed entity row form.
*
* @param $form
* The complete parent form.
* @param $form_state
* The form state of the parent form.
*/
function inline_entity_form_cleanup_row_form_state($form, FormStateInterface $form_state) {
$element = inline_entity_form_get_element($form, $form_state);
$delta = $form_state
->getTriggeringElement()['#ief_row_delta'];
$entity_form = $element['entities'][$delta]['form']['inline_entity_form'];
EntityInlineForm::submitCleanFormState($entity_form, $form_state);
}
/**
* Returns an IEF widget nearest to the triggering element.
*/
function inline_entity_form_get_element($form, FormStateInterface $form_state) {
$element = [];
$triggering_element = $form_state
->getTriggeringElement();
// Remove the action and the actions container.
$array_parents = array_slice($triggering_element['#array_parents'], 0, -2);
while (!isset($element['#ief_root'])) {
$element = NestedArray::getValue($form, $array_parents);
array_pop($array_parents);
}
return $element;
}
/**
* Prepares variables for inline_entity_form_entity_table form templates.
*
* Default template: inline-entity-form-entity-table.html.twig.
*
* @param array $variables
* An associative array containing:
* - form: A render element representing the form.
*/
function template_preprocess_inline_entity_form_entity_table(array &$variables) {
$form = $variables['form'];
$entity_type = $form['#entity_type'];
$fields = $form['#table_fields'];
$has_tabledrag = \Drupal::entityTypeManager()
->getHandler($entity_type, 'inline_form')
->isTableDragEnabled($form);
// Sort the fields by weight.
uasort($fields, '\\Drupal\\Component\\Utility\\SortArray::sortByWeightElement');
$header = [];
if ($has_tabledrag) {
$header[] = [
'data' => '',
'class' => [
'ief-tabledrag-header',
],
];
$header[] = [
'data' => t('Sort order'),
'class' => [
'ief-sort-order-header',
],
];
}
// Add header columns for each field.
$first = TRUE;
foreach ($fields as $field_name => $field) {
$column = [
'data' => $field['label'],
'class' => [
'inline-entity-form-' . $entity_type . '-' . $field_name,
],
];
// The first column gets a special class.
if ($first) {
$column['class'][] = 'ief-first-column-header';
$first = FALSE;
}
$header[] = $column;
}
$header[] = t('Operations');
// Build an array of entity rows for the table.
$rows = [];
foreach (Element::children($form) as $key) {
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
$entity = $form[$key]['#entity'];
$row_classes = [
'ief-row-entity',
];
$cells = [];
if ($has_tabledrag) {
$cells[] = [
'data' => [
'#plain_text' => '',
],
'#wrapper_attributes' => [
'class' => [
'ief-tabledrag-handle',
],
],
];
$cells[] = [
'data' => $form[$key]['delta'],
];
$row_classes[] = 'draggable';
}
// Add a special class to rows that have a form underneath, to allow
// for additional styling.
if (!empty($form[$key]['form'])) {
$row_classes[] = 'ief-row-entity-form';
}
foreach ($fields as $field_name => $field) {
if ($field['type'] == 'label') {
$data = [
'#markup' => $variables['form'][$key]['#label'],
];
}
elseif ($field['type'] == 'field' && $entity
->hasField($field_name)) {
$display_options = [
'label' => 'hidden',
];
if (isset($field['display_options'])) {
$display_options += $field['display_options'];
}
$data = $entity
->get($field_name)
->view($display_options);
}
elseif ($field['type'] == 'callback') {
$arguments = [
'entity' => $entity,
'variables' => $variables,
];
if (isset($field['callback_arguments'])) {
$arguments = array_merge($arguments, $field['callback_arguments']);
}
$data = call_user_func_array($field['callback'], $arguments);
// Backward compatibility for callbacks that just provide a string not an array.
if (!is_array($data)) {
$data = [
'#markup' => $data,
];
}
}
else {
$data = [
'#markup' => t('N/A'),
];
}
$cells[$field_name] = array_merge($data, [
'#wrapper_attributes' => [
'class' => [
'inline-entity-form-' . $entity_type . '-' . $field_name,
],
],
]);
}
// Add the buttons belonging to the "Operations" column, when entity is not
// being displayed as a form.
if (empty($form[$key]['form'])) {
$cells['actions'] = $form[$key]['actions'];
}
// Create the row.
$rows[] = $cells + [
'#attributes' => [
'class' => $row_classes,
],
];
// If the current entity array specifies a form, output it in the next row.
if (!empty($form[$key]['form'])) {
$row = [];
$row[] = $form[$key]['form'] + [
'#wrapper_attributes' => [
'colspan' => count($fields) + 1,
],
];
$rows[] = $row + [
'#attributes' => [
'class' => [
'ief-row-form',
],
'no_striping' => TRUE,
],
];
}
}
if (!empty($rows)) {
$tabledrag = [];
if ($has_tabledrag) {
$tabledrag = [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'ief-entity-delta',
],
];
}
$variables['table'] = [
'#type' => 'table',
'#header' => $header,
'#attributes' => [
'id' => 'ief-entity-table-' . $form['#id'],
'class' => [
'ief-entity-table',
],
],
'#tabledrag' => $tabledrag,
] + $rows;
}
}
/**
* Implements hook_migrate_prepare_row().
*/
function inline_entity_form_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
\Drupal::classResolver(MigrationHelper::class)
->alterRow($row, $source, $migration);
}
/**
* Implements hook_migration_plugins_alter().
*/
function inline_entity_form_migration_plugins_alter(array &$migrations) {
\Drupal::classResolver(MigrationHelper::class)
->alterPlugins($migrations);
}
Functions
Name | Description |
---|---|
inline_entity_form_cleanup_form_state | Button #submit callback: Cleans up form state for a closed entity form. |
inline_entity_form_cleanup_row_form_state | Button #submit callback: Cleans up form state for a closed entity row form. |
inline_entity_form_close_all_forms | Closes all open IEF forms. |
inline_entity_form_entity_type_build | Implements hook_entity_type_build(). |
inline_entity_form_form_alter | Implements hook_form_alter(). |
inline_entity_form_get_element | Returns an IEF widget nearest to the triggering element. |
inline_entity_form_migrate_prepare_row | Implements hook_migrate_prepare_row(). |
inline_entity_form_migration_plugins_alter | Implements hook_migration_plugins_alter(). |
inline_entity_form_open_form | Button #submit callback: Opens a form in the IEF widget. |
inline_entity_form_open_row_form | Button #submit callback: Opens a row form in the IEF widget. |
inline_entity_form_reference_form | Provides the form for adding existing entities through an autocomplete field. |
inline_entity_form_reference_form_submit | Submits the form for adding existing entities. |
inline_entity_form_reference_form_validate | Validates the form for adding existing entities. |
inline_entity_form_theme | Implements hook_theme(). |
template_preprocess_inline_entity_form_entity_table | Prepares variables for inline_entity_form_entity_table form templates. |