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