From 774c01bc690de25eb18473226bd9bea8fb0a2c5c Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Sun, 8 May 2016 12:55:05 -0500
Subject: [PATCH] Issue #2718697 by damiankloip, dawehner: EntityAutocomplete
 element cannot handle GET input values

(cherry picked from commit 677116e1497a2ee4141878ce09cf13a2ef9ebb00)
---
 .../Entity/Element/EntityAutocomplete.php     | 75 ++++++++++++-------
 .../user/src/Plugin/views/filter/Name.php     |  2 +-
 .../Tests/Views/HandlerFilterUserNameTest.php | 23 ++++++
 .../EntityAutocompleteElementFormTest.php     | 25 +++++++
 4 files changed, 98 insertions(+), 27 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
index 3e17e80df932..23fd0b50a3f2 100644
--- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
+++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
@@ -57,19 +57,33 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
       if (is_array($element['#default_value']) && $element['#tags'] !== TRUE) {
         throw new \InvalidArgumentException('The #default_value property is an array but the form element does not allow multiple values.');
       }
-      elseif (!is_array($element['#default_value'])) {
+      elseif (!empty($element['#default_value']) && !is_array($element['#default_value'])) {
         // Convert the default value into an array for easier processing in
         // static::getEntityLabels().
         $element['#default_value'] = array($element['#default_value']);
       }
 
-      if ($element['#default_value'] && !(reset($element['#default_value']) instanceof EntityInterface)) {
-        throw new \InvalidArgumentException('The #default_value property has to be an entity object or an array of entity objects.');
+      if ($element['#default_value']) {
+        if (!(reset($element['#default_value']) instanceof EntityInterface)) {
+          throw new \InvalidArgumentException('The #default_value property has to be an entity object or an array of entity objects.');
+        }
+
+        // Extract the labels from the passed-in entity objects, taking access
+        // checks into account.
+        return static::getEntityLabels($element['#default_value']);
       }
+    }
 
-      // Extract the labels from the passed-in entity objects, taking access
-      // checks into account.
-      return static::getEntityLabels($element['#default_value']);
+    // Potentially the #value is set directly, so it contains the 'target_id'
+    // array structure instead of a string.
+    if ($input !== FALSE && is_array($input)) {
+      $entity_ids = array_map(function(array $item) {
+        return $item['target_id'];
+      }, $input);
+
+      $entities = \Drupal::entityTypeManager()->getStorage($element['#target_type'])->loadMultiple($entity_ids);
+
+      return static::getEntityLabels($entities);
     }
   }
 
@@ -136,6 +150,7 @@ public static function processEntityAutocomplete(array &$element, FormStateInter
    */
   public static function validateEntityAutocomplete(array &$element, FormStateInterface $form_state, array &$complete_form) {
     $value = NULL;
+
     if (!empty($element['#value'])) {
       $options = array(
         'target_type' => $element['#target_type'],
@@ -146,27 +161,35 @@ public static function validateEntityAutocomplete(array &$element, FormStateInte
       $handler = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options);
       $autocreate = (bool) $element['#autocreate'] && $handler instanceof SelectionWithAutocreateInterface;
 
-      $input_values = $element['#tags'] ? Tags::explode($element['#value']) : array($element['#value']);
-      foreach ($input_values as $input) {
-        $match = static::extractEntityIdFromAutocompleteInput($input);
-        if ($match === NULL) {
-          // Try to get a match from the input string when the user didn't use
-          // the autocomplete but filled in a value manually.
-          $match = static::matchEntityByTitle($handler, $input, $element, $form_state, !$autocreate);
-        }
+      // GET forms might pass the validated data around on the next request, in
+      // which case it will already be in the expected format.
+      if (is_array($element['#value'])) {
+        $value = $element['#value'];
+      }
+      else {
+        $input_values = $element['#tags'] ? Tags::explode($element['#value']) : array($element['#value']);
+
+        foreach ($input_values as $input) {
+          $match = static::extractEntityIdFromAutocompleteInput($input);
+          if ($match === NULL) {
+            // Try to get a match from the input string when the user didn't use
+            // the autocomplete but filled in a value manually.
+            $match = static::matchEntityByTitle($handler, $input, $element, $form_state, !$autocreate);
+          }
 
-        if ($match !== NULL) {
-          $value[] = array(
-            'target_id' => $match,
-          );
-        }
-        elseif ($autocreate) {
-          /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface $handler */
-          // Auto-create item. See an example of how this is handled in
-          // \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::presave().
-          $value[] = array(
-            'entity' => $handler->createNewEntity($element['#target_type'], $element['#autocreate']['bundle'], $input, $element['#autocreate']['uid']),
-          );
+          if ($match !== NULL) {
+            $value[] = array(
+              'target_id' => $match,
+            );
+          }
+          elseif ($autocreate) {
+            /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface $handler */
+            // Auto-create item. See an example of how this is handled in
+            // \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::presave().
+            $value[] = array(
+              'entity' => $handler->createNewEntity($element['#target_type'], $element['#autocreate']['bundle'], $input, $element['#autocreate']['uid']),
+            );
+          }
         }
       }
 
diff --git a/core/modules/user/src/Plugin/views/filter/Name.php b/core/modules/user/src/Plugin/views/filter/Name.php
index 7554adfd21d6..d31c78102ebb 100644
--- a/core/modules/user/src/Plugin/views/filter/Name.php
+++ b/core/modules/user/src/Plugin/views/filter/Name.php
@@ -28,7 +28,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
       '#target_type' => 'user',
       '#tags' => TRUE,
       '#default_value' => $default_value,
-      '#process_default_value' => FALSE,
+      '#process_default_value' => $this->isExposed(),
     );
 
     $user_input = $form_state->getUserInput();
diff --git a/core/modules/user/src/Tests/Views/HandlerFilterUserNameTest.php b/core/modules/user/src/Tests/Views/HandlerFilterUserNameTest.php
index e0cba5e6bb08..eaf24aa0fd62 100644
--- a/core/modules/user/src/Tests/Views/HandlerFilterUserNameTest.php
+++ b/core/modules/user/src/Tests/Views/HandlerFilterUserNameTest.php
@@ -137,6 +137,17 @@ public function testExposedFilter() {
     $this->drupalGet($path, $options);
     $this->assertRaw(t('There are no entities matching "%value".', array('%value' => implode(', ', $users))));
 
+    // Pass in an invalid target_id in for the entity_autocomplete value format.
+    // There should be no errors, but all results should be returned as the
+    // default value for the autocomplete will not match any users so should
+    // be empty.
+    $options['query']['uid'] = [['target_id' => 9999]];
+    $this->drupalGet($path, $options);
+    // The actual result should contain all of the user ids.
+    foreach ($this->accounts as $account) {
+      $this->assertRaw($account->id());
+    }
+
     // Pass in an invalid username and a valid username.
     $users = array($this->randomMachineName(), $this->names[0]);
     $users = array_map('strtolower', $users);
@@ -156,6 +167,18 @@ public function testExposedFilter() {
     foreach ($this->accounts as $account) {
       $this->assertRaw($account->id());
     }
+
+    // Pass in just valid user IDs in the entity_autocomplete target_id format.
+    $options['query']['uid'] = array_map(function($account) {
+      return ['target_id' => $account->id()];
+    }, $this->accounts);
+
+    $this->drupalGet($path, $options);
+    $this->assertNoRaw('Unable to find user');
+    // The actual result should contain all of the user ids.
+    foreach ($this->accounts as $account) {
+      $this->assertRaw($account->id());
+    }
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/Element/EntityAutocompleteElementFormTest.php b/core/tests/Drupal/KernelTests/Core/Entity/Element/EntityAutocompleteElementFormTest.php
index a0286756acfd..ec81f7b38f8e 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/Element/EntityAutocompleteElementFormTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/Element/EntityAutocompleteElementFormTest.php
@@ -310,6 +310,31 @@ public function testEntityAutocompleteAccess() {
     $this->assertEqual($form['tags_access']['#value'], $expected);
   }
 
+  /**
+   * Tests ID input is handled correctly.
+   *
+   * E.g. This can happen with GET form parameters.
+   */
+  public function testEntityAutocompleteIdInput() {
+    /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */
+    $form_builder = $this->container->get('form_builder');
+    //$form = $form_builder->getForm($this);
+    $form_state = (new FormState())
+      ->setMethod('GET')
+      ->setValues([
+        'single' => [['target_id' => $this->referencedEntities[0]->id()]],
+        'single_no_validate' => [['target_id' => $this->referencedEntities[0]->id()]],
+      ]);
+
+    $form_builder->submitForm($this, $form_state);
+
+    $form = $form_state->getCompleteForm();
+
+    $expected_label = $this->getAutocompleteInput($this->referencedEntities[0]);
+    $this->assertSame($expected_label, $form['single']['#value']);
+    $this->assertSame($expected_label, $form['single_no_validate']['#value']);
+  }
+
   /**
    * Returns an entity label in the format needed by the EntityAutocomplete
    * element.
-- 
GitLab