Skip to content

Commit 7cf904f

Browse files
committed
Merge branch '2.10.x' into 2.11.x
2 parents 804fc79 + 46650c4 commit 7cf904f

File tree

18 files changed

+380
-12
lines changed

18 files changed

+380
-12
lines changed

src/module-elasticsuite-catalog/Block/Plugin/Adminhtml/Product/Attribute/Edit/Tab/FrontPlugin.php

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@
2323
use Smile\ElasticsuiteCatalog\Model\Attribute\Source\FilterSortOrder;
2424
use Magento\Framework\Data\Form\Element\Fieldset;
2525
use Magento\Catalog\Api\Data\EavAttributeInterface;
26+
use Smile\ElasticsuiteCatalog\Model\Attribute\Source\TextScoringAlgorithm;
2627
use Smile\ElasticsuiteCore\Api\Index\Mapping\FieldInterface;
2728

2829
/**
29-
* Plugin that happend custom fields dedicated to search configuration
30+
* Plugin that append custom fields dedicated to search configuration
3031
*
3132
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
3233
*
@@ -74,6 +75,11 @@ class FrontPlugin
7475
*/
7576
private $filterBooleanLogic;
7677

78+
/**
79+
* @var TextScoringAlgorithm
80+
*/
81+
private $textScoringAlgorithm;
82+
7783
/**
7884
* @var UrlInterface
7985
*/
@@ -82,27 +88,30 @@ class FrontPlugin
8288
/**
8389
* Class constructor
8490
*
85-
* @param Yesno $booleanSource The YesNo source.
86-
* @param Weight $weightSource Weight source.
87-
* @param Registry $coreRegistry Core registry.
88-
* @param FilterSortOrder $filterSortOrder Filter Sort Order.
89-
* @param FilterBooleanLogic $filterBooleanLogic Filter boolean logic source model.
90-
* @param UrlInterface $urlBuilder Url Builder.
91+
* @param Yesno $booleanSource The YesNo source.
92+
* @param Weight $weightSource Weight source.
93+
* @param Registry $coreRegistry Core registry.
94+
* @param FilterSortOrder $filterSortOrder Filter Sort Order.
95+
* @param FilterBooleanLogic $filterBooleanLogic Filter boolean logic source model.
96+
* @param TextScoringAlgorithm $textScoringAlgorithm Text scoring algorithm source model.
97+
* @param UrlInterface $urlBuilder Url Builder.
9198
*/
9299
public function __construct(
93100
Yesno $booleanSource,
94101
Weight $weightSource,
95102
Registry $coreRegistry,
96103
FilterSortOrder $filterSortOrder,
97104
FilterBooleanLogic $filterBooleanLogic,
105+
TextScoringAlgorithm $textScoringAlgorithm,
98106
UrlInterface $urlBuilder
99107
) {
100108
$this->weightSource = $weightSource;
101109
$this->booleanSource = $booleanSource;
102110
$this->coreRegistry = $coreRegistry;
103111
$this->filterSortOrder = $filterSortOrder;
104-
$this->filterBooleanLogic = $filterBooleanLogic;
105-
$this->urlBuilder = $urlBuilder;
112+
$this->filterBooleanLogic = $filterBooleanLogic;
113+
$this->textScoringAlgorithm = $textScoringAlgorithm;
114+
$this->urlBuilder = $urlBuilder;
106115
}
107116

108117
/**
@@ -153,6 +162,12 @@ public function afterSetForm(Front $subject, Front $result, Form $form)
153162
$this->addDisableNormsField($advancedFieldset);
154163
}
155164

165+
if (in_array($this->getAttribute()->getBackendType(), ['varchar', 'text'])
166+
|| in_array($this->getAttribute()->getFrontendInput(), ['select', 'multiselect'])
167+
) {
168+
$this->addScoringAlgorithmField($advancedFieldset);
169+
}
170+
156171
$this->appendFieldsDependency($subject);
157172

158173
return $result;
@@ -635,6 +650,57 @@ private function addDisableNormsField(Fieldset $fieldset)
635650
return $this;
636651
}
637652

653+
/**
654+
* Add field allowing to configure a non-default similarity/text scoring model.
655+
*
656+
* @param Fieldset $fieldset Target fieldset
657+
*
658+
* @return FrontPlugin
659+
*/
660+
private function addScoringAlgorithmField(Fieldset $fieldset)
661+
{
662+
$link = sprintf(
663+
'<a href="%s" target="_blank">%s</a>',
664+
$this->urlBuilder->getUrl(
665+
'adminhtml/system_config/edit',
666+
[
667+
'section' => 'smile_elasticsuite_catalogsearch_settings',
668+
'_fragment' => 'smile_elasticsuite_catalogsearch_settings_catalogsearch-link',
669+
]
670+
),
671+
implode(' &gt; ', [__('ElasticSuite'), __('Catalog Search'), __('Catalog Search Configuration')])
672+
);
673+
674+
// @codingStandardsIgnoreStart
675+
$scoringAlgorithmNote = __(
676+
'The text scoring algorithm (or "similarity") determines how the relevance score is computed for a field during search.'
677+
. '<br/> Selecting "Default" will let your server apply the default scoring algorithm which takes into account every occurence of a search term in a field.'
678+
. ' Eg: a product with "dress" 3 times in its description should be scored higher than a product with "dress" only 1 time.'
679+
. '<br/> You can opt for a "Boolean" scoring model that produces at most a score of 1 (before the "Search weight" is applied) for the whole field whatever the number of matches.'
680+
. ' Eg: the product with "dress" 3 times in its description would be scored the same as the product with "dress" only 1 time in its description.'
681+
)
682+
. '<br />'
683+
. __(
684+
'<strong>Warning</strong>: a searchable attribute data is always copied to some searchable "collector fields" whose behavior will not be impacted by the change you do here.'
685+
. '<br />So you might want to also look at the specific "<strong>Text scoring algorithm for collector fields</strong>" setting in the Stores Configuration section %1.',
686+
$link
687+
);
688+
// @codingStandardsIgnoreEnd
689+
$fieldset->addField(
690+
'scoring_algorithm',
691+
'select',
692+
[
693+
'name' => 'scoring_algorithm',
694+
'label' => __('Text scoring algorithm'),
695+
'values' => $this->textScoringAlgorithm->toOptionArray(),
696+
'note' => $scoringAlgorithmNote,
697+
],
698+
'norms_disabled'
699+
);
700+
701+
return $this;
702+
}
703+
638704
/**
639705
* Add field allowing to configure if a field can be used for span queries.
640706
*
@@ -709,13 +775,15 @@ private function appendFieldsDependency($subject)
709775
->addFieldMap('is_spannable', 'is_spannable')
710776
->addFieldMap('norms_disabled', 'norms_disabled')
711777
->addFieldMap('default_analyzer', 'default_analyzer')
778+
->addFieldMap('scoring_algorithm', 'scoring_algorithm')
712779
->addFieldMap('search_weight', 'search_weight')
713780
->addFieldDependence('is_displayed_in_autocomplete', 'is_filterable_in_search', '1')
714781
->addFieldDependence('is_used_in_spellcheck', 'is_searchable', '1')
715782
->addFieldDependence('is_spannable', 'is_searchable', '1')
716783
->addFieldDependence('norms_disabled', 'is_searchable', '1')
717784
->addFieldDependence('search_weight', 'is_searchable', '1')
718785
->addFieldDependence('default_analyzer', 'is_searchable', '1')
786+
->addFieldDependence('scoring_algorithm', 'is_searchable', '1')
719787
->addFieldDependence('sort_order_asc_missing', 'used_for_sort_by', '1')
720788
->addFieldDependence('sort_order_desc_missing', 'used_for_sort_by', '1')
721789
->addFieldDependence('is_display_rel_nofollow', 'is_filterable', '1');

src/module-elasticsuite-catalog/Helper/AbstractAttribute.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ public function getMappingFieldOptions(AttributeInterface $attribute)
155155
$options['default_search_analyzer'] = $attribute->getDefaultAnalyzer();
156156
}
157157

158+
if ($attribute->getScoringAlgorithm()) {
159+
$options['similarity'] = $attribute->getScoringAlgorithm();
160+
}
161+
158162
return $options;
159163
}
160164

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/**
3+
* DISCLAIMER
4+
*
5+
* Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer versions in the future.
6+
*
7+
* @category Smile
8+
* @package Smile\ElasticsuiteCatalog
9+
* @author Richard Bayet <richard.bayet@smile.fr>
10+
* @copyright 2025 Smile
11+
* @license Open Software License ("OSL") v. 3.0
12+
*/
13+
namespace Smile\ElasticsuiteCatalog\Model\Attribute\Source;
14+
15+
use Magento\Framework\Data\OptionSourceInterface;
16+
use Smile\ElasticsuiteCore\Api\Index\Mapping\FieldInterface;
17+
18+
/**
19+
* Source model for available text scoring models/similarities for searchable attributes.
20+
*
21+
* @category Smile
22+
* @package Smile\ElasticsuiteCatalog
23+
* @author Richard Bayet <richard.bayet@smile.fr>
24+
*/
25+
class TextScoringAlgorithm implements OptionSourceInterface
26+
{
27+
/**
28+
* Return array of boolean logic operator.
29+
*
30+
* @return array
31+
*/
32+
public function toOptionArray()
33+
{
34+
return [
35+
['label' => __('Default'), 'value' => FieldInterface::SIMILARITY_DEFAULT],
36+
['label' => __('Boolean'), 'value' => FieldInterface::SIMILARITY_BOOLEAN],
37+
];
38+
}
39+
}

src/module-elasticsuite-catalog/Plugin/Catalog/Eav/AttributePlugin.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
class AttributePlugin
3333
{
3434
/**
35+
* List of _mapping field properties_ for which we want to detect changes.
36+
* Sometimes the mapping field property name differs from the attribute configuration property name.
37+
* For instance attribute[search_analyzer] => field[default_search_analyzer]
38+
* or attribute[scoring_algorithm] => field[similarity].
39+
*
3540
* @var array
3641
*/
3742
private $updateMappingFields = [
@@ -43,9 +48,15 @@ class AttributePlugin
4348
'include_zero_false_values',
4449
'disable_norms',
4550
'default_search_analyzer',
51+
'similarity',
4652
];
4753

4854
/**
55+
* List of _attribute properties_ for which we went to detect changes.
56+
* Sometimes the attribute configuration property name differs from the mapping field property name.
57+
* For instance attribute[search_analyzer] => field[default_search_analyzer]
58+
* or attribute[scoring_algorithm] => field[similarity].
59+
*
4960
* @var string[]
5061
*/
5162
private $cleanCacheFields = [
@@ -59,6 +70,7 @@ class AttributePlugin
5970
'disable_norms',
6071
'is_spannable',
6172
'default_analyzer',
73+
'scoring_algorithm',
6274
];
6375

6476
/**
@@ -218,6 +230,14 @@ private function checkUpdateNeeded($subject)
218230
continue;
219231
}
220232

233+
if ($field === 'similarity') {
234+
$cleanCache = true;
235+
// Updating the similarity is not possible on a field with shingles.
236+
$updateMapping = false;
237+
$invalidateIndex = true;
238+
continue;
239+
}
240+
221241
// Value for option has changed. Cache needs to be cleared.
222242
$cleanCache = true;
223243
// If option is disabled, we do nothing. Data will remain until next full reindex.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
/**
3+
* DISCLAIMER
4+
*
5+
* Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer versions in the future.
6+
*
7+
* @category Smile
8+
* @package Smile\ElasticsuiteCatalog
9+
* @author Richard Bayet <richard.bayet@smile.fr>
10+
* @copyright 2025 Smile
11+
* @license Open Software License ("OSL") v. 3.0
12+
*/
13+
14+
namespace Smile\ElasticsuiteCatalog\Plugin\Index\Indices\Mapping;
15+
16+
use Magento\Framework\App\Config\ScopeConfigInterface;
17+
use Smile\ElasticsuiteCore\Api\Index\Mapping\FieldInterface;
18+
use Smile\ElasticsuiteCore\Index\Mapping;
19+
use Smile\ElasticsuiteCore\Api\Index\MappingInterface;
20+
21+
/**
22+
* Plugin that applies the configured text scoring algorithm/similarity on default/collector fields.
23+
*
24+
* @category Smile
25+
* @package Smile\ElasticsuiteCatalog
26+
* @author Richard Bayet <richard.bayet@smile.fr>
27+
*/
28+
class DefaultFieldsSimilarityPlugin
29+
{
30+
/**
31+
* @var string
32+
*/
33+
// phpcs:ignore Generic.Files.LineLength
34+
const COLLECTOR_FIELDS_SIMILARITY_XML_PATH = 'smile_elasticsuite_catalogsearch_settings/catalogsearch/collector_fields_scoring_algorithm';
35+
36+
/**
37+
* @var ScopeConfigInterface
38+
*/
39+
private $scopeConfig;
40+
41+
/**
42+
* Constructor.
43+
*
44+
* @param ScopeConfigInterface $scopeConfig Scope configuration.
45+
*/
46+
public function __construct(ScopeConfigInterface $scopeConfig)
47+
{
48+
$this->scopeConfig = $scopeConfig;
49+
}
50+
51+
/**
52+
* After plugin - iterates over the default/collector fields and fixes their similarity if need be.
53+
*
54+
* @param Mapping $subject Index Mapping
55+
* @param array $result Mapping properties
56+
*
57+
* @return array
58+
*
59+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
60+
*/
61+
public function afterGetProperties(Mapping $subject, array $result): array
62+
{
63+
$similarity = $this->getConfiguredSimilarity();
64+
if (FieldInterface::SIMILARITY_DEFAULT !== $similarity) {
65+
$analyzers = $this->getDefaultSearchFieldsAnalyzers();
66+
foreach ($this->getDefaultSearchFields() as $defaultSearchField) {
67+
if (array_key_exists($defaultSearchField, $result)) {
68+
$result[$defaultSearchField]['similarity'] = $similarity;
69+
foreach ($analyzers as $analyzer) {
70+
if (array_key_exists('fields', $result[$defaultSearchField])
71+
&& array_key_exists($analyzer, $result[$defaultSearchField]['fields'])
72+
) {
73+
$result[$defaultSearchField]['fields'][$analyzer]['similarity'] = $similarity;
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
return $result;
81+
}
82+
83+
/**
84+
* Get the list of default/collector fields to look into.
85+
*
86+
* @return array
87+
*/
88+
private function getDefaultSearchFields()
89+
{
90+
return [
91+
MappingInterface::DEFAULT_SEARCH_FIELD,
92+
MappingInterface::DEFAULT_SPELLING_FIELD,
93+
MappingInterface::DEFAULT_AUTOCOMPLETE_FIELD,
94+
MappingInterface::DEFAULT_REFERENCE_FIELD,
95+
MappingInterface::DEFAULT_EDGE_NGRAM_FIELD,
96+
];
97+
}
98+
99+
/**
100+
* Get the list of possible text analyzers for default/collector fields to apply the configured similarity to.
101+
*
102+
* @return array
103+
*/
104+
private function getDefaultSearchFieldsAnalyzers()
105+
{
106+
return [
107+
FieldInterface::ANALYZER_STANDARD,
108+
FieldInterface::ANALYZER_WHITESPACE,
109+
FieldInterface::ANALYZER_SHINGLE,
110+
FieldInterface::ANALYZER_PHONETIC,
111+
FieldInterface::ANALYZER_REFERENCE,
112+
FieldInterface::ANALYZER_EDGE_NGRAM,
113+
];
114+
}
115+
116+
/**
117+
* Get the configured similarity for collector fields.
118+
*
119+
* @return string
120+
*/
121+
private function getConfiguredSimilarity()
122+
{
123+
return ($this->scopeConfig->getValue(self::COLLECTOR_FIELDS_SIMILARITY_XML_PATH) ?? FieldInterface::SIMILARITY_DEFAULT);
124+
}
125+
}

0 commit comments

Comments
 (0)