Skip to content

Commit a9914bb

Browse files
Merge pull request #11655 from creative-commoners/pulls/5.3/search-fallback
FIX Use search context when no database field for label
2 parents 436c92c + ef6c4cf commit a9914bb

2 files changed

Lines changed: 69 additions & 4 deletions

File tree

src/Forms/SearchableDropdownTrait.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use SilverStripe\ORM\DataObjectInterface;
1515
use SilverStripe\ORM\Relation;
1616
use SilverStripe\ORM\SS_List;
17-
use SilverStripe\ORM\FieldType\DBHTMLText;
1817
use SilverStripe\View\ArrayData;
1918
use SilverStripe\ORM\Search\SearchContext;
2019
use SilverStripe\Security\SecurityToken;
@@ -225,6 +224,9 @@ public function setUseDynamicPlaceholder(bool $useDynamicPlaceholder): static
225224

226225
/**
227226
* Get whether to use a search context instead searching on labelField
227+
*
228+
* If the label field doesn't map to a database field and instead uses a getter e.g. 'MyField' calls
229+
* a 'getMyField()' method, then search context will be used regardless of what is returned here
228230
*/
229231
public function getUseSearchContext(): bool
230232
{
@@ -233,6 +235,9 @@ public function getUseSearchContext(): bool
233235

234236
/**
235237
* Set whether to use a search context instead searching on labelField
238+
*
239+
* If the label field doesn't map to a database field and instead uses a getter e.g. 'MyField' calls
240+
* a 'getMyField()' method, then search context will be used regardless of what is set here
236241
*/
237242
public function setUseSearchContext(bool $useSearchContext): static
238243
{
@@ -285,8 +290,10 @@ public function setSource($source): static
285290

286291
/**
287292
* Get the field to use for the label of the option
293+
* This field is also used for searching if that field exists in the database and search context is not being used
288294
*
289-
* The default value of 'Title' will map to DataObject::getTitle() if a Title DB field does not exist
295+
* If the label field does not map to a database field then a getter will be used if it exists
296+
* e.g. the default value of 'Title' will map to DataObject::getTitle() if a Title DB field does not exist
290297
*/
291298
public function getLabelField(): string
292299
{
@@ -295,6 +302,7 @@ public function getLabelField(): string
295302

296303
/**
297304
* Set the field to use for the label of the option
305+
* This field is also used for searching if that field exists in the database and search context is not being used
298306
*/
299307
public function setLabelField(string $labelField): static
300308
{
@@ -497,9 +505,14 @@ private function getOptionsForSearchRequest(string $term): array
497505
$labelField = $this->getLabelField();
498506
/** @var DataObject $obj */
499507
$obj = $dataClass::create();
500-
$key = $this->getUseSearchContext() ? $obj->getGeneralSearchFieldName() : $this->getLabelField();
501-
$searchParams = [$key => $term];
502508
$hasLabelField = (bool) $obj->getSchema()->fieldSpec($dataClass, $labelField);
509+
$key = $labelField;
510+
// Label field still works for making labels if there's a getter method
511+
// on the object e.g. 'Title' ... getTitle(), however we can't use it for database queries
512+
if (!$hasLabelField || $this->getUseSearchContext()) {
513+
$key = $obj->getGeneralSearchFieldName();
514+
}
515+
$searchParams = [$key => $term];
503516
$sort = $hasLabelField ? $labelField : null;
504517
$limit = $this->getLazyLoadLimit();
505518
$newList = $this->getSearchContext()->getQuery($searchParams, $sort, $limit);

tests/php/Forms/SearchableDropdownTraitTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use SilverStripe\Forms\HiddenField;
1616
use stdClass;
1717
use SilverStripe\Forms\Form;
18+
use SilverStripe\Security\Member;
1819

1920
class SearchableDropdownTraitTest extends SapphireTest
2021
{
@@ -261,4 +262,55 @@ public function testLazyLoadedDoesntCallGetSource(string $fieldClass, string $me
261262
$mockField->setForm(new Form());
262263
$mockField->$methodToCall();
263264
}
265+
266+
public static function provideSearchDataObjectWithMethodForLabelField(): array
267+
{
268+
return [
269+
'single' => [
270+
'term' => 'alex',
271+
'expected' => '[{"value":0,"label":"Aziel, Alex"}]',
272+
],
273+
'multi' => [
274+
'term' => 'z',
275+
'expected' => '[{"value":0,"label":"Aziel, Alex"},{"value":0,"label":"Cazton, Cindy"}]',
276+
],
277+
'email' => [
278+
'term' => '@example.com',
279+
'expected' => implode('', [
280+
'[{"value":0,"label":"Aziel, Alex"},{"value":0,"label":"Brown, Bob"}',
281+
',{"value":0,"label":"Cazton, Cindy"}]',
282+
]),
283+
],
284+
];
285+
}
286+
287+
/**
288+
* Member has a method getTitle() that returns "Surname, FirstName"
289+
* The default label field on the field is "Title", which doesn't exist on the Member database table
290+
* It should fall back to using search context in this scenario
291+
*
292+
* @dataProvider provideSearchDataObjectWithMethodForLabelField
293+
*/
294+
public function testSearchDataObjectWithMethodForLabelField(string $term, string $expected): void
295+
{
296+
$data = [
297+
['FirstName' => 'Alex', 'Surname' => 'Aziel', 'Email' => 'aaron.aziel@example.com'],
298+
['FirstName' => 'Bob', 'Surname' => 'Brown', 'Email' => 'bob.brown@example.com'],
299+
['FirstName' => 'Cindy', 'Surname' => 'Cazton', 'Email' => 'cindy.cazton@example.com'],
300+
];
301+
foreach ($data as $row) {
302+
$member = new Member($row);
303+
$member->write();
304+
}
305+
// 4 members because of the 3 members above and the default admin member
306+
$this->assertSame(4, Member::get()->count());
307+
$field = new SearchableDropdownField('MyField', 'MyField', Member::get());
308+
$field->setLabelField('Title');
309+
$request = new HTTPRequest('GET', 'someurl', ['term' => $term]);
310+
$request->addHeader('X-SecurityID', SecurityToken::getSecurityID());
311+
$body = $field->search($request)->getBody();
312+
// Replace the member ID value with 0 to make the test more stable
313+
$body = preg_replace('/"value":[0-9]+/', '"value":0', $body);
314+
$this->assertSame($expected, $body);
315+
}
264316
}

0 commit comments

Comments
 (0)