diff --git a/src/Search/Comb/Comb.php b/src/Search/Comb/Comb.php index 7e2693f0e5e..48245adc861 100644 --- a/src/Search/Comb/Comb.php +++ b/src/Search/Comb/Comb.php @@ -36,6 +36,8 @@ class Comb 'partial_whole' => 2, 'partial_whole_start' => 2, 'whole' => 10, + 'phrase' => 0, + 'phrase_start' => 0, ]; /** @@ -530,6 +532,8 @@ private function searchOverData($params, $raw_query) 'partial_whole' => 0, 'partial_whole_start' => 0, 'whole' => 0, + 'phrase' => 0, + 'phrase_start' => 0, ]; $snippets = []; @@ -562,6 +566,9 @@ private function searchOverData($params, $raw_query) if ($result) { $matched = true; $found['partial_whole'] += $strength * $result; + if (! $chunk_is_word) { + $found['phrase'] += $strength * $result; + } if ($chunk_is_word) { $found['partial_word'] += $strength * $result; if (preg_match_all($regex['partial_anywhere'], $first_word)) { @@ -594,6 +601,9 @@ private function searchOverData($params, $raw_query) if ($result) { $matched = true; $found['partial_whole_start'] += $strength * $result; + if (! $chunk_is_word) { + $found['phrase_start'] += $strength * $result; + } $result = preg_match_all($regex['whole'], $property); if ($result) { diff --git a/tests/Search/CombTest.php b/tests/Search/CombTest.php index 4df7a7f01c4..0d33e526c63 100644 --- a/tests/Search/CombTest.php +++ b/tests/Search/CombTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; +use ReflectionClass; use Statamic\Search\Comb\Comb; use Statamic\Search\Comb\Exceptions\NoResultsFound; use Tests\TestCase; @@ -419,6 +420,64 @@ public function it_handles_stop_words_case_insensitive() $this->assertEquals(['One two three'], collect($results['data'] ?? [])->pluck('data.title')->all()); } + #[Test] + public function phrase_weights_default_to_zero_and_do_not_change_scores() + { + $weights = (new ReflectionClass(Comb::class))->getDefaultProperties()['match_weights']; + + $this->assertSame(0, $weights['phrase']); + $this->assertSame(0, $weights['phrase_start']); + + $documents = [ + ['title' => 'Two years in at Passive House Accelerator'], + ['title' => str_repeat('Passive House Accelerator ', 12)], + ]; + $term = 'Two years in at Passive House Accelerator'; + + $this->assertSame( + $this->combScores($documents, $term), + $this->combScores($documents, $term, ['phrase' => 0, 'phrase_start' => 0]) + ); + } + + #[Test] + public function a_title_containing_the_full_query_can_outrank_repeated_words() + { + $documents = [ + ['title' => 'Two years in at Passive House Accelerator'], + ['title' => str_repeat('Passive House Accelerator ', 12)], + ]; + $term = 'Two years in at Passive House Accelerator'; + + $baseline = $this->combScores($documents, $term); + $repeated = str_repeat('Passive House Accelerator ', 12); + + $this->assertGreaterThan( + $baseline['Two years in at Passive House Accelerator'], + $baseline[$repeated] + ); + + $boosted = $this->combScores($documents, $term, ['phrase' => 500]); + + $this->assertSame($baseline[$repeated], $boosted[$repeated]); + $this->assertGreaterThan($boosted[$repeated], $boosted['Two years in at Passive House Accelerator']); + } + + private function combScores(array $documents, string $term, array $matchWeights = []): array + { + $settings = ['score_threshold' => 0]; + + if ($matchWeights !== []) { + $settings['match_weights'] = $matchWeights; + } + + $results = (new Comb($documents, $settings))->lookUp($term); + + return collect($results['data'])->mapWithKeys(fn ($result) => [ + $result['data']['title'] => $result['score'], + ])->all(); + } + public static function searchesProvider() { return [