From eaaaa4c3f0d6093c62692d13f9989df802f9c2e3 Mon Sep 17 00:00:00 2001
From: Spokje <39382-Spokje@users.noreply.drupalcode.org>
Date: Thu, 11 May 2023 13:00:32 +0300
Subject: [PATCH] Issue #2581223 by Spokje, Dylan Donkersgoed, attisan,
 DieterHolvoet, jienckebd, Suresh Prabhu Parkala, KapilV, dwkitchen: Support
 for configuration entities

---
 inline_entity_form.module           |  17 ++-
 src/Element/InlineEntityForm.php    |   7 +-
 src/Form/ConfigEntityInlineForm.php | 163 ++++++++++++++++++++++++++++
 src/Form/EntityInlineForm.php       |   4 +-
 src/TranslationHelper.php           |  87 ++++++++-------
 5 files changed, 230 insertions(+), 48 deletions(-)
 create mode 100644 src/Form/ConfigEntityInlineForm.php

diff --git a/inline_entity_form.module b/inline_entity_form.module
index 1636ba24..8545189f 100644
--- a/inline_entity_form.module
+++ b/inline_entity_form.module
@@ -11,11 +11,12 @@
  */
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
+use Drupal\Core\Entity\ContentEntityTypeInterface;
 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;
@@ -33,7 +34,12 @@ function inline_entity_form_entity_type_build(array &$entity_types) {
 
   foreach ($entity_types as &$entity_type) {
     if (!$entity_type->hasHandlerClass('inline_form')) {
-      $entity_type->setHandlerClass('inline_form', '\Drupal\inline_entity_form\Form\EntityInlineForm');
+      if ($entity_type instanceof ContentEntityTypeInterface) {
+        $entity_type->setHandlerClass('inline_form', '\Drupal\inline_entity_form\Form\EntityInlineForm');
+      }
+      elseif ($entity_type instanceof ConfigEntityTypeInterface) {
+        $entity_type->setHandlerClass('inline_form', '\Drupal\inline_entity_form\Form\ConfigEntityInlineForm');
+      }
     }
   }
 }
@@ -244,7 +250,9 @@ function inline_entity_form_open_form(array $form, FormStateInterface $form_stat
  */
 function inline_entity_form_cleanup_form_state(array $form, FormStateInterface $form_state) {
   $element = inline_entity_form_get_element($form, $form_state);
-  EntityInlineForm::submitCleanFormState($element['form']['inline_entity_form'], $form_state);
+  $entity_type = $element['form']['inline_entity_form']['#entity_type'];
+  $handler = \Drupal::entityTypeManager()->getHandler($entity_type, 'inline_form');
+  get_class($handler)::submitCleanFormState($element['form']['inline_entity_form'], $form_state);
 }
 
 /**
@@ -310,7 +318,8 @@ function inline_entity_form_cleanup_row_form_state(array $form, FormStateInterfa
   $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);
+  $handler = \Drupal::entityTypeManager()->getHandler($entity_form['#entity_type'], 'inline_form');
+  get_class($handler)::submitCleanFormState($entity_form, $form_state);
 }
 
 /**
diff --git a/src/Element/InlineEntityForm.php b/src/Element/InlineEntityForm.php
index 74a71424..4bce4d40 100644
--- a/src/Element/InlineEntityForm.php
+++ b/src/Element/InlineEntityForm.php
@@ -6,6 +6,7 @@ use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\RevisionLogInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element\RenderElement;
+use Drupal\Core\TypedData\TranslatableInterface;
 use Drupal\inline_entity_form\ElementSubmit;
 use Drupal\inline_entity_form\TranslationHelper;
 
@@ -131,8 +132,10 @@ class InlineEntityForm extends RenderElement {
       }
     }
     // Prepare the entity form and the entity itself for translating.
-    $entity_form['#entity'] = TranslationHelper::prepareEntity($entity_form['#entity'], $form_state);
-    $entity_form['#translating'] = TranslationHelper::isTranslating($form_state) && $entity_form['#entity']->isTranslatable();
+    if ($entity_form['#entity'] instanceof TranslatableInterface) {
+      $entity_form['#entity'] = TranslationHelper::prepareEntity($entity_form['#entity'], $form_state);
+      $entity_form['#translating'] = TranslationHelper::isTranslating($form_state) && $entity_form['#entity']->isTranslatable();
+    }
 
     // Handle revisioning if the entity supports it.
     if ($entity_type->isRevisionable() && $entity_form['#revision']) {
diff --git a/src/Form/ConfigEntityInlineForm.php b/src/Form/ConfigEntityInlineForm.php
new file mode 100644
index 00000000..fef46993
--- /dev/null
+++ b/src/Form/ConfigEntityInlineForm.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Drupal\inline_entity_form\Form;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\SubformState;
+
+/**
+ * Generic entity inline form handler.
+ */
+class ConfigEntityInlineForm extends EntityInlineForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTableFields($bundles) {
+
+    $fields = [];
+    $fields['label'] = [
+      'type' => 'label',
+      'label' => $this->getEntityType()->getLabel(),
+      'weight' => 1,
+    ];
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function entityForm(array $entity_form, FormStateInterface $form_state) {
+    /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
+    $entity = $entity_form['#entity'];
+    $form_object = $this->getFormObject($entity, $entity_form['#form_mode']);
+    $entity_form += $form_object->form($entity_form, $this->getSubformState($entity_form, $form_state));
+    $entity_form['#ief_element_submit'][] = [
+      get_class($this),
+      'submitCleanFormState',
+    ];
+    $this->expandCallbacks($form_object, $entity_form['#after_build']);
+    $this->expandCallbacks($form_object, $entity_form['#pre_render']);
+    // Allow other modules to alter the form.
+    $this->moduleHandler->alter('inline_entity_form_entity_form', $entity_form, $form_state);
+
+    return $entity_form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function entityFormValidate(array &$entity_form, FormStateInterface $form_state) {
+    // Perform entity validation only if the inline form was submitted,
+    // skipping other requests such as file uploads.
+    $triggering_element = $form_state->getTriggeringElement();
+    if (!empty($triggering_element['#ief_submit_trigger'])) {
+      /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
+      $entity = $entity_form['#entity'];
+      $this->buildEntity($entity_form, $entity, $form_state);
+
+      $form_object = $this->getFormObject($entity, $entity_form['#form_mode']);
+      $form_object->validateForm($entity_form, $this->getSubformState($entity_form, $form_state));
+
+      foreach ($form_state->getErrors() as $name => $message) {
+        // $name may be unknown in $form_state and
+        // $form_state->setErrorByName($name, $message) may suppress the error
+        // message.
+        $form_state->setError($triggering_element, $message);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function buildEntity(array $entity_form, EntityInterface $entity, FormStateInterface $form_state) {
+    // Start section based on protected EntityForm::copyFormValuesToEntity()
+    // method.
+    $subform_state = $this->getSubformState($entity_form, $form_state);
+    $values = $subform_state->getValues();
+    if ($entity instanceof EntityWithPluginCollectionInterface) {
+      // Do not manually update values represented by plugin collections.
+      $values = array_diff_key($values, $entity->getPluginCollections());
+    }
+    // Invoke all specified builders for copying form values to entity fields.
+    if (isset($entity_form['#entity_builders'])) {
+      foreach ($entity_form['#entity_builders'] as $function) {
+        call_user_func_array($function, [
+          $entity->getEntityTypeId(),
+          $entity,
+          &$entity_form,
+          &$form_state,
+        ]);
+      }
+    }
+    foreach ($values as $key => $value) {
+      $entity->set($key, $value);
+    }
+    // End section based on protected EntityForm::copyFormValuesToEntity()
+    // method.
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Not applicable for config entities since they are not fieldable.
+   */
+  public static function submitCleanFormState(&$entity_form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * Get the form object for the config entity and form mode.
+   *
+   * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
+   *   The config entity.
+   * @param string $form_mode
+   *   The form mode.
+   *
+   * @return \Drupal\Core\Entity\EntityFormInterface
+   *   The form object.
+   */
+  protected function getFormObject(ConfigEntityInterface $entity, $form_mode) {
+    $form_object = $this->entityTypeManager->getFormObject($entity->getEntityTypeId(), $form_mode);
+    $form_object->setEntity($entity);
+
+    return $form_object;
+  }
+
+  /**
+   * Get the subform state for the entity form.
+   *
+   * @param array $entity_form
+   *   The entity form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state of the main form.
+   *
+   * @return \Drupal\Core\Form\SubformState
+   *   The subform state for the entity form.
+   */
+  protected function getSubformState(array $entity_form, FormStateInterface $form_state) {
+    return SubformState::createForSubform($entity_form, $form_state->getCompleteForm(), $form_state);
+  }
+
+  /**
+   * Expand callbacks in the form ::methodName() to className::methodName().
+   *
+   * @param \Drupal\Core\Form\FormInterface $form_object
+   *   The form object the callback belongs to.
+   * @param array &$callbacks
+   *   An array of callbacks to expand.
+   */
+  protected function expandCallbacks(FormInterface $form_object, array &$callbacks) {
+    foreach ($callbacks as &$callback) {
+      if (is_string($callback) && strpos($callback, '::') === 0) {
+        $callback = [$form_object, substr($callback, 2)];
+      }
+    }
+  }
+
+}
diff --git a/src/Form/EntityInlineForm.php b/src/Form/EntityInlineForm.php
index b767601c..f6d4a578 100644
--- a/src/Form/EntityInlineForm.php
+++ b/src/Form/EntityInlineForm.php
@@ -309,12 +309,12 @@ class EntityInlineForm implements InlineFormInterface {
    *
    * @param array $entity_form
    *   The entity form.
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The entity.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
    */
-  protected function buildEntity(array $entity_form, ContentEntityInterface $entity, FormStateInterface $form_state) {
+  protected function buildEntity(array $entity_form, EntityInterface $entity, FormStateInterface $form_state) {
     $form_display = $this->getFormDisplay($entity, $entity_form['#form_mode']);
     $form_display->extractFormValues($entity, $entity_form, $form_state);
     // Invoke all specified builders for copying form values to entity fields.
diff --git a/src/TranslationHelper.php b/src/TranslationHelper.php
index fd5d88c5..68e0200f 100644
--- a/src/TranslationHelper.php
+++ b/src/TranslationHelper.php
@@ -2,7 +2,8 @@
 
 namespace Drupal\inline_entity_form;
 
-use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityTypeInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 
 /**
@@ -13,43 +14,46 @@ class TranslationHelper {
   /**
    * Prepares the inline entity for translation.
    *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The inline entity.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The form state.
    *
-   * @return \Drupal\Core\Entity\ContentEntityInterface
+   * @return \Drupal\Core\Entity\EntityInterface
    *   The prepared entity.
    *
    * @see \Drupal\Core\Entity\ContentEntityForm::initFormLangcodes()
    */
-  public static function prepareEntity(ContentEntityInterface $entity, FormStateInterface $form_state) {
-    $form_langcode = $form_state->get('langcode');
-    if (empty($form_langcode) || !$entity->isTranslatable()) {
-      return $entity;
-    }
-
-    $entity_langcode = $entity->language()->getId();
-    if (self::isTranslating($form_state) && !$entity->hasTranslation($form_langcode)) {
-      // Create a translation from the source language values.
-      $source = $form_state->get(['content_translation', 'source']);
-      $source_langcode = $source ? $source->getId() : $entity_langcode;
-      if (!$entity->hasTranslation($source_langcode)) {
-        $entity->addTranslation($source_langcode, $entity->toArray());
+  public static function prepareEntity(EntityInterface $entity, FormStateInterface $form_state) {
+    if ($entity instanceof ContentEntityTypeInterface) {
+      $form_langcode = $form_state->get('langcode');
+      if (empty($form_langcode) || !$entity->isTranslatable()) {
+        return $entity;
       }
-      $source_translation = $entity->getTranslation($source_langcode);
-      $entity->addTranslation($form_langcode, $source_translation->toArray());
-      $translation = $entity->getTranslation($form_langcode);
-      $translation->set('content_translation_source', $source_langcode);
-      // Make sure we do not inherit the affected status from the source values.
-      if ($entity->getEntityType()->isRevisionable()) {
-        $translation->setRevisionTranslationAffected(NULL);
+
+      $entity_langcode = $entity->language()->getId();
+      if (self::isTranslating($form_state) && !$entity->hasTranslation($form_langcode)) {
+        // Create a translation from the source language values.
+        $source = $form_state->get(['content_translation', 'source']);
+        $source_langcode = $source ? $source->getId() : $entity_langcode;
+        if (!$entity->hasTranslation($source_langcode)) {
+          $entity->addTranslation($source_langcode, $entity->toArray());
+        }
+        $source_translation = $entity->getTranslation($source_langcode);
+        $entity->addTranslation($form_langcode, $source_translation->toArray());
+        $translation = $entity->getTranslation($form_langcode);
+        $translation->set('content_translation_source', $source_langcode);
+        // Make sure we do not inherit the affected status from the source
+        // values.
+        if ($entity->getEntityType()->isRevisionable()) {
+          $translation->setRevisionTranslationAffected(NULL);
+        }
       }
-    }
 
-    if ($entity_langcode != $form_langcode && $entity->hasTranslation($form_langcode)) {
-      // Switch to the needed translation.
-      $entity = $entity->getTranslation($form_langcode);
+      if ($entity_langcode != $form_langcode && $entity->hasTranslation($form_langcode)) {
+        // Switch to the needed translation.
+        $entity = $entity->getTranslation($form_langcode);
+      }
     }
 
     return $entity;
@@ -61,7 +65,7 @@ class TranslationHelper {
    * Called on submit to allow the user to select a different language through
    * the langcode form element, which is then transferred to form state.
    *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The entity.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
@@ -69,20 +73,23 @@ class TranslationHelper {
    * @return bool
    *   TRUE if the entity langcode was updated, FALSE otherwise.
    */
-  public static function updateEntityLangcode(ContentEntityInterface $entity, FormStateInterface $form_state) {
+  public static function updateEntityLangcode(EntityInterface $entity, FormStateInterface $form_state) {
     $changed = FALSE;
-    // This method is first called during form validation, at which point
-    // the 'langcode' form state flag hasn't been updated with the new value.
-    $form_langcode = $form_state->getValue(['langcode', 0, 'value'], $form_state->get('langcode'));
-    if (empty($form_langcode) || !$entity->isTranslatable()) {
-      return $changed;
-    }
 
-    $entity_langcode = $entity->language()->getId();
-    if ($entity_langcode != $form_langcode && !$entity->hasTranslation($form_langcode)) {
-      $langcode_key = $entity->getEntityType()->getKey('langcode');
-      $entity->set($langcode_key, $form_langcode);
-      $changed = TRUE;
+    if ($entity instanceof ContentEntityTypeInterface) {
+      // This method is first called during form validation, at which point
+      // the 'langcode' form state flag hasn't been updated with the new value.
+      $form_langcode = $form_state->getValue(['langcode', 0, 'value'], $form_state->get('langcode'));
+      if (empty($form_langcode) || !$entity->isTranslatable()) {
+        return $changed;
+      }
+
+      $entity_langcode = $entity->language()->getId();
+      if ($entity_langcode != $form_langcode && !$entity->hasTranslation($form_langcode)) {
+        $langcode_key = $entity->getEntityType()->getKey('langcode');
+        $entity->set($langcode_key, $form_langcode);
+        $changed = TRUE;
+      }
     }
 
     return $changed;
-- 
GitLab