diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php index 8a95237..0862caf 100644 --- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php +++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php @@ -30,6 +30,7 @@ public function getInfo() { $info['#target_type'] = NULL; $info['#selection_handler'] = 'default'; $info['#selection_settings'] = array(); + $info['#validate_reference'] = TRUE; $info['#tags'] = FALSE; $info['#autocreate'] = NULL; @@ -77,7 +78,7 @@ public static function processEntityAutocomplete(&$element, FormStateInterface $ throw new \InvalidArgumentException("Missing required #autocreate['bundle'] parameter."); } // Default the autocreate user ID to the current user. - $element['#autocreate']['uid'] = $element['#autocreate']['uid'] ?: \Drupal::currentUser()->id(); + $element['#autocreate']['uid'] = isset($element['#autocreate']['uid']) ? $element['#autocreate']['uid'] : \Drupal::currentUser()->id(); } $element['#autocomplete_route_name'] = 'system.entity_autocomplete'; @@ -134,6 +135,25 @@ public static function validateEntityAutocomplete(&$element, FormStateInterface } } + // Check that the referenced entities are valid, if needed. + if ($element['#validate_reference'] && !$autocreate && !empty($value)) { + $ids = array_reduce($value, function ($return, $item) { + if (isset($item['target_id'])) { + $return[] = $item['target_id']; + } + return $return; + }); + + if ($ids) { + $valid_ids = $handler->validateReferenceableEntities($ids); + if ($invalid_ids = array_diff($ids, $valid_ids)) { + foreach ($invalid_ids as $invalid_id) { + $form_state->setError($element, t('The referenced entity (%type: %id) does not exist.', array('%type' => $element['#target_type'], '%id' => $invalid_id))); + } + } + } + } + // Use only the last value if the form element does not support multiple // matches (tags). if (!$element['#tags'] && !empty($value)) { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/AutocompleteWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/AutocompleteWidget.php index bc646ec..0be6656 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/AutocompleteWidget.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/AutocompleteWidget.php @@ -100,6 +100,9 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen '#target_type' => $this->getFieldSetting('target_type'), '#selection_handler' => $this->getFieldSetting('handler'), '#selection_settings' => $this->getFieldSetting('handler_settings'), + // Entiy reference field items are handling validation themselves via + // the 'ValidReference' constraint. + '#validate_reference' => FALSE, '#maxlength' => 1024, '#default_value' => implode(', ', $this->getLabels($items, $delta)), '#size' => $this->getSetting('size'), diff --git a/core/modules/system/src/Tests/Entity/Element/EntityAutocompleteElementFormTest.php b/core/modules/system/src/Tests/Entity/Element/EntityAutocompleteElementFormTest.php new file mode 100644 index 0000000..d7d0dc1 --- /dev/null +++ b/core/modules/system/src/Tests/Entity/Element/EntityAutocompleteElementFormTest.php @@ -0,0 +1,292 @@ +installSchema('system', array('router')); + \Drupal::service('router.builder')->rebuild(); + + $this->testUser = User::create(array( + 'name' => 'foobar1', + 'mail' => 'foobar1@example.com', + )); + $this->testUser->save(); + \Drupal::service('current_user')->setAccount($this->testUser); + + $this->testAutocreateUser = User::create(array( + 'name' => 'foobar2', + 'mail' => 'foobar2@example.com', + )); + $this->testAutocreateUser->save(); + + for ($i = 1; $i < 3; $i++) { + $entity = EntityTest::create(array( + 'name' => $this->randomMachineName() + )); + $entity->save(); + $this->referencedEntities[] = $entity; + } + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'test_entity_autocomplete'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['single'] = array( + '#type' => 'entity_autocomplete', + '#target_type' => 'entity_test', + ); + $form['single_autocreate'] = array( + '#type' => 'entity_autocomplete', + '#target_type' => 'entity_test', + '#autocreate' => array( + 'bundle' => 'entity_test', + ), + ); + $form['single_autocreate_specific_uid'] = array( + '#type' => 'entity_autocomplete', + '#target_type' => 'entity_test', + '#autocreate' => array( + 'bundle' => 'entity_test', + 'uid' => $this->testAutocreateUser->id(), + ), + ); + + $form['tags'] = array( + '#type' => 'entity_autocomplete', + '#target_type' => 'entity_test', + '#tags' => TRUE, + ); + $form['tags_autocreate'] = array( + '#type' => 'entity_autocomplete', + '#target_type' => 'entity_test', + '#tags' => TRUE, + '#autocreate' => array( + 'bundle' => 'entity_test', + ), + ); + $form['tags_autocreate_specific_uid'] = array( + '#type' => 'entity_autocomplete', + '#target_type' => 'entity_test', + '#tags' => TRUE, + '#autocreate' => array( + 'bundle' => 'entity_test', + 'uid' => $this->testAutocreateUser->id(), + ), + ); + + $form['single_no_validate'] = array( + '#type' => 'entity_autocomplete', + '#target_type' => 'entity_test', + '#validate_reference' => FALSE, + ); + $form['single_autocreate_no_validate'] = array( + '#type' => 'entity_autocomplete', + '#target_type' => 'entity_test', + '#autocreate' => array( + 'bundle' => 'entity_test', + ), + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { } + + /** + * Tests valid entries in the EntityAutocomplete Form API element. + */ + public function testValidEntityAutocompleteElement() { + $form_state = (new FormState()) + ->setValues([ + 'single' => $this->getAutocompleteInput($this->referencedEntities[0]), + 'single_autocreate' => 'single - autocreated entity label', + 'single_autocreate_specific_uid' => 'single - autocreated entity label with specific uid', + 'tags' => $this->getAutocompleteInput($this->referencedEntities[0]) . ', ' . $this->getAutocompleteInput($this->referencedEntities[1]), + 'tags_autocreate' => + $this->getAutocompleteInput($this->referencedEntities[0]) + . ', tags - autocreated entity label, ' + . $this->getAutocompleteInput($this->referencedEntities[1]), + 'tags_autocreate_specific_uid' => + $this->getAutocompleteInput($this->referencedEntities[0]) + . ', tags - autocreated entity label with specific uid, ' + . $this->getAutocompleteInput($this->referencedEntities[1]), + ]); + $form_builder = $this->container->get('form_builder'); + $form_builder->submitForm($this, $form_state); + + // Valid form state. + $this->assertEqual(count($form_state->getErrors()), 0); + + // Test the 'single' element. + $this->assertEqual($form_state->getValue('single'), $this->referencedEntities[0]->id()); + + // Test the 'single_autocreate' element. + $value = $form_state->getValue('single_autocreate'); + $this->assertEqual($value['entity']->label(), 'single - autocreated entity label'); + $this->assertEqual($value['entity']->bundle(), 'entity_test'); + $this->assertEqual($value['entity']->getOwnerId(), $this->testUser->id()); + + // Test the 'single_autocreate_specific_uid' element. + $value = $form_state->getValue('single_autocreate_specific_uid'); + $this->assertEqual($value['entity']->label(), 'single - autocreated entity label with specific uid'); + $this->assertEqual($value['entity']->bundle(), 'entity_test'); + $this->assertEqual($value['entity']->getOwnerId(), $this->testAutocreateUser->id()); + + // Test the 'tags' element. + $expected = array( + array('target_id' => $this->referencedEntities[0]->id()), + array('target_id' => $this->referencedEntities[1]->id()), + ); + $this->assertEqual($form_state->getValue('tags'), $expected); + + // Test the 'single_autocreate' element. + $value = $form_state->getValue('tags_autocreate'); + // First value is an existing entity. + $this->assertEqual($value[0]['target_id'], $this->referencedEntities[0]->id()); + // Second value is an autocreated entity. + $this->assertTrue(!isset($value[1]['target_id'])); + $this->assertEqual($value[1]['entity']->label(), 'tags - autocreated entity label'); + $this->assertEqual($value[1]['entity']->getOwnerId(), $this->testUser->id()); + // Third value is an existing entity. + $this->assertEqual($value[2]['target_id'], $this->referencedEntities[1]->id()); + + // Test the 'tags_autocreate_specific_uid' element. + $value = $form_state->getValue('tags_autocreate_specific_uid'); + // First value is an existing entity. + $this->assertEqual($value[0]['target_id'], $this->referencedEntities[0]->id()); + // Second value is an autocreated entity. + $this->assertTrue(!isset($value[1]['target_id'])); + $this->assertEqual($value[1]['entity']->label(), 'tags - autocreated entity label with specific uid'); + $this->assertEqual($value[1]['entity']->getOwnerId(), $this->testAutocreateUser->id()); + // Third value is an existing entity. + $this->assertEqual($value[2]['target_id'], $this->referencedEntities[1]->id()); + } + + /** + * Tests invalid entries in the EntityAutocomplete Form API element. + */ + public function testInvalidEntityAutocompleteElement() { + $form_builder = $this->container->get('form_builder'); + + // Test 'single' with a entity label that doesn't exist + $form_state = (new FormState()) + ->setValues([ + 'single' => 'single - inexistent label', + ]); + $form_builder->submitForm($this, $form_state); + $this->assertEqual(count($form_state->getErrors()), 1); + $this->assertEqual($form_state->getErrors()['single'], t('There are no entities matching "%value".', array('%value' => 'single - inexistent label'))); + + // Test 'single' with a entity ID that doesn't exist. + $form_state = (new FormState()) + ->setValues([ + 'single' => 'single - inexistent label (42)', + ]); + $form_builder->submitForm($this, $form_state); + $this->assertEqual(count($form_state->getErrors()), 1); + $this->assertEqual($form_state->getErrors()['single'], t('The referenced entity (%type: %id) does not exist.', array('%type' => 'entity_test', '%id' => 42))); + + // Do the same tests as above but on an element with '#validate_reference' + // set to FALSE. + $form_state = (new FormState()) + ->setValues([ + 'single_no_validate' => 'single - inexistent label', + 'single_autocreate_no_validate' => 'single - autocreate inexistent label' + ]); + $form_builder->submitForm($this, $form_state); + + // The element without 'autocreate' support still has to emit a warning when + // the input doesn't end with an entity ID enclosed in parentheses. + $this->assertEqual(count($form_state->getErrors()), 1); + $this->assertEqual($form_state->getErrors()['single_no_validate'], t('There are no entities matching "%value".', array('%value' => 'single - inexistent label'))); + + $form_state = (new FormState()) + ->setValues([ + 'single_no_validate' => 'single - inexistent label (42)', + 'single_autocreate_no_validate' => 'single - autocreate inexistent label (43)' + ]); + $form_builder->submitForm($this, $form_state); + + // The input is complete (i.e. contains an entity ID at the ent), no errors + // are triggered. + $this->assertEqual(count($form_state->getErrors()), 0); + } + + + /** + * Returns an entity label in the format needed by the EntityAutocomplete + * element. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * A Drupal entity. + * + * @return string + * A string that can be used as a value for EntityAutocomplete elements. + */ + protected function getAutocompleteInput(EntityInterface $entity) { + return $entity->label() . ' (' . $entity->id() . ')'; + } + +}