Issue #2826826 by vasike, dpi, raman.b, rpayanm, jibran, gpap, mpolishchuck, rwohleb, ranjith_kumar_k_u, smustgrave, johnnydarkko, mrinalini9, Zarpele, Berdir, amateescu, hchonov, amitaibu, larowlan, heddn, RoySegall, quietone: Entity autocomplete widget does not pass along entity to AJAX request
parent
08bde872c0
commit
91f59e7033
|
@ -173,6 +173,12 @@ class EntityAutocomplete extends Textfield {
|
|||
// Store the selection settings in the key/value store and pass a hashed key
|
||||
// in the route parameters.
|
||||
$selection_settings = $element['#selection_settings'] ?? [];
|
||||
// Don't serialize the entity, it will be added explicitly afterwards.
|
||||
if (isset($selection_settings['entity']) && ($selection_settings['entity'] instanceof EntityInterface)) {
|
||||
$element['#autocomplete_query_parameters']['entity_type'] = $selection_settings['entity']->getEntityTypeId();
|
||||
$element['#autocomplete_query_parameters']['entity_id'] = $selection_settings['entity']->id();
|
||||
unset($selection_settings['entity']);
|
||||
}
|
||||
$data = serialize($selection_settings) . $element['#target_type'] . $element['#selection_handler'];
|
||||
$selection_settings_key = Crypt::hmacBase64($data, Settings::getHashSalt());
|
||||
|
||||
|
|
|
@ -103,6 +103,11 @@ class EntityReferenceAutocompleteWidget extends WidgetBase {
|
|||
'match_limit' => $this->getSetting('match_limit'),
|
||||
];
|
||||
|
||||
// Append the entity if it is already created.
|
||||
if (!$entity->isNew()) {
|
||||
$selection_settings['entity'] = $entity;
|
||||
}
|
||||
|
||||
$element += [
|
||||
'#type' => 'entity_autocomplete',
|
||||
'#target_type' => $this->getFieldSetting('target_type'),
|
||||
|
|
|
@ -157,7 +157,7 @@ abstract class FormElement extends RenderElement implements FormElementInterface
|
|||
*
|
||||
* This sets up autocomplete functionality for elements with an
|
||||
* #autocomplete_route_name property, using the #autocomplete_route_parameters
|
||||
* property if present.
|
||||
* and #autocomplete_query_parameters properties if present.
|
||||
*
|
||||
* For example, suppose your autocomplete route name is
|
||||
* 'mymodule.autocomplete' and its path is
|
||||
|
@ -176,6 +176,8 @@ abstract class FormElement extends RenderElement implements FormElementInterface
|
|||
* autocomplete JavaScript library.
|
||||
* - #autocomplete_route_parameters: The parameters to be used in
|
||||
* conjunction with the route name.
|
||||
* - #autocomplete_query_parameters: The parameters to be used in
|
||||
* query string
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param array $complete_form
|
||||
|
@ -190,7 +192,11 @@ abstract class FormElement extends RenderElement implements FormElementInterface
|
|||
|
||||
if (!empty($element['#autocomplete_route_name'])) {
|
||||
$parameters = $element['#autocomplete_route_parameters'] ?? [];
|
||||
$url = Url::fromRoute($element['#autocomplete_route_name'], $parameters)->toString(TRUE);
|
||||
$options = [];
|
||||
if (!empty($element['#autocomplete_query_parameters'])) {
|
||||
$options['query'] = $element['#autocomplete_query_parameters'];
|
||||
}
|
||||
$url = Url::fromRoute($element['#autocomplete_route_name'], $parameters, $options)->toString(TRUE);
|
||||
/** @var \Drupal\Core\Access\AccessManagerInterface $access_manager */
|
||||
$access_manager = \Drupal::service('access_manager');
|
||||
$access = $access_manager->checkNamedRoute($element['#autocomplete_route_name'], $parameters, \Drupal::currentUser(), TRUE);
|
||||
|
|
|
@ -99,6 +99,17 @@ class EntityAutocompleteController extends ControllerBase {
|
|||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$entity_type_id = $request->query->get('entity_type');
|
||||
if ($entity_type_id && $this->entityTypeManager()->hasDefinition($entity_type_id)) {
|
||||
$entity_id = $request->query->get('entity_id');
|
||||
if ($entity_id) {
|
||||
$entity = $this->entityTypeManager()->getStorage($entity_type_id)->load($entity_id);
|
||||
if ($entity->access('update')) {
|
||||
$selection_settings['entity'] = $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$matches = $this->matcher->getMatches($target_type, $selection_handler, $selection_settings, $typed_string);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Schema for the entity reference 'entity_test_all_except_host' selection
|
||||
# handler settings.
|
||||
entity_reference_selection.entity_test_all_except_host:
|
||||
type: entity_reference_selection.default
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\entity_reference_test\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
|
||||
|
||||
/**
|
||||
* Allows access to all entities except for the host entity.
|
||||
*
|
||||
* @EntityReferenceSelection(
|
||||
* id = "entity_test_all_except_host",
|
||||
* label = @Translation("All except host entity."),
|
||||
* group = "entity_test_all_except_host"
|
||||
* )
|
||||
*/
|
||||
class AllExceptHostEntity extends DefaultSelection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityQuery($match, $match_operator);
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity */
|
||||
if ($entity = $this->configuration['entity']) {
|
||||
$target_type = $this->configuration['target_type'];
|
||||
$entity_type = $this->entityTypeManager->getDefinition($target_type);
|
||||
$query->condition($entity_type->getKey('id'), $entity->id(), '<>');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,8 @@ namespace Drupal\FunctionalJavascriptTests\EntityReference;
|
|||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
|
||||
|
@ -21,7 +23,12 @@ class EntityReferenceAutocompleteWidgetTest extends WebDriverTestBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'field_ui'];
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'entity_test',
|
||||
'entity_reference_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -149,6 +156,49 @@ class EntityReferenceAutocompleteWidgetTest extends WebDriverTestBase {
|
|||
$this->assertCount(2, $page->findAll('css', '.ui-autocomplete li'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the autocomplete widget knows about the entity its attached to.
|
||||
*
|
||||
* Ensures that the entity the autocomplete widget stores the entity it is
|
||||
* rendered on, and is available in the autocomplete results' AJAX request.
|
||||
*/
|
||||
public function testEntityReferenceAutocompleteWidgetAttachedEntity() {
|
||||
$user = $this->drupalCreateUser([
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$field_name = 'field_test';
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', $field_name, $field_name, 'entity_test', 'entity_test_all_except_host', ['target_bundles' => ['entity_test']]);
|
||||
$form_display = EntityFormDisplay::load('entity_test.entity_test.default');
|
||||
$form_display->setComponent($field_name, [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
'settings' => [
|
||||
'match_operator' => 'CONTAINS',
|
||||
],
|
||||
]);
|
||||
$form_display->save();
|
||||
|
||||
$host = EntityTest::create(['name' => 'dark green']);
|
||||
$host->save();
|
||||
EntityTest::create(['name' => 'dark blue'])->save();
|
||||
|
||||
$this->drupalGet($host->toUrl('edit-form'));
|
||||
|
||||
// Trigger the autocomplete.
|
||||
$page = $this->getSession()->getPage();
|
||||
$autocomplete_field = $page->findField($field_name . '[0][target_id]');
|
||||
$autocomplete_field->setValue('dark');
|
||||
$this->getSession()->getDriver()->keyDown($autocomplete_field->getXpath(), ' ');
|
||||
$this->assertSession()->waitOnAutocomplete();
|
||||
|
||||
// Check the autocomplete results.
|
||||
$results = $page->findAll('css', '.ui-autocomplete li');
|
||||
$this->assertCount(1, $results);
|
||||
$this->assertSession()->elementTextNotContains('css', '.ui-autocomplete li', 'dark green');
|
||||
$this->assertSession()->elementTextContains('css', '.ui-autocomplete li', 'dark blue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an autocomplete on a given field and waits for it to finish.
|
||||
*
|
||||
|
|
|
@ -18,6 +18,13 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|||
*/
|
||||
class EntityAutocompleteTest extends EntityKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_reference_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity type used in this test.
|
||||
*
|
||||
|
@ -71,6 +78,16 @@ class EntityAutocompleteTest extends EntityKernelTestBase {
|
|||
];
|
||||
$this->assertSame($target, reset($data), 'Autocomplete returns only the expected matching entity.');
|
||||
|
||||
// Pass the first entity to the request.
|
||||
// We should get empty results.
|
||||
// First we need to have permission to pass entity.
|
||||
$user = $this->drupalCreateUser([
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalSetCurrentUser($user);
|
||||
$data = $this->getAutocompleteResult($input, $entity_1->id());
|
||||
$this->assertSame([], $data, 'Autocomplete returns empty results as first entity is passed to autocomplete request.');
|
||||
|
||||
// Try to autocomplete an entity label that matches the second entity, and
|
||||
// the first entity is already typed in the autocomplete (tags) widget.
|
||||
$input = $entity_1->name->value . ' (1), 10/17';
|
||||
|
@ -99,6 +116,13 @@ class EntityAutocompleteTest extends EntityKernelTestBase {
|
|||
$this->assertSame(Html::escape($entity_2->name->value), $data[1]['label'], 'Autocomplete returned the second matching entity');
|
||||
$this->assertSame(Html::escape($entity_3->name->value), $data[2]['label'], 'Autocomplete returned the third matching entity');
|
||||
|
||||
// Pass the first entity to the request.
|
||||
// We should not get the first entity in the results.
|
||||
$data = $this->getAutocompleteResult($input, $entity_1->id());
|
||||
$this->assertCount(2, $data, 'Autocomplete returned only 2 entities');
|
||||
$this->assertSame(Html::escape($entity_2->name->value), $data[0]['label'], 'Autocomplete returned the second matching entity');
|
||||
$this->assertSame(Html::escape($entity_3->name->value), $data[1]['label'], 'Autocomplete returned the third matching entity');
|
||||
|
||||
// Strange input that is mangled by
|
||||
// \Drupal\Component\Utility\Tags::explode().
|
||||
$input = '"l!J>&Tw';
|
||||
|
@ -149,20 +173,28 @@ class EntityAutocompleteTest extends EntityKernelTestBase {
|
|||
*
|
||||
* @param string $input
|
||||
* The label of the entity to query by.
|
||||
* @param int $entity_id
|
||||
* The label of the entity to query by.
|
||||
*
|
||||
* @return mixed
|
||||
* The JSON value encoded in its appropriate PHP type.
|
||||
*/
|
||||
protected function getAutocompleteResult($input) {
|
||||
$request = Request::create('entity_reference_autocomplete/' . $this->entityType . '/default');
|
||||
protected function getAutocompleteResult($input, $entity_id = NULL) {
|
||||
// Use "entity_test_all_except_host" EntityReferenceSelection
|
||||
// to also test passing an entity to autocomplete requests.
|
||||
$request = Request::create('entity_reference_autocomplete/' . $this->entityType . '/entity_test_all_except_host');
|
||||
$request->query->set('q', $input);
|
||||
|
||||
$selection_settings = [];
|
||||
$selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $this->entityType . 'default', Settings::getHashSalt());
|
||||
if ($entity_id) {
|
||||
$request->query->set('entity_type', $this->entityType);
|
||||
$request->query->set('entity_id', $entity_id);
|
||||
}
|
||||
|
||||
$selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $this->entityType . 'entity_test_all_except_host', Settings::getHashSalt());
|
||||
\Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings);
|
||||
|
||||
$entity_reference_controller = EntityAutocompleteController::create($this->container);
|
||||
$result = $entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key)->getContent();
|
||||
$result = $entity_reference_controller->handleAutocomplete($request, $this->entityType, 'entity_test_all_except_host', $selection_settings_key)->getContent();
|
||||
|
||||
return Json::decode($result);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue