Skip to content
Snippets Groups Projects
Verified Commit 91f59e70 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #2826826 by vasike, dpi, raman.b, rpayanm, jibran, gpap, mpolishchuck,...

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 08bde872
No related branches found
No related tags found
45 merge requests!12227Issue #3181946 by jonmcl, mglaman,!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!4594Applying patch for Views Global Text area field to allow extra HTML tags. As video, source and iframe tag is not rendering. Due to which Media embedded video and remote-video not rendering in Views Global Text area field.,!3878Removed unused condition head title for views,!38582585169-10.1.x,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3546refactored dialog.pcss file,!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3502Issue #3335308: Confusing behavior with FormState::setFormState and FormState::setMethod,!3478Issue #3337882: Deleted menus are not removed from content type config,!3452Issue #3332701: Refactor Claro's tablesort-indicator stylesheet,!3451Issue #2410579: Allows setting the current language programmatically.,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3147Issue #3328457: Replace most substr($a, $i) where $i is negative with str_ends_with(),!3146Issue #3328456: Replace substr($a, 0, $i) with str_starts_with(),!3133core/modules/system/css/components/hidden.module.css,!31312878513-10.1.x,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2614Issue #2981326: Replace non-test usages of \Drupal::logger() with IoC injection,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!844Resolve #3036010 "Updaters",!673Issue #3214208: FinishResponseSubscriber could create duplicate headers,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!213Issue #2906496: Give Media a menu item under Content,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
......@@ -173,6 +173,12 @@ public static function processEntityAutocomplete(array &$element, FormStateInter
// 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 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
'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 @@ public static function validatePattern(&$element, FormStateInterface $form_state
*
* 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 @@ public static function validatePattern(&$element, FormStateInterface $form_state
* 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 @@ public static function processAutocomplete(&$element, FormStateInterface $form_s
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 @@ public function handleAutocomplete(Request $request, $target_type, $selection_ha
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);
}
......
# 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
<?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 @@
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 @@ public function testEntityReferenceAutocompleteWidget() {
$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 @@
*/
class EntityAutocompleteTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_reference_test',
];
/**
* The entity type used in this test.
*
......@@ -71,6 +78,16 @@ public function testEntityReferenceAutocompletion() {
];
$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 @@ public function testEntityReferenceAutocompletion() {
$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 @@ public function testSelectionSettingsHandling() {
*
* @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);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment