Commit 438f3839 authored by larowlan's avatar larowlan

Issue #2863188 by acbramley, Erik Frèrejean, Maouna, andypost, init90,...

Issue #2863188 by acbramley, Erik Frèrejean, Maouna, andypost, init90, larowlan, sathish.redcrackle, vasi1186, chr.fritsch, tstoeckler, phjou, jibran, hchonov, alexpott, catch: Hardcoded result size limit in the entity reference autocomplete widget
parent c1346b67
......@@ -234,6 +234,9 @@ field.widget.settings.entity_reference_autocomplete_tags:
match_operator:
type: string
label: 'Autocomplete matching'
match_limit:
type: integer
label: 'Maximum number of autocomplete suggestions.'
size:
type: integer
label: 'Size of textfield'
......@@ -248,6 +251,9 @@ field.widget.settings.entity_reference_autocomplete:
match_operator:
type: string
label: 'Autocomplete matching'
match_limit:
type: integer
label: 'Maximum number of autocomplete suggestions.'
size:
type: integer
label: 'Size of textfield'
......
......@@ -61,7 +61,8 @@ public function getMatches($target_type, $selection_handler, $selection_settings
if (isset($string)) {
// Get an array of matching entities.
$match_operator = !empty($selection_settings['match_operator']) ? $selection_settings['match_operator'] : 'CONTAINS';
$entity_labels = $handler->getReferenceableEntities($string, $match_operator, 10);
$match_limit = isset($selection_settings['match_limit']) ? (int) $selection_settings['match_limit'] : 10;
$entity_labels = $handler->getReferenceableEntities($string, $match_operator, $match_limit);
// Loop through the entities and convert them into autocomplete output.
foreach ($entity_labels as $values) {
......
......@@ -28,6 +28,7 @@ class EntityReferenceAutocompleteWidget extends WidgetBase {
public static function defaultSettings() {
return [
'match_operator' => 'CONTAINS',
'match_limit' => 10,
'size' => 60,
'placeholder' => '',
] + parent::defaultSettings();
......@@ -44,6 +45,13 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
'#options' => $this->getMatchOperatorOptions(),
'#description' => t('Select the method used to collect autocomplete suggestions. Note that <em>Contains</em> can cause performance issues on sites with thousands of entities.'),
];
$element['match_limit'] = [
'#type' => 'number',
'#title' => $this->t('Number of results'),
'#default_value' => $this->getSetting('match_limit'),
'#min' => 0,
'#description' => $this->t('The number of suggestions that will be listed. Use <em>0</em> to remove the limit.'),
];
$element['size'] = [
'#type' => 'number',
'#title' => t('Size of textfield'),
......@@ -68,6 +76,8 @@ public function settingsSummary() {
$operators = $this->getMatchOperatorOptions();
$summary[] = t('Autocomplete matching: @match_operator', ['@match_operator' => $operators[$this->getSetting('match_operator')]]);
$size = $this->getSetting('match_limit') ?: $this->t('unlimited');
$summary[] = $this->t('Autocomplete suggestion list size: @size', ['@size' => $size]);
$summary[] = t('Textfield size: @size', ['@size' => $this->getSetting('size')]);
$placeholder = $this->getSetting('placeholder');
if (!empty($placeholder)) {
......@@ -88,7 +98,10 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
$referenced_entities = $items->referencedEntities();
// Append the match operation to the selection settings.
$selection_settings = $this->getFieldSetting('handler_settings') + ['match_operator' => $this->getSetting('match_operator')];
$selection_settings = $this->getFieldSetting('handler_settings') + [
'match_operator' => $this->getSetting('match_operator'),
'match_limit' => $this->getSetting('match_limit'),
];
$element += [
'#type' => 'entity_autocomplete',
......
......@@ -62,6 +62,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......
......@@ -77,6 +77,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......
......@@ -139,6 +139,7 @@ protected function getExpectedDocument() {
'weight' => 5,
'settings' => [
'match_operator' => 'CONTAINS',
'match_limit' => 10,
'size' => 60,
'placeholder' => '',
],
......
......@@ -51,6 +51,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -62,6 +62,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -36,6 +36,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -53,6 +53,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -36,6 +36,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -95,6 +95,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -25,6 +25,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......
......@@ -15,6 +15,8 @@
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Extension\Dependency;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
......@@ -1466,3 +1468,32 @@ function system_modules_uninstalled($modules) {
}
}
}
/**
* Implements hook_ENTITY_TYPE_presave() for entity_form_display entities.
*
* Provides a BC layer for modules providing old configurations.
*
* @todo Remove this hook in Drupal 9.0.x https://www.drupal.org/project/drupal/issues/3086388
*/
function system_entity_form_display_presave(EntityFormDisplayInterface $display) {
/** @var \Drupal\Core\Field\WidgetPluginManager $field_widget_manager */
$field_widget_manager = \Drupal::service('plugin.manager.field.widget');
foreach ($display->getComponents() as $field_name => $component) {
if (empty($component['type'])) {
continue;
}
$plugin_definition = $field_widget_manager->getDefinition($component['type'], FALSE);
if (!is_a($plugin_definition['class'], EntityReferenceAutocompleteWidget::class, TRUE)) {
continue;
}
if (!isset($component['settings']['match_limit'])) {
@trigger_error(sprintf('Any entity_reference_autocomplete component of an entity_form_display must have a match_limit setting. The %s field on the %s form display is missing it. This BC layer will be removed before 9.0.0. See https://www.drupal.org/node/2863188', $field_name, $display->id()), E_USER_DEPRECATED);
$component['settings']['match_limit'] = 10;
$display->setComponent($field_name, $component);
}
}
}
......@@ -10,6 +10,7 @@
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget;
/**
* Re-save all configuration entities to recalculate dependencies.
......@@ -213,3 +214,29 @@ function system_post_update_clear_menu_cache() {
function system_post_update_layout_plugin_schema_change() {
// Empty post-update hook.
}
/**
* Populate the new 'match_limit' setting for the ER autocomplete widget.
*/
function system_post_update_entity_reference_autocomplete_match_limit(&$sandbox = NULL) {
$config_entity_updater = \Drupal::classResolver(ConfigEntityUpdater::class);
/** @var \Drupal\Core\Field\WidgetPluginManager $field_widget_manager */
$field_widget_manager = \Drupal::service('plugin.manager.field.widget');
$callback = function (EntityDisplayInterface $display) use ($field_widget_manager) {
foreach ($display->getComponents() as $field_name => $component) {
if (empty($component['type'])) {
continue;
}
$plugin_definition = $field_widget_manager->getDefinition($component['type'], FALSE);
if (is_a($plugin_definition['class'], EntityReferenceAutocompleteWidget::class, TRUE)) {
return TRUE;
}
}
return FALSE;
};
$config_entity_updater->update($sandbox, 'entity_form_display', $callback);
}
<?php
namespace Drupal\Tests\system\Functional\Update;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests that the match_limit setting is added to entity_reference_autocomplete.
*
* @see system_post_update_entity_reference_autocomplete_match_limit()
*
* @group legacy
*/
class EntityReferenceAutocompleteWidgetMatchLimitUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
];
}
/**
* Tests that the match_limit setting is added to the config.
*
* @expectedDeprecation Any entity_reference_autocomplete component of an entity_form_display must have a match_limit setting. The field_tags field on the node.article.default form display is missing it. This BC layer will be removed before 9.0.0. See https://www.drupal.org/node/2863188
* @expectedDeprecation Any entity_reference_autocomplete component of an entity_form_display must have a match_limit setting. The uid field on the node.article.default form display is missing it. This BC layer will be removed before 9.0.0. See https://www.drupal.org/node/2863188
*/
public function testViewsPostUpdateEntityLinkUrl() {
$display = EntityFormDisplay::load('node.article.default');
$this->assertArrayNotHasKey('match_limit', $display->getComponent('field_tags')['settings']);
$this->assertArrayNotHasKey('match_limit', $display->getComponent('uid')['settings']);
$this->runUpdates();
$display = EntityFormDisplay::load('node.article.default');
$this->assertEquals(10, $display->getComponent('field_tags')['settings']['match_limit']);
$this->assertEquals(10, $display->getComponent('uid')['settings']['match_limit']);
}
}
......@@ -50,6 +50,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -50,6 +50,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -52,6 +52,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -51,6 +51,7 @@ content:
weight: 4
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -50,6 +50,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -48,6 +48,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......@@ -110,6 +111,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......
......@@ -89,6 +89,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......
......@@ -79,6 +79,7 @@ content:
weight: 6
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......@@ -104,6 +105,7 @@ content:
weight: 7
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......@@ -167,6 +169,7 @@ content:
weight: 12
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -54,6 +54,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......@@ -98,6 +99,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......
......@@ -69,6 +69,7 @@ content:
region: content
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
third_party_settings: { }
......
......@@ -43,6 +43,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -36,6 +36,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -38,6 +38,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -44,6 +44,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -43,6 +43,7 @@ content:
weight: 5
settings:
match_operator: CONTAINS
match_limit: 10
size: 60
placeholder: ''
region: content
......
......@@ -21,7 +21,7 @@ class EntityReferenceAutocompleteWidgetTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node'];
public static $modules = ['node', 'field_ui'];
/**
* {@inheritdoc}
......@@ -92,18 +92,70 @@ public function testEntityReferenceAutocompleteWidget() {
->save();
$this->drupalGet('node/add/page');
$page = $this->getSession()->getPage();
$autocomplete_field = $assert_session->waitForElement('css', '[name="' . $field_name . '[0][target_id]"].ui-autocomplete-input');
$autocomplete_field->setValue('Test');
$this->getSession()->getDriver()->keyDown($autocomplete_field->getXpath(), ' ');
$assert_session->waitOnAutocomplete();
$this->doAutocomplete($field_name);
$results = $page->findAll('css', '.ui-autocomplete li');
$this->assertCount(1, $results);
$assert_session->pageTextContains('Test page');
$assert_session->pageTextNotContains('Page test');
// Change the size of the result set.
$display_repository->getFormDisplay('node', 'page')
->setComponent($field_name, [
'type' => 'entity_reference_autocomplete',
'settings' => [
'match_limit' => 1,
],
])
->save();
$this->drupalGet('node/add/page');
$this->doAutocomplete($field_name);
$results = $page->findAll('css', '.ui-autocomplete li');
$this->assertCount(1, $results);
$assert_session->pageTextContains('Test page');
$assert_session->pageTextNotContains('Page test');
// Change the size of the result set via the UI.
$this->drupalLogin($this->createUser([
'access content',
'administer content types',
'administer node fields',
'administer node form display',
'create page content',
]
));
$this->drupalGet('/admin/structure/types/manage/page/form-display');
$assert_session->pageTextContains('Autocomplete suggestion list size: 1');
// Click on the widget settings button to open the widget settings form.
$this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
$this->assertSession()->waitForElement('css', sprintf('[name="fields[%s][settings_edit_form][settings][match_limit]"]', $field_name));
$page->fillField('Number of results', 2);
$page->pressButton('Save');
$assert_session->pageTextContains('Your settings have been saved.');
$assert_session->pageTextContains('Autocomplete suggestion list size: 2');
$this->drupalGet('node/add/page');
$this->doAutocomplete($field_name);
$this->assertCount(2, $page->findAll('css', '.ui-autocomplete li'));
}
/**
* Executes an autocomplete on a given field and waits for it to finish.
*
* @param string $field_name
* The field name.
*/
protected function doAutocomplete($field_name) {
$autocomplete_field = $this->getSession()->getPage()->findField($field_name . '[0][target_id]');
$autocomplete_field->setValue('Test');
$this->getSession()->getDriver()->keyDown($autocomplete_field->getXpath(), ' ');
$this->assertSession()->waitOnAutocomplete();
}
}
......@@ -109,6 +109,7 @@ protected function getExpectedNormalizedEntity() {
'weight' => 5,
'settings' => [
'match_operator' => 'CONTAINS',
'match_limit' => 10,
'size' => 60,
'placeholder' => '',
],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment