Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/Search/Comb/Comb.php
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ private function searchOverData($params, $raw_query)
*/
private function removeDisallowedMatches($params)
{
$disallowed = '#'.implode('|', $params['disallowed']).'#iu';
$disallowed = $this->pattern($params['disallowed']);
$new_data = [];

// this only applies to boolean mode
Expand Down Expand Up @@ -740,7 +740,7 @@ private function removeDisallowedMatches($params)

// check for required, one word at a time so each is a single linear scan
foreach ($params['required'] as $word) {
if (! preg_match('#'.$word.'#iu', $record)) {
if (! preg_match($this->pattern([$word]), $record)) {
// a required word is missing, we don't want this
throw new CombException('');
}
Expand All @@ -755,6 +755,11 @@ private function removeDisallowedMatches($params)
return $new_data;
}

private function pattern(array $words): string
{
return '#'.implode('|', array_map(fn (string $word) => preg_quote($word, '#'), $words)).'#iu';
}

/**
* Removes properties that are on the exclusion list.
*
Expand Down
32 changes: 32 additions & 0 deletions tests/Search/CombTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,38 @@ public function it_filters_out_results_with_disallowed_words_where_results_are_a
$this->assertEquals(['Chicken & Sweetcorn Soup'], collect($results['data'] ?? [])->pluck('data.title')->all());
}

#[Test]
#[DataProvider('regexMetacharactersProvider')]
public function it_treats_regex_metacharacters_in_boolean_words_literally($term, $expected)
{
$comb = new Comb([
['title' => 'node.js guide'],
['title' => 'nodeXjs guide'],
['title' => 'c++ guide'],
['title' => 'c guide'],
]);

try {
$results = $comb->lookUp($term);
} catch (NoResultsFound $e) {
$results = [];
}

$this->assertEquals($expected, collect($results['data'] ?? [])->pluck('data.title')->all());
}

public static function regexMetacharactersProvider()
{
return [
'required word with a dot' => ['+node.js', ['node.js guide']],
'disallowed word with a dot' => ['guide -node.js', ['nodeXjs guide', 'c++ guide', 'c guide']],
'required word with plus signs' => ['+c++', ['c++ guide']],
'disallowed word with plus signs' => ['guide -c++', ['node.js guide', 'nodeXjs guide', 'c guide']],
'required word with a trailing backslash' => ['+foo\\', []],
'disallowed word with a trailing backslash' => ['guide -foo\\', ['node.js guide', 'nodeXjs guide', 'c++ guide', 'c guide']],
];
}

#[Test]
#[DataProvider('requiredWordsProvider')]
public function it_filters_out_results_without_required_words($term, $expected)
Expand Down
Loading