3 namespace Drupal\inline_entity_form\Form;
5 use Drupal\Core\Entity\ContentEntityInterface;
6 use Drupal\Core\Entity\Entity\EntityFormDisplay;
7 use Drupal\Core\Entity\EntityInterface;
8 use Drupal\Core\Entity\EntityFieldManagerInterface;
9 use Drupal\Core\Entity\EntityTypeInterface;
10 use Drupal\Core\Entity\EntityTypeManagerInterface;
11 use Drupal\Core\Extension\ModuleHandlerInterface;
12 use Drupal\Core\Field\WidgetBase;
13 use Drupal\Core\Form\FormStateInterface;
14 use Drupal\Core\Render\Element;
15 use Drupal\inline_entity_form\InlineFormInterface;
16 use Symfony\Component\DependencyInjection\ContainerInterface;
19 * Generic entity inline form handler.
21 class EntityInlineForm implements InlineFormInterface {
24 * The entity field manager.
26 * @var \Drupal\Core\Entity\EntityFieldManagerInterface
28 protected $entityFieldManager;
31 * The entity type manager.
33 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
35 protected $entityTypeManager;
38 * The entity type managed by this handler.
40 * @var \Drupal\Core\Entity\EntityTypeInterface
42 protected $entityType;
45 * Module handler service.
47 * @var \Drupal\Core\Extension\ModuleHandlerInterface
49 protected $moduleHandler;
52 * Constructs the inline entity form controller.
54 * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
55 * The entity field manager.
56 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
57 * The entity type manager.
58 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
60 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
63 public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, EntityTypeInterface $entity_type) {
64 $this->entityFieldManager = $entity_field_manager;
65 $this->entityTypeManager = $entity_type_manager;
66 $this->moduleHandler = $module_handler;
67 $this->entityType = $entity_type;
73 public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
75 $container->get('entity_field.manager'),
76 $container->get('entity_type.manager'),
77 $container->get('module_handler'),
85 public function getEntityType() {
86 return $this->entityType;
92 public function getEntityTypeLabels() {
93 $lowercase_label = $this->entityType->getLowercaseLabel();
95 'singular' => $lowercase_label,
96 'plural' => t('@entity_type entities', ['@entity_type' => $lowercase_label]),
103 public function getEntityLabel(EntityInterface $entity) {
104 return $entity->label();
110 public function getTableFields($bundles) {
111 $definitions = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id());
112 $label_key = $this->entityType->getKey('label');
113 $label_field_label = t('Label');
114 if ($label_key && isset($definitions[$label_key])) {
115 $label_field_label = $definitions[$label_key]->getLabel();
117 $bundle_key = $this->entityType->getKey('bundle');
118 $bundle_field_label = t('Type');
119 if ($bundle_key && isset($definitions[$bundle_key])) {
120 $bundle_field_label = $definitions[$bundle_key]->getLabel();
126 'label' => $label_field_label,
129 if (count($bundles) > 1) {
130 $fields[$bundle_key] = [
132 'label' => $bundle_field_label,
134 'display_options' => [
135 'type' => 'entity_reference_label',
136 'settings' => ['link' => FALSE],
147 public function isTableDragEnabled($element) {
148 $children = Element::children($element);
149 // If there is only one row, disable tabledrag.
150 if (count($children) == 1) {
153 // If one of the rows is in form context, disable tabledrag.
154 foreach ($children as $key) {
155 if (!empty($element[$key]['form'])) {
166 public function entityForm(array $entity_form, FormStateInterface $form_state) {
167 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
168 $entity = $entity_form['#entity'];
169 $form_display = $this->getFormDisplay($entity, $entity_form['#form_mode']);
170 $form_display->buildForm($entity, $entity_form, $form_state);
171 $entity_form['#ief_element_submit'][] = [get_class($this), 'submitCleanFormState'];
172 // Inline entities inherit the parent language.
173 $langcode_key = $this->entityType->getKey('langcode');
174 if ($langcode_key && isset($entity_form[$langcode_key])) {
175 $entity_form[$langcode_key]['#access'] = FALSE;
177 if (!empty($entity_form['#translating'])) {
178 // Hide the non-translatable fields.
179 foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
180 if (isset($entity_form[$field_name]) && $field_name != $langcode_key) {
181 $entity_form[$field_name]['#access'] = $definition->isTranslatable();
186 // Determine the children of the entity form before it has been altered.
187 $children_before = Element::children($entity_form);
189 // Allow other modules to alter the form.
190 $this->moduleHandler->alter('inline_entity_form_entity_form', $entity_form, $form_state);
192 // Determine the children of the entity form after it has been altered.
193 $children_after = Element::children($entity_form);
195 // Ensure that any new children added have #tree, #parents, #array_parents
196 // and handle setting the proper #group if it's referencing a local element.
197 // Note: the #tree, #parents and #array_parents code is a direct copy from
198 // \Drupal\Core\Form\FormBuilder::doBuildForm.
199 $children_diff = array_diff($children_after, $children_before);
200 foreach ($children_diff as $child) {
201 // Don't squash an existing tree value.
202 if (!isset($entity_form[$child]['#tree'])) {
203 $entity_form[$child]['#tree'] = $entity_form['#tree'];
206 // Don't squash existing parents value.
207 if (!isset($entity_form[$child]['#parents'])) {
208 // Check to see if a tree of child elements is present. If so,
209 // continue down the tree if required.
210 $entity_form[$child]['#parents'] = $entity_form[$child]['#tree'] && $entity_form['#tree'] ? array_merge($entity_form['#parents'], [$child]) : [$child];
213 // Ensure #array_parents follows the actual form structure.
214 $array_parents = $entity_form['#array_parents'];
215 $array_parents[] = $child;
216 $entity_form[$child]['#array_parents'] = $array_parents;
218 // Detect if there is a #group and it specifies a local element. If so,
219 // change it to use the proper local element's #parents group name.
220 if (isset($entity_form[$child]['#group']) && isset($entity_form[$entity_form[$child]['#group']])) {
221 $entity_form[$child]['#group'] = implode('][', $entity_form[$entity_form[$child]['#group']]['#parents']);
231 public function entityFormValidate(array &$entity_form, FormStateInterface $form_state) {
232 // Perform entity validation only if the inline form was submitted,
233 // skipping other requests such as file uploads.
234 $triggering_element = $form_state->getTriggeringElement();
235 if (!empty($triggering_element['#ief_submit_trigger'])) {
236 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
237 $entity = $entity_form['#entity'];
238 $this->buildEntity($entity_form, $entity, $form_state);
239 $form_display = $this->getFormDisplay($entity, $entity_form['#form_mode']);
240 $form_display->validateFormValues($entity, $entity_form, $form_state);
241 $entity->setValidationRequired(FALSE);
243 foreach ($form_state->getErrors() as $name => $message) {
244 // $name may be unknown in $form_state and
245 // $form_state->setErrorByName($name, $message) may suppress the error message.
246 $form_state->setError($triggering_element, $message);
254 public function entityFormSubmit(array &$entity_form, FormStateInterface $form_state) {
255 $form_state->cleanValues();
256 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
257 $entity = $entity_form['#entity'];
258 $this->buildEntity($entity_form, $entity, $form_state);
264 public function save(EntityInterface $entity) {
271 public function delete($ids, $context) {
272 $storage_handler = $this->entityTypeManager->getStorage($this->entityType->id());
273 $entities = $storage_handler->loadMultiple($ids);
274 $storage_handler->delete($entities);
278 * Builds an updated entity object based upon the submitted form values.
280 * @param array $entity_form
282 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
284 * @param \Drupal\Core\Form\FormStateInterface $form_state
285 * The current state of the form.
287 protected function buildEntity(array $entity_form, ContentEntityInterface $entity, FormStateInterface $form_state) {
288 $form_display = $this->getFormDisplay($entity, $entity_form['#form_mode']);
289 $form_display->extractFormValues($entity, $entity_form, $form_state);
290 // Invoke all specified builders for copying form values to entity fields.
291 if (isset($entity_form['#entity_builders'])) {
292 foreach ($entity_form['#entity_builders'] as $function) {
293 call_user_func_array($function, [$entity->getEntityTypeId(), $entity, &$entity_form, &$form_state]);
299 * Cleans up the form state for a submitted entity form.
301 * After field_attach_submit() has run and the form has been closed, the form
302 * state still contains field data in $form_state->get('field'). Unless that
303 * data is removed, the next form with the same #parents (reopened add form,
304 * for example) will contain data (i.e. uploaded files) from the previous form.
306 * @param $entity_form
309 * The form state of the parent form.
311 public static function submitCleanFormState(&$entity_form, FormStateInterface $form_state) {
312 /** @var \Drupal\Core\Entity\EntityInterface $entity */
313 $entity = $entity_form['#entity'];
314 $bundle = $entity->bundle();
315 /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $instances */
316 $instances = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_form['#entity_type'], $bundle);
317 foreach ($instances as $instance) {
318 $field_name = $instance->getName();
319 if (!empty($entity_form[$field_name]['#parents'])) {
320 $parents = $entity_form[$field_name]['#parents'];
322 if (!empty($parents)) {
324 WidgetBase::setWidgetState($parents, $field_name, $form_state, $field_state);
331 * Gets the form display for the given entity.
333 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
335 * @param string $form_mode
338 * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
341 protected function getFormDisplay(ContentEntityInterface $entity, $form_mode) {
342 return EntityFormDisplay::collectRenderDisplay($entity, $form_mode);