From 82b5bc08b076eb0d8a0dd01cbe70d2c259581f05 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Thu, 19 Oct 2023 11:46:37 +0200
Subject: [PATCH] Issue #3376692 by lauriii, mstrelan, quietone, acbramley,
 larowlan, smustgrave: Removed values in multi-value fields reappear

---
 core/lib/Drupal/Core/Field/WidgetBase.php     | 12 ++++++++--
 .../MultipleValueWidgetTest.php               | 15 +++++++++++++
 ...erInputMappingOnFieldDeltaElementsTest.php | 22 +++++++++++++++++--
 3 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php
index 7b0fa5496153..9e4de97ff602 100644
--- a/core/lib/Drupal/Core/Field/WidgetBase.php
+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -68,8 +68,7 @@ public function form(FieldItemListInterface $items, array &$form, FormStateInter
     $field_name = $this->fieldDefinition->getName();
     $parents = $form['#parents'];
 
-    // Store field information in $form_state.
-    if (!static::getWidgetState($parents, $field_name, $form_state)) {
+    if (!$field_state = static::getWidgetState($parents, $field_name, $form_state)) {
       $field_state = [
         'items_count' => count($items),
         'array_parents' => [],
@@ -77,6 +76,13 @@ public function form(FieldItemListInterface $items, array &$form, FormStateInter
       static::setWidgetState($parents, $field_name, $form_state, $field_state);
     }
 
+    // Remove deleted items from the field item list.
+    if (isset($field_state['deleted_item']) && $items->get($field_state['deleted_item'])) {
+      $items->removeItem($field_state['deleted_item']);
+      unset($field_state['deleted_item']);
+      static::setWidgetState($parents, $field_name, $form_state, $field_state);
+    }
+
     // Collect widget elements.
     $elements = [];
 
@@ -384,6 +390,8 @@ public static function deleteSubmit(&$form, FormStateInterface $form_state) {
       $form_state->setUserInput($user_input);
     }
 
+    $field_state['deleted_item'] = $delta;
+
     unset($parent_element[$delta]);
     NestedArray::setValue($form, $array_parents, $parent_element);
 
diff --git a/core/modules/field/tests/src/FunctionalJavascript/MultipleValueWidgetTest.php b/core/modules/field/tests/src/FunctionalJavascript/MultipleValueWidgetTest.php
index b4bce7b3d225..730d0c8b44fa 100644
--- a/core/modules/field/tests/src/FunctionalJavascript/MultipleValueWidgetTest.php
+++ b/core/modules/field/tests/src/FunctionalJavascript/MultipleValueWidgetTest.php
@@ -150,6 +150,21 @@ public function testFieldMultipleValueWidget() {
 
     // Assert that the wrapper exists and isn't nested.
     $this->assertSession()->elementsCount('css', '[data-drupal-selector="edit-field-unlimited-wrapper"]', 1);
+
+    // Test removing items/values on saved entities resets to initial value.
+    $this->submitForm([], 'Save');
+    $field_2_remove_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $field_1_remove_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $field_0_remove_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $add_more_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSame('', $field_0->getValue());
+    $add_more_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSame('', $field_1->getValue());
   }
 
 }
diff --git a/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormCorrectUserInputMappingOnFieldDeltaElementsTest.php b/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormCorrectUserInputMappingOnFieldDeltaElementsTest.php
index 1cd176328f5b..bd4cb68c126d 100644
--- a/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormCorrectUserInputMappingOnFieldDeltaElementsTest.php
+++ b/core/tests/Drupal/FunctionalTests/Entity/ContentEntityFormCorrectUserInputMappingOnFieldDeltaElementsTest.php
@@ -77,10 +77,10 @@ protected function setUp(): void {
    * Tests the correct user input mapping on complex fields.
    */
   public function testCorrectUserInputMappingOnComplexFields() {
-    /** @var ContentEntityStorageInterface $storage */
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
     $storage = $this->container->get('entity_type.manager')->getStorage($this->entityTypeId);
 
-    /** @var ContentEntityInterface $entity */
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = $storage->create([
       $this->fieldName => [
         ['shape' => 'rectangle', 'color' => 'green'],
@@ -118,6 +118,24 @@ public function testCorrectUserInputMappingOnComplexFields() {
       ['shape' => 'circle', 'color' => 'blue'],
       ['shape' => 'rectangle', 'color' => 'green'],
     ], $entity->get($this->fieldName)->getValue());
+
+    $this->drupalGet($this->entityTypeId . '/manage/' . $entity->id() . '/edit');
+
+    // Delete one of the field items and ensure that the user input is mapped on
+    // the correct delta field items.
+    $edit = [
+      "$this->fieldName[0][_weight]" => 0,
+      "$this->fieldName[1][_weight]" => -1,
+    ];
+    $this->submitForm($edit, "{$this->fieldName}_0_remove_button");
+    $this->submitForm([], 'Save');
+
+    $storage->resetCache([$entity->id()]);
+    $entity = $storage->load($entity->id());
+    $this->assertEquals([
+      ['shape' => 'rectangle', 'color' => 'green'],
+    ], $entity->get($this->fieldName)->getValue());
+
   }
 
 }
-- 
GitLab