diff --git a/.github/workflows/99-no-response.yml b/.github/workflows/99-no-response.yml index 34fc0033d..a7565f276 100644 --- a/.github/workflows/99-no-response.yml +++ b/.github/workflows/99-no-response.yml @@ -4,7 +4,7 @@ on: issue_comment: types: [created] schedule: - - cron: '30 * * * *' + - cron: '0 1 * * *' jobs: noResponse: diff --git a/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products.php b/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products.php index ea6dfe10c..f7789094b 100644 --- a/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products.php +++ b/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products.php @@ -77,6 +77,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value 'current_page' => $searchResult->getCurrentPage(), 'total_pages' => $searchResult->getTotalPages(), 'is_spellchecked' => $searchResult->isSpellchecked(), + 'query_id' => $searchResult->getQueryId(), ], 'search_result' => $searchResult, 'layer_type' => $layerType, diff --git a/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products/Query/Search.php b/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products/Query/Search.php index 55a00ede3..c0c436a2a 100644 --- a/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products/Query/Search.php +++ b/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products/Query/Search.php @@ -114,6 +114,7 @@ public function getResult(array $args, ResolveInfo $info, ContextInterface $cont 'currentPage' => $searchCriteria->getCurrentPage(), 'totalPages' => $maxPages, 'isSpellchecked' => $searchResults->__toArray()['is_spellchecked'] ?? false, + 'queryId' => $searchResults->__toArray()['query_id'] ?? null, ]); } diff --git a/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products/SearchResult.php b/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products/SearchResult.php index 607919936..9e6417856 100644 --- a/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products/SearchResult.php +++ b/src/module-elasticsuite-catalog-graph-ql/Model/Resolver/Products/SearchResult.php @@ -44,4 +44,12 @@ public function isSpellchecked() { return (bool) $this->data['isSpellchecked'] ?? false; } + + /** + * @return ?int + */ + public function getQueryId() + { + return $this->data['queryId'] ?? null; + } } diff --git a/src/module-elasticsuite-catalog-graph-ql/etc/schema.graphqls b/src/module-elasticsuite-catalog-graph-ql/etc/schema.graphqls index 94cf9c3cf..ec0eea18c 100644 --- a/src/module-elasticsuite-catalog-graph-ql/etc/schema.graphqls +++ b/src/module-elasticsuite-catalog-graph-ql/etc/schema.graphqls @@ -20,4 +20,5 @@ type ViewMoreResult @doc(description: "The Products object is the top-level obje type SearchResultPageInfo { is_spellchecked: Boolean + query_id: Int } diff --git a/src/module-elasticsuite-catalog-optimizer/Ui/Component/Listing/Column/BoostWeight.php b/src/module-elasticsuite-catalog-optimizer/Ui/Component/Listing/Column/BoostWeight.php index a24fc5b9a..9c5c08483 100644 --- a/src/module-elasticsuite-catalog-optimizer/Ui/Component/Listing/Column/BoostWeight.php +++ b/src/module-elasticsuite-catalog-optimizer/Ui/Component/Listing/Column/BoostWeight.php @@ -62,8 +62,18 @@ public function prepareDataSource(array $dataSource) { if (isset($dataSource['data']['items'])) { foreach ($dataSource['data']['items'] as &$item) { + $value = ''; $config = $this->serializer->unserialize($item['config']); - $value = $config['constant_score_value'] ? ($config['constant_score_value'] . '%') : ''; + $type = $item['model'] ?? 'constant_score'; + if ($type === 'constant_score') { + $value = $config['constant_score_value'] ? ($config['constant_score_value'] . '%') : ''; + } elseif ($type === 'attribute_value') { + $factor = $config['scale_factor'] ?? ''; + $modifier = $config['scale_function'] ?? ''; + $field = $config['attribute_code'] ?? ''; + $value = sprintf("%s(%s * %s)", $modifier, $factor, $field); + } + $item[$this->getData('name')] = $value; } } diff --git a/src/module-elasticsuite-catalog/Plugin/Category/Toolbar/SortDirectionPerCategoryPlugin.php b/src/module-elasticsuite-catalog/Plugin/Category/Toolbar/SortDirectionPerCategoryPlugin.php index 28fd83d0c..b0924c12c 100644 --- a/src/module-elasticsuite-catalog/Plugin/Category/Toolbar/SortDirectionPerCategoryPlugin.php +++ b/src/module-elasticsuite-catalog/Plugin/Category/Toolbar/SortDirectionPerCategoryPlugin.php @@ -15,7 +15,11 @@ use Magento\Catalog\Block\Product\ProductList\Toolbar as ProductListToolbar; use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Request\Http; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; /** * Plugin which is modified the behavior of sorting arrows based on the custom sort direction attribute. @@ -26,6 +30,8 @@ */ class SortDirectionPerCategoryPlugin { + const XML_PATH_LIST_DEFAULT_SORT_DIRECTION_BY = 'catalog/frontend/default_sort_direction_by'; + /** * @var CategoryRepositoryInterface */ @@ -36,18 +42,38 @@ class SortDirectionPerCategoryPlugin */ private $request; + /** + * Scope configuration. + * + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * Store manager. + * + * @var StoreManagerInterface + */ + protected $storeManager; + /** * Toolbar constructor. * * @param CategoryRepositoryInterface $categoryRepository Category Repository. * @param Http $request Http request. + * @param ScopeConfigInterface $scopeConfig Scope configuration. + * @param StoreManagerInterface $storeManager Store manager. */ public function __construct( CategoryRepositoryInterface $categoryRepository, - Http $request + Http $request, + ScopeConfigInterface $scopeConfig, + StoreManagerInterface $storeManager ) { $this->categoryRepository = $categoryRepository; $this->request = $request; + $this->scopeConfig = $scopeConfig; + $this->storeManager = $storeManager; } /** @@ -57,6 +83,7 @@ public function __construct( * @param mixed $collection Collection. * * @return array + * @throws NoSuchEntityException */ public function beforeSetCollection(ProductListToolbar $subject, $collection) { @@ -69,30 +96,55 @@ public function beforeSetCollection(ProductListToolbar $subject, $collection) return [$collection]; } + /** + * Retrieve Product List Default Sort Direction By + * + * @return string|null + * @throws NoSuchEntityException + */ + private function getProductListDefaultSortDirectionBy() + { + // Get the current store ID. + $storeId = $this->storeManager->getStore()->getId(); + + // Fetch system configuration value for 'default_sort_direction_by' at the store level. + return $this->scopeConfig->getValue( + self::XML_PATH_LIST_DEFAULT_SORT_DIRECTION_BY, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + /** * Get the custom sort direction from the current category. * * @return string|null + * @throws NoSuchEntityException */ private function getCustomSortDirection() { $categoryId = $this->request->getParam('id'); if (!$categoryId) { - return null; // Return null if category ID is not available. + return $this->getProductListDefaultSortDirectionBy(); // Fallback to system config value if no category ID. } try { $category = $this->categoryRepository->get($categoryId); + + // Check if the category has a custom sort direction set. $customDirection = $category->getSortDirection(); + // If a custom sort direction exists for the category and is valid, return it. if ($customDirection && in_array($customDirection, ['asc', 'desc'])) { return $customDirection; } } catch (\Exception $e) { - return null; // Handle category not found or other exceptions. + // Handle exceptions (e.g., category not found) by falling back to the system config. + return $this->getProductListDefaultSortDirectionBy(); } - return null; + // If no custom sort direction for the category, return the default system config. + return $this->getProductListDefaultSortDirectionBy(); } } diff --git a/src/module-elasticsuite-catalog/i18n/nl_NL.csv b/src/module-elasticsuite-catalog/i18n/nl_NL.csv index c80780e18..2611f1dc3 100644 --- a/src/module-elasticsuite-catalog/i18n/nl_NL.csv +++ b/src/module-elasticsuite-catalog/i18n/nl_NL.csv @@ -4,7 +4,7 @@ "Save and Continue Edit","Opslaan en Doorgaan" "Save","Opslaan" "1 product","1 artikel" -"<%- count %> products","<%- aantal %> producten" +"<%- count %> products","<%- count %> producten" "No products in the selected range.","Geen producten in het geselecteerde bereik." "Search Configuration","Zoekconfiguratie" "Can be used only with catalog input type Text field, Dropdown, Multiple Select and Price.","Kan alleen gebruikt worden met invoertype catalogus, tekstveld, Dropdown, Multiple select en Prijs." diff --git a/src/module-elasticsuite-core/Api/Client/ClientConfigurationInterface.php b/src/module-elasticsuite-core/Api/Client/ClientConfigurationInterface.php index f182f43b2..d6da764c7 100644 --- a/src/module-elasticsuite-core/Api/Client/ClientConfigurationInterface.php +++ b/src/module-elasticsuite-core/Api/Client/ClientConfigurationInterface.php @@ -30,6 +30,13 @@ interface ClientConfigurationInterface */ public function getServerList(); + /** + * Indicates whether the body of requests in error should be logged or not. + * + * @return boolean + */ + public function isLoggingErrorRequest(); + /** * Indicates whether the debug node is enabled or not. * diff --git a/src/module-elasticsuite-core/Client/Client.php b/src/module-elasticsuite-core/Client/Client.php index 60518262a..cb28c87e4 100644 --- a/src/module-elasticsuite-core/Client/Client.php +++ b/src/module-elasticsuite-core/Client/Client.php @@ -15,6 +15,7 @@ namespace Smile\ElasticsuiteCore\Client; use Elasticsearch\Common\Exceptions\Missing404Exception; +use Psr\Log\LoggerInterface; use Smile\ElasticsuiteCore\Api\Client\ClientConfigurationInterface; use Smile\ElasticsuiteCore\Api\Client\ClientInterface; @@ -44,16 +45,26 @@ class Client implements ClientInterface */ private $clientBuilder; + /** + * @var LoggerInterface + */ + private $logger; + /** * Constructor. * * @param ClientConfigurationInterface $clientConfiguration Client configuration factory. * @param ClientBuilder $clientBuilder ES client builder. + * @param LoggerInterface $logger Logger. */ - public function __construct(ClientConfigurationInterface $clientConfiguration, ClientBuilder $clientBuilder) - { + public function __construct( + ClientConfigurationInterface $clientConfiguration, + ClientBuilder $clientBuilder, + LoggerInterface $logger + ) { $this->clientConfiguration = $clientConfiguration; $this->clientBuilder = $clientBuilder; + $this->logger = $logger; } /** @@ -201,10 +212,22 @@ public function bulk($bulkParams) /** * {@inheritDoc} + * @throws \Exception */ public function search($params) { - return $this->getEsClient()->search($params); + try { + $response = $this->getEsClient()->search($params); + } catch (\Exception $e) { + if ($this->clientConfiguration->isLoggingErrorRequest()) { + $requestInfo = json_encode($params, JSON_PRESERVE_ZERO_FRACTION + JSON_INVALID_UTF8_SUBSTITUTE); + $this->logger->error(sprintf("Search Request Failure [error] : %s", $e->getMessage())); + $this->logger->error(sprintf("Search Request Failure [request] : %s", $requestInfo)); + } + throw $e; + } + + return $response; } /** diff --git a/src/module-elasticsuite-core/Client/ClientConfiguration.php b/src/module-elasticsuite-core/Client/ClientConfiguration.php index 50c332df5..0ba2fabee 100644 --- a/src/module-elasticsuite-core/Client/ClientConfiguration.php +++ b/src/module-elasticsuite-core/Client/ClientConfiguration.php @@ -54,6 +54,14 @@ public function getServerList() return explode(',', $this->getElasticsearchClientConfigParam('servers') ?? ''); } + /** + * {@inheritDoc} + */ + public function isLoggingErrorRequest() + { + return (bool) $this->getElasticsearchClientConfigParam('enable_error_request_logging'); + } + /** * {@inheritdoc} */ diff --git a/src/module-elasticsuite-core/Model/Search.php b/src/module-elasticsuite-core/Model/Search.php index cee7a93de..ddb81fde2 100644 --- a/src/module-elasticsuite-core/Model/Search.php +++ b/src/module-elasticsuite-core/Model/Search.php @@ -14,6 +14,11 @@ namespace Smile\ElasticsuiteCore\Model; +use Magento\Framework\Search\SearchEngineInterface; +use Magento\Framework\Search\SearchResponseBuilder; +use Smile\ElasticsuiteCore\Api\Search\ContextInterface; +use Smile\ElasticsuiteCore\Model\Search\RequestBuilder; + /** * SearchInterface implementation using elasticsuite. * @@ -24,35 +29,43 @@ class Search implements \Magento\Search\Api\SearchInterface { /** - * @var \Smile\ElasticsuiteCore\Model\Search\RequestBuilder + * @var RequestBuilder */ private $searchRequestBuilder; /** - * @var \Magento\Framework\Search\SearchEngineInterface + * @var SearchEngineInterface */ private $searchEngine; /** - * @var \Magento\Framework\Search\SearchResponseBuilder + * @var SearchResponseBuilder */ private $searchResponseBuilder; + /** + * @var ContextInterface + */ + private $searchContext; + /** * Constructor. * - * @param \Magento\Framework\Search\SearchEngineInterface $searchEngine Search engine. - * @param \Smile\ElasticsuiteCore\Model\Search\RequestBuilder $searchRequestBuilder Search request builder. - * @param \Magento\Framework\Search\SearchResponseBuilder $searchResponseBuilder Search response builder. + * @param SearchEngineInterface $searchEngine Search engine. + * @param RequestBuilder $searchRequestBuilder Search request builder. + * @param SearchResponseBuilder $searchResponseBuilder Search response builder. + * @param ContextInterface $searchContext Search context. */ public function __construct( - \Magento\Framework\Search\SearchEngineInterface $searchEngine, - \Smile\ElasticsuiteCore\Model\Search\RequestBuilder $searchRequestBuilder, - \Magento\Framework\Search\SearchResponseBuilder $searchResponseBuilder + SearchEngineInterface $searchEngine, + RequestBuilder $searchRequestBuilder, + SearchResponseBuilder $searchResponseBuilder, + ContextInterface $searchContext ) { - $this->searchRequestBuilder = $searchRequestBuilder; - $this->searchEngine = $searchEngine; - $this->searchResponseBuilder = $searchResponseBuilder; + $this->searchRequestBuilder = $searchRequestBuilder; + $this->searchEngine = $searchEngine; + $this->searchResponseBuilder = $searchResponseBuilder; + $this->searchContext = $searchContext; } /** @@ -68,10 +81,13 @@ public function search(\Magento\Framework\Api\Search\SearchCriteriaInterface $se $searchResponse = $this->searchEngine->search($searchRequest); $searchResult = $this->searchResponseBuilder->build($searchResponse); + $query = $this->searchContext->getCurrentSearchQuery(); + $totalCount = $searchResponse->count(); $searchResult->setTotalCount($totalCount); $searchResult->setSearchCriteria($searchCriteria); $searchResult->setData('is_spellchecked', (bool) $searchRequest->isSpellchecked()); + $searchResult->setData('query_id', ($query && $query->getId()) ? (int) $query->getId() : null); return $searchResult; } diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Regexp.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Regexp.php new file mode 100644 index 000000000..82e4bebec --- /dev/null +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Request/Query/Builder/Regexp.php @@ -0,0 +1,56 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder; + +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\BuilderInterface; +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; + +/** + * Build an ES regexp query. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Richard BAYET + */ +class Regexp implements BuilderInterface +{ + /** + * @var string + */ + const DEFAULT_FLAGS = 'NONE'; + + /** + * {@inheritDoc} + */ + public function buildQuery(QueryInterface $query) + { + if ($query->getType() !== QueryInterface::TYPE_REGEXP) { + throw new \InvalidArgumentException("Query builder : invalid query type {$query->getType()}"); + } + + $searchQueryParams = [ + 'value' => $query->getValue(), + 'boost' => $query->getBoost(), + 'flags' => self::DEFAULT_FLAGS, + ]; + + $searchQuery = ['regexp' => [$query->getField() => $searchQueryParams]]; + + if ($query->getName()) { + $searchQuery['regexp']['_name'] = $query->getName(); + } + + return $searchQuery; + } +} diff --git a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Spellchecker.php b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Spellchecker.php index d324ab5e9..a284a9628 100644 --- a/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Spellchecker.php +++ b/src/module-elasticsuite-core/Search/Adapter/Elasticsuite/Spellchecker.php @@ -164,6 +164,7 @@ private function getTermVectors(RequestInterface $request) MappingInterface::DEFAULT_SPELLING_FIELD => $request->getQueryText(), ], ]; + $perFieldAnalyzer = []; if ($request->isUsingReference()) { $doc['fields'][] = MappingInterface::DEFAULT_REFERENCE_FIELD . "." . FieldInterface::ANALYZER_REFERENCE; @@ -172,9 +173,15 @@ private function getTermVectors(RequestInterface $request) if ($request->isUsingEdgeNgram()) { $doc['fields'][] = MappingInterface::DEFAULT_EDGE_NGRAM_FIELD . "." . FieldInterface::ANALYZER_EDGE_NGRAM; + $perFieldAnalyzer[MappingInterface::DEFAULT_EDGE_NGRAM_FIELD . "." . FieldInterface::ANALYZER_EDGE_NGRAM] + = FieldInterface::ANALYZER_STANDARD; $doc['doc'][MappingInterface::DEFAULT_EDGE_NGRAM_FIELD] = $request->getQueryText(); } + if (!empty($perFieldAnalyzer)) { + $doc['per_field_analyzer'] = $perFieldAnalyzer; + } + $docs = []; // Compute the mtermvector query on all indices. diff --git a/src/module-elasticsuite-core/Search/Request/Query/Regexp.php b/src/module-elasticsuite-core/Search/Request/Query/Regexp.php new file mode 100644 index 000000000..99c5974b2 --- /dev/null +++ b/src/module-elasticsuite-core/Search/Request/Query/Regexp.php @@ -0,0 +1,116 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Search\Request\Query; + +use Smile\ElasticsuiteCore\Search\Request\QueryInterface; + +/** + * ElasticSuite minimal regexp query implementation. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Richard BAYET + */ +class Regexp implements QueryInterface +{ + /** + * @var string + */ + private $name; + + /** + * @var integer + */ + private $boost; + + /** + * @var string + */ + private $value; + + /** + * @var string + */ + private $field; + + /** + * Constructor. + * + * @param string $value Query value ie the regular expression. + * @param string $field Query field. + * @param string $name Name of the query. + * @param integer $boost Query boost. + */ + public function __construct($value, $field, $name = null, $boost = QueryInterface::DEFAULT_BOOST_VALUE) + { + $this->name = $name; + $this->value = $value; + $this->field = $field; + $this->boost = $boost; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritDoc} + */ + public function setName($name): self + { + $this->name = $name; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function getBoost() + { + return $this->boost; + } + + /** + * {@inheritDoc} + */ + public function getType() + { + return QueryInterface::TYPE_REGEXP; + } + + /** + * Search value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Search field. + * + * @return string + */ + public function getField() + { + return $this->field; + } +} diff --git a/src/module-elasticsuite-core/Search/Request/QueryInterface.php b/src/module-elasticsuite-core/Search/Request/QueryInterface.php index df176bcf9..92c2ccd8a 100644 --- a/src/module-elasticsuite-core/Search/Request/QueryInterface.php +++ b/src/module-elasticsuite-core/Search/Request/QueryInterface.php @@ -38,6 +38,7 @@ interface QueryInterface extends \Magento\Framework\Search\Request\QueryInterfac const TYPE_MORELIKETHIS = 'moreLikeThisQuery'; const TYPE_MATCHPHRASEPREFIX = 'matchPhrasePrefixQuery'; const TYPE_PREFIX = 'prefixQuery'; + const TYPE_REGEXP = 'regexpQuery'; /** * Set the query name diff --git a/src/module-elasticsuite-core/Test/Unit/Model/SearchTest.php b/src/module-elasticsuite-core/Test/Unit/Model/SearchTest.php index fee90de05..4990e40b2 100644 --- a/src/module-elasticsuite-core/Test/Unit/Model/SearchTest.php +++ b/src/module-elasticsuite-core/Test/Unit/Model/SearchTest.php @@ -13,10 +13,11 @@ */ namespace Smile\ElasticsuiteCore\Test\Unit\Model; -use Smile\ElasticsuiteCore\Model\Search; +use Smile\ElasticsuiteCore\Api\Search\ContextInterface; /** * Search API unit testing. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * * @category Smile * @package Smile\ElasticsuiteCore @@ -39,8 +40,9 @@ public function testSearch($documents, $docCount) $searchEngine = $this->getSearchEngine($documents, $docCount); $searchRequestBuilder = $this->getSearchRequestBuilder(); $searchResponseBuilder = $this->getSearchResponseBuilder(); + $searchContext = $this->createMock(ContextInterface::class); - $searchApi = new \Smile\ElasticsuiteCore\Model\Search($searchEngine, $searchRequestBuilder, $searchResponseBuilder); + $searchApi = new \Smile\ElasticsuiteCore\Model\Search($searchEngine, $searchRequestBuilder, $searchResponseBuilder, $searchContext); $searchCriteria = $this->createMock(\Magento\Framework\Api\Search\SearchCriteriaInterface::class); $searchResponse = $searchApi->search($searchCriteria); diff --git a/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Query/Builder/RegexpTest.php b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Query/Builder/RegexpTest.php new file mode 100644 index 000000000..7b877f187 --- /dev/null +++ b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/Request/Query/Builder/RegexpTest.php @@ -0,0 +1,78 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteCore\Test\Unit\Search\Adapter\Elasticsuite\Request\Query\Builder; + +use Smile\ElasticsuiteCore\Search\Request\Query\Regexp as RegexpQuery; +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Regexp as RegexpQueryBuilder; + +/** + * Regexp search query test case. + * + * @category Smile + * @package Smile\ElasticsuiteCore + * @author Richard BAYET + */ +class RegexpTest extends AbstractSimpleQueryBuilderTest +{ + /** + * Test the builder with mandatory params only. + * + * @return void + */ + public function testAnonymousRegexpQueryBuilder() + { + $builder = $this->getQueryBuilder(); + + $regexpQuery = new RegexpQuery('value', 'field'); + $query = $builder->buildQuery($regexpQuery); + + $this->assertArrayHasKey('regexp', $query); + $this->assertArrayHasKey('field', $query['regexp']); + $this->assertArrayHasKey('value', $query['regexp']['field']); + $this->assertEquals('value', $query['regexp']['field']['value']); + + $this->assertArrayHasKey('boost', $query['regexp']['field']); + $this->assertEquals(RegexpQuery::DEFAULT_BOOST_VALUE, $query['regexp']['field']['boost']); + + $this->assertArrayHasKey('flags', $query['regexp']['field']); + $this->assertEquals($builder::DEFAULT_FLAGS, $query['regexp']['field']['flags']); + + $this->assertArrayNotHasKey('_name', $query['regexp']); + } + + /** + * Test the builder with mandatory + name params. + * + * @return void + */ + public function testNamedRegexpQueryBuilder() + { + $builder = $this->getQueryBuilder(); + + $regexpQuery = new RegexpQuery('value', 'field', 'queryName'); + $query = $builder->buildQuery($regexpQuery); + + $this->assertArrayHasKey('regexp', $query); + $this->assertArrayHasKey('_name', $query['regexp']); + $this->assertEquals('queryName', $query['regexp']['_name']); + } + + /** + * {@inheritDoc} + */ + protected function getQueryBuilder() + { + return new RegexpQueryBuilder(); + } +} diff --git a/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/SpellcheckerTest.php b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/SpellcheckerTest.php index a6c5e1939..ce5da6713 100644 --- a/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/SpellcheckerTest.php +++ b/src/module-elasticsuite-core/Test/Unit/Search/Adapter/Elasticsuite/SpellcheckerTest.php @@ -184,6 +184,10 @@ public function testEdgeNgramTermVectorsParams() MappingInterface::DEFAULT_SEARCH_FIELD . "." . FieldInterface::ANALYZER_WHITESPACE, MappingInterface::DEFAULT_EDGE_NGRAM_FIELD . "." . FieldInterface::ANALYZER_EDGE_NGRAM, ], + 'per_field_analyzer' => [ + MappingInterface::DEFAULT_EDGE_NGRAM_FIELD . "." . FieldInterface::ANALYZER_EDGE_NGRAM + => FieldInterface::ANALYZER_STANDARD, + ], 'doc' => [ MappingInterface::DEFAULT_SEARCH_FIELD => $queryText, MappingInterface::DEFAULT_SPELLING_FIELD => $queryText, @@ -243,6 +247,10 @@ public function testReferenceAndEdgeNgramTermVectorsParams() MappingInterface::DEFAULT_REFERENCE_FIELD . "." . FieldInterface::ANALYZER_REFERENCE, MappingInterface::DEFAULT_EDGE_NGRAM_FIELD . "." . FieldInterface::ANALYZER_EDGE_NGRAM, ], + 'per_field_analyzer' => [ + MappingInterface::DEFAULT_EDGE_NGRAM_FIELD . "." . FieldInterface::ANALYZER_EDGE_NGRAM + => FieldInterface::ANALYZER_STANDARD, + ], 'doc' => [ MappingInterface::DEFAULT_SEARCH_FIELD => $queryText, MappingInterface::DEFAULT_SPELLING_FIELD => $queryText, diff --git a/src/module-elasticsuite-core/etc/adminhtml/system.xml b/src/module-elasticsuite-core/etc/adminhtml/system.xml index 09221fe26..c34a94fa5 100644 --- a/src/module-elasticsuite-core/etc/adminhtml/system.xml +++ b/src/module-elasticsuite-core/etc/adminhtml/system.xml @@ -30,53 +30,58 @@ - + List of servers in [host]:[port] format separated by a comma (e.g. : "es-node1.fqdn:9200, es-node2.fqdn:9200") - + Select yes if you want to connect to your Elasticsearch server over HTTPS. Magento\Config\Model\Config\Source\Yesno - + Select no if you are using opensearch. Magento\Config\Model\Config\Source\Yesno - + Enable this option when your Elasticsearch server use basic HTTP authentication. Magento\Config\Model\Config\Source\Yesno - + Enable this option when you want to base64 encode the Authorization headers. (Open Distro requires this) Magento\Config\Model\Config\Source\Yesno - + 1 - + 1 - + + + + Magento\Config\Model\Config\Source\Yesno + + When enabled the module will produce logs through Magento logging system. Magento\Config\Model\Config\Source\Yesno - + In seconds. validate-number - + Maximum number of times to retry connection when there is a connection failure validate-number diff --git a/src/module-elasticsuite-core/etc/config.xml b/src/module-elasticsuite-core/etc/config.xml index 248f2bc1c..5e65c1d64 100644 --- a/src/module-elasticsuite-core/etc/config.xml +++ b/src/module-elasticsuite-core/etc/config.xml @@ -19,6 +19,7 @@ + 0 0 localhost:9200 1 diff --git a/src/module-elasticsuite-core/etc/di.xml b/src/module-elasticsuite-core/etc/di.xml index 4fbbe3f69..bf9fd4bfe 100644 --- a/src/module-elasticsuite-core/etc/di.xml +++ b/src/module-elasticsuite-core/etc/di.xml @@ -122,6 +122,7 @@ spanTermFactory spanWithinFactory prefixQueryFactory + regexpQueryFactory @@ -151,6 +152,7 @@ + @@ -180,6 +182,7 @@ Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Span\SpanTerm\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Span\SpanWithin\Proxy Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Prefix\Proxy + Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Request\Query\Builder\Regexp\Proxy diff --git a/src/module-elasticsuite-core/etc/elasticsuite_analysis.xml b/src/module-elasticsuite-core/etc/elasticsuite_analysis.xml index 235b34eb6..8e059509b 100644 --- a/src/module-elasticsuite-core/etc/elasticsuite_analysis.xml +++ b/src/module-elasticsuite-core/etc/elasticsuite_analysis.xml @@ -402,7 +402,7 @@ - + @@ -452,7 +452,7 @@ - + diff --git a/src/module-elasticsuite-core/i18n/de_DE.csv b/src/module-elasticsuite-core/i18n/de_DE.csv index 59c4faa4f..ca5231b5e 100644 --- a/src/module-elasticsuite-core/i18n/de_DE.csv +++ b/src/module-elasticsuite-core/i18n/de_DE.csv @@ -53,6 +53,8 @@ "Basic HTTP authentication password","Basic HTTP Authentifikation Passwort" "Enable Debug Mode","Debug Modus aktivieren" "When enabled the module will produce logs through Magento logging system.","Wenn aktiviert werden vom Modul Logs mit dem Magento Logging System geschrieben." +"Enable logging of request that produce errors","Enable logging of request that produce errors" +"This is an alternative to the debug mode for production environments. If enabled, even when the debug mode is disabled, the body of search requests that produce an error will be logged. If disabled, only the error message/exception message will be logged (legacy behavior). A log rotation system on the var/log/system.log file is advised if enabled.","This is an alternative to the debug mode for production environments. If enabled, even when the debug mode is disabled, the body of search requests that produce an error will be logged. If disabled, only the error message/exception message will be logged (legacy behavior). A log rotation system on the var/log/system.log file is advised if enabled." "Server Connection Timeout","Server Verbindung Timeout" "In seconds.","In Sekunden." "Indices Settings","Einstellungen der Indizes" diff --git a/src/module-elasticsuite-core/i18n/en_US.csv b/src/module-elasticsuite-core/i18n/en_US.csv index 3c35ff94d..514bf1f7c 100644 --- a/src/module-elasticsuite-core/i18n/en_US.csv +++ b/src/module-elasticsuite-core/i18n/en_US.csv @@ -53,6 +53,8 @@ General,General "Basic HTTP authentication password","Basic HTTP authentication password" "Enable Debug Mode","Enable Debug Mode" "When enabled the module will produce logs through Magento logging system.","When enabled the module will produce logs through Magento logging system." +"Enable logging of request that produce errors","Enable logging of request that produce errors" +"This is an alternative to the debug mode for production environments. If enabled, even when the debug mode is disabled, the body of search requests that produce an error will be logged. If disabled, only the error message/exception message will be logged (legacy behavior). A log rotation system on the var/log/system.log file is advised if enabled.","This is an alternative to the debug mode for production environments. If enabled, even when the debug mode is disabled, the body of search requests that produce an error will be logged. If disabled, only the error message/exception message will be logged (legacy behavior). A log rotation system on the var/log/system.log file is advised if enabled." "Server Connection Timeout","Server Connection Timeout" "In seconds.","In seconds." "Indices Settings","Indices Settings" diff --git a/src/module-elasticsuite-core/i18n/fr_FR.csv b/src/module-elasticsuite-core/i18n/fr_FR.csv index 8b2fe1b43..71547b973 100644 --- a/src/module-elasticsuite-core/i18n/fr_FR.csv +++ b/src/module-elasticsuite-core/i18n/fr_FR.csv @@ -53,6 +53,8 @@ General,Général "Basic HTTP authentication password","Mot de passe pour l'authentification HTTP" "Enable Debug Mode","Activer le mode debug" "When enabled the module will produce logs through Magento logging system.","Lorsqu'activé, le module va produire des logs via le système de logs Magento standard." +"Enable logging of request that produce errors","Journaliser les requêtes en erreur" +"This is an alternative to the debug mode for production environments. If enabled, even when the debug mode is disabled, the body of search requests that produce an error will be logged. If disabled, only the error message/exception message will be logged (legacy behavior). A log rotation system on the var/log/system.log file is advised if enabled.","Ceci est une alternative au mode debug pour les environnements de production. Lorsqu'activé, même si le mode debug est désactivé, le corps des requêtes en erreur sera loggé. Si désactivé, seul l'exception/le message d'erreur sera loggé (comportement historique). Un système de rotation des logs sur le fichier var/log/system.log est recommandé si activé." "Server Connection Timeout","Délai de connexion au serveur" "In seconds.","En secondes." "Indices Settings","Paramètres des index" diff --git a/src/module-elasticsuite-core/i18n/nl_NL.csv b/src/module-elasticsuite-core/i18n/nl_NL.csv index 6c1e33ea4..f1d970759 100644 --- a/src/module-elasticsuite-core/i18n/nl_NL.csv +++ b/src/module-elasticsuite-core/i18n/nl_NL.csv @@ -53,6 +53,8 @@ "Basic HTTP authentication password","Basis HTTP authenticatie wachtwoord" "Enable Debug Mode","Debug modus inschakelen" "When enabled the module will produce logs through Magento logging system.","Indien ingeschakeld zal de module logboeken produceren via Magento logging systeem." +"Enable logging of request that produce errors","Enable logging of request that produce errors" +"This is an alternative to the debug mode for production environments. If enabled, even when the debug mode is disabled, the body of search requests that produce an error will be logged. If disabled, only the error message/exception message will be logged (legacy behavior). A log rotation system on the var/log/system.log file is advised if enabled.","This is an alternative to the debug mode for production environments. If enabled, even when the debug mode is disabled, the body of search requests that produce an error will be logged. If disabled, only the error message/exception message will be logged (legacy behavior). A log rotation system on the var/log/system.log file is advised if enabled." "Server Connection Timeout","Time-out verbinding server" "In seconds.","In seconden." "Indices Settings","Indices instellingen" diff --git a/src/module-elasticsuite-indices/Model/Index/Collection.php b/src/module-elasticsuite-indices/Model/Index/Collection.php index a3e440776..088a7e3be 100644 --- a/src/module-elasticsuite-indices/Model/Index/Collection.php +++ b/src/module-elasticsuite-indices/Model/Index/Collection.php @@ -52,10 +52,18 @@ public function __construct( */ public function addFieldToFilter($field, $condition = null) { - if (in_array($field, ['index_alias', 'index_name'])) { + $type = 'include'; + if ($field === 'index_status') { + $type = 'exclude'; + } + if (in_array($field, ['index_alias', 'index_name', 'index_status'])) { if (is_array($condition)) { foreach ($condition as $value) { - $this->addFilter($field, preg_replace('/[^A-Za-z0-9\-_]/', '', $value->__toString())); + $this->addFilter( + $field, + preg_replace('/[^A-Za-z0-9\-_]/', '', (string) $value), + $type + ); } } } @@ -108,7 +116,13 @@ private function applyPostFilters(array $filters = [], array $indices = []) foreach ($filters as $filter) { $column = $filter->getField(); $value = $filter->getValue(); - if (strpos((string) $index->getData($column), $value) === false) { + $type = $filter->getType(); + if ($type === 'exclude') { + if (strpos((string) $index->getData($column), $value) !== false) { + $keep = false; + break; + } + } elseif (strpos((string) $index->getData($column), $value) === false) { $keep = false; break; } diff --git a/src/module-elasticsuite-indices/i18n/de_DE.csv b/src/module-elasticsuite-indices/i18n/de_DE.csv index 2200bbeb0..66f42e661 100644 --- a/src/module-elasticsuite-indices/i18n/de_DE.csv +++ b/src/module-elasticsuite-indices/i18n/de_DE.csv @@ -37,3 +37,5 @@ "Position","Position" "Start Offset","Start-Versatz" "End Offset","Ende Versatz" +"Show all indices","Alle Indizes anzeigen" +"Hide external indices","Externe Indizes ausblenden" diff --git a/src/module-elasticsuite-indices/i18n/en_US.csv b/src/module-elasticsuite-indices/i18n/en_US.csv index f6131d81c..57eeecc78 100644 --- a/src/module-elasticsuite-indices/i18n/en_US.csv +++ b/src/module-elasticsuite-indices/i18n/en_US.csv @@ -37,3 +37,5 @@ "Position","Position" "Start Offset","Start Offset" "End Offset","End Offset" +"Show all indices","Show all indices" +"Hide external indices","Hide external indices" diff --git a/src/module-elasticsuite-indices/i18n/fr_FR.csv b/src/module-elasticsuite-indices/i18n/fr_FR.csv index cb5cc123a..5e62019b6 100644 --- a/src/module-elasticsuite-indices/i18n/fr_FR.csv +++ b/src/module-elasticsuite-indices/i18n/fr_FR.csv @@ -37,3 +37,5 @@ "Position","Position" "Start Offset","Début de la chaîne" "End Offset","Fin de la chaîne" +"Show all indices","Voir tous les index" +"Hide external indices","Cacher les index externes" diff --git a/src/module-elasticsuite-indices/i18n/nl_NL.csv b/src/module-elasticsuite-indices/i18n/nl_NL.csv index 7b2dae690..9e31b7dad 100644 --- a/src/module-elasticsuite-indices/i18n/nl_NL.csv +++ b/src/module-elasticsuite-indices/i18n/nl_NL.csv @@ -37,3 +37,5 @@ "Position","Positie" "Start Offset","Start Compensatie" "End Offset","Offset eindigen" +"Show all indices","Toon alle indexen" +"Hide external indices","Externe indexen verbergen" diff --git a/src/module-elasticsuite-indices/view/adminhtml/layout/smile_elasticsuite_indices_index_index.xml b/src/module-elasticsuite-indices/view/adminhtml/layout/smile_elasticsuite_indices_index_index.xml index a7e51c104..c378ae375 100644 --- a/src/module-elasticsuite-indices/view/adminhtml/layout/smile_elasticsuite_indices_index_index.xml +++ b/src/module-elasticsuite-indices/view/adminhtml/layout/smile_elasticsuite_indices_index_index.xml @@ -86,7 +86,17 @@ Index Status index_status - 0 + Magento\Backend\Block\Widget\Grid\Column\Filter\Select + + + + Show all indices + + + external + Hide external indices + + text left Smile\ElasticsuiteIndices\Block\Widget\Grid\Column\Renderer\IndexStatus diff --git a/src/module-elasticsuite-indices/view/adminhtml/web/css/source/_module.less b/src/module-elasticsuite-indices/view/adminhtml/web/css/source/_module.less index 2ea61e294..c8dddf9c0 100644 --- a/src/module-elasticsuite-indices/view/adminhtml/web/css/source/_module.less +++ b/src/module-elasticsuite-indices/view/adminhtml/web/css/source/_module.less @@ -29,3 +29,14 @@ background: #000000; color: #e22626; } + +.smile_elasticsuite_indices-index-index { + .data-grid { + .col-index_status { + max-width: 13rem; + } + .col-number_of_documents, .col-size { + text-align: right; + } + } +} diff --git a/src/module-elasticsuite-thesaurus/Config/ThesaurusConfig.php b/src/module-elasticsuite-thesaurus/Config/ThesaurusConfig.php index e09269e31..f89ab7377 100644 --- a/src/module-elasticsuite-thesaurus/Config/ThesaurusConfig.php +++ b/src/module-elasticsuite-thesaurus/Config/ThesaurusConfig.php @@ -62,6 +62,16 @@ public function getMaxRewrites() return (int) $this->general['max_rewrites']; } + /** + * Max allowed alternative queries generated by the synonym engine. + * + * @return int + */ + public function getMaxRewrittenQueries() + { + return (int) $this->general['max_rewritten_queries']; + } + /** * Is the synonyms search enabled ? * diff --git a/src/module-elasticsuite-thesaurus/Plugin/QueryRewrite.php b/src/module-elasticsuite-thesaurus/Plugin/QueryRewrite.php index 1ec26a3a9..7971c4d77 100644 --- a/src/module-elasticsuite-thesaurus/Plugin/QueryRewrite.php +++ b/src/module-elasticsuite-thesaurus/Plugin/QueryRewrite.php @@ -17,6 +17,8 @@ use Smile\ElasticsuiteCore\Search\Request\Query\Fulltext\QueryBuilder; use Smile\ElasticsuiteCore\Api\Search\Request\ContainerConfigurationInterface; use Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory; +use Smile\ElasticsuiteThesaurus\Config\ThesaurusConfig; +use Smile\ElasticsuiteThesaurus\Config\ThesaurusConfigFactory; use Smile\ElasticsuiteThesaurus\Model\Index; use Smile\ElasticsuiteCore\Api\Search\SpellcheckerInterface; use Smile\ElasticsuiteCore\Search\Request\QueryInterface; @@ -35,6 +37,11 @@ class QueryRewrite */ private $queryFactory; + /** + * @var ThesaurusConfigFactory + */ + private $thesaurusConfigFactory; + /** * @var Index */ @@ -48,12 +55,17 @@ class QueryRewrite /** * Constructor. * - * @param QueryFactory $queryFactory Search request query factory. - * @param Index $index Synonym index. + * @param QueryFactory $queryFactory Search request query factory. + * @param ThesaurusConfigFactory $thesaurusConfigFactory Thesaurus configuration factory. + * @param Index $index Synonym index. */ - public function __construct(QueryFactory $queryFactory, Index $index) - { + public function __construct( + QueryFactory $queryFactory, + ThesaurusConfigFactory $thesaurusConfigFactory, + Index $index + ) { $this->queryFactory = $queryFactory; + $this->thesaurusConfigFactory = $thesaurusConfigFactory; $this->index = $index; } @@ -141,6 +153,26 @@ private function getWeightedRewrites($queryText, $containerConfig, $originalBoos $rewrites = $rewrites + $this->index->getQueryRewrites($containerConfig, $currentQueryText, $originalBoost); } + $maxRewrittenQueries = $this->getThesaurusConfig($containerConfig)->getMaxRewrittenQueries(); + if ($maxRewrittenQueries > 0) { + $rewrites = array_slice($rewrites, 0, $maxRewrittenQueries, true); + } + return $rewrites; } + + /** + * Return thesaurus/relevance configuration. + * + * @param ContainerConfigurationInterface $containerConfig Container configuration. + * + * @return ThesaurusConfig + */ + private function getThesaurusConfig(ContainerConfigurationInterface $containerConfig) + { + $storeId = $containerConfig->getStoreId(); + $containerName = $containerConfig->getName(); + + return $this->thesaurusConfigFactory->create($storeId, $containerName); + } } diff --git a/src/module-elasticsuite-thesaurus/Test/Unit/Plugin/QueryRewriteTest.php b/src/module-elasticsuite-thesaurus/Test/Unit/Plugin/QueryRewriteTest.php index ddd1225ff..bc139cb4c 100644 --- a/src/module-elasticsuite-thesaurus/Test/Unit/Plugin/QueryRewriteTest.php +++ b/src/module-elasticsuite-thesaurus/Test/Unit/Plugin/QueryRewriteTest.php @@ -28,6 +28,8 @@ use Smile\ElasticsuiteCore\Search\Request\Query\Builder; use Smile\ElasticsuiteCore\Search\Request\Query\QueryFactory; use Smile\ElasticsuiteCore\Search\Request\QueryInterface; +use Smile\ElasticsuiteThesaurus\Config\ThesaurusConfig; +use Smile\ElasticsuiteThesaurus\Config\ThesaurusConfigFactory; use Smile\ElasticsuiteThesaurus\Model\Index as ThesaurusIndex; use Smile\ElasticsuiteThesaurus\Plugin\QueryRewrite; use Smile\ElasticsuiteThesaurus\Test\Unit\FulltextQueryBuilderInterceptor; @@ -89,11 +91,13 @@ public function testMultipleSearchQueryDepthBuilder() $containerConfig = $this->getContainerConfigMock($this->fields); $spellingType = SpellcheckerInterface::SPELLING_TYPE_EXACT; + $thesaurusConfigFactory = $this->getThesaurusConfigFactoryMock(); + $thesaurusIndex = $this->getMockBuilder(ThesaurusIndex::class) ->disableOriginalConstructor() ->getMock(); - $queryRewritePlugin = new QueryRewrite($queryFactory, $thesaurusIndex); + $queryRewritePlugin = new QueryRewrite($queryFactory, $thesaurusConfigFactory, $thesaurusIndex); $queryBuilderInterceptor = $this->getQueryBuilderWithPlugin($queryFactory, $queryRewritePlugin); /* @@ -130,11 +134,13 @@ public function testMultipleSearchQueryDepthBuilderWithRewrites() $containerConfig = $this->getContainerConfigMock($this->fields); $spellingType = SpellcheckerInterface::SPELLING_TYPE_EXACT; + $thesaurusConfigFactory = $this->getThesaurusConfigFactoryMock(); + $thesaurusIndex = $this->getMockBuilder(ThesaurusIndex::class) ->disableOriginalConstructor() ->getMock(); - $queryRewritePlugin = new QueryRewrite($queryFactory, $thesaurusIndex); + $queryRewritePlugin = new QueryRewrite($queryFactory, $thesaurusConfigFactory, $thesaurusIndex); $queryBuilderInterceptor = $this->getQueryBuilderWithPlugin($queryFactory, $queryRewritePlugin); $thesaurusIndex->expects($this->exactly(2))->method('getQueryRewrites')->withConsecutive( @@ -219,6 +225,26 @@ private function getQueryFactory($queryTypes) return new QueryFactory($factories); } + /** + * Mock the thesaurus config factory. + * + * @return \PHPUnit\Framework\MockObject\MockObject + */ + private function getThesaurusConfigFactoryMock() + { + $thesaurusConfig = $this->getMockBuilder(ThesaurusConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $thesaurusConfig->method('getMaxRewrittenQueries')->will($this->returnValue(0)); + + $thesaurusConfigFactory = $this->getMockBuilder(ThesaurusConfigFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $thesaurusConfigFactory->method('create')->will($this->returnValue($thesaurusConfig)); + + return $thesaurusConfigFactory; + } + /** * Mock the configuration used by the query builder. * diff --git a/src/module-elasticsuite-thesaurus/etc/adminhtml/elasticsuite_relevance.xml b/src/module-elasticsuite-thesaurus/etc/adminhtml/elasticsuite_relevance.xml index ba0b34a8f..156a632a1 100644 --- a/src/module-elasticsuite-thesaurus/etc/adminhtml/elasticsuite_relevance.xml +++ b/src/module-elasticsuite-thesaurus/etc/adminhtml/elasticsuite_relevance.xml @@ -23,8 +23,14 @@ - + + + + + + + integer validate-number validate-zero-or-greater @@ -41,7 +47,7 @@ - + diff --git a/src/module-elasticsuite-thesaurus/etc/elasticsuite_relevance.xml b/src/module-elasticsuite-thesaurus/etc/elasticsuite_relevance.xml index 48e7257f3..c256ece23 100644 --- a/src/module-elasticsuite-thesaurus/etc/elasticsuite_relevance.xml +++ b/src/module-elasticsuite-thesaurus/etc/elasticsuite_relevance.xml @@ -18,6 +18,7 @@ 2 + 0 1 diff --git a/src/module-elasticsuite-thesaurus/i18n/de_DE.csv b/src/module-elasticsuite-thesaurus/i18n/de_DE.csv index 84df0317b..c756ee8a1 100644 --- a/src/module-elasticsuite-thesaurus/i18n/de_DE.csv +++ b/src/module-elasticsuite-thesaurus/i18n/de_DE.csv @@ -30,6 +30,11 @@ "Total of %1 record(s) were deleted.","%1 Eintrag / Einträge wurden gelöscht." "You saved the thesaurus %1.","Der Thesaurus %1 wurde gespeichert." "Thesaurus Configuration","Thesaurus Einstellungen" +"General Configuration","General Configuration" +"Max Allowed Rewrites","Max Allowed Rewrites" +"Maximum number of thesaurus rules applied at a given time to a given search query to produce alternative queries. That number applies first to the synonyms rules and then the expansion rules. For instance if the setting's value is 2, it means each alternative query will be the result of the application of at most 2 synonyms rules and at most 2 expansion rules. But if you have 10 synonym rules and 5 expansion rules, they could all end up being applied by pairs. So be careful about augmenting this setting's value, especially if you already have a lot of rules with long lists of alternative terms.","Maximum number of thesaurus rules applied at a given time to a given search query to produce alternative queries. That number applies first to the synonyms rules and then the expansion rules. For instance if the setting's value is 2, it means each alternative query will be the result of the application of at most 2 synonyms rules and at most 2 expansion rules. But if you have 10 synonym rules and 5 expansion rules, they could all end up being applied by pairs. So be careful about augmenting this setting's value, especially if you already have a lot of rules with long lists of alternative terms." +"Max Alternative Search Queries","Max Alternative Search Queries" +"Maximum number of alternative search queries taken into account. Use this setting if you have performance issues arising on your cluster related to a huge volume of thesaurus rules. Defaults to 0 (no limitation).","Maximum number of alternative search queries taken into account. Use this setting if you have performance issues arising on your cluster related to a huge volume of thesaurus rules. Defaults to 0 (no limitation)." "Synonyms Configuration","Synonyme Einstellungen" "Enable Synonyms Search","Aktiviere Suche nach Synonymen" "Synonyms Weight Divider","Verteilung der Gewichtung von Synonymen" diff --git a/src/module-elasticsuite-thesaurus/i18n/en_US.csv b/src/module-elasticsuite-thesaurus/i18n/en_US.csv index 57eefe7b3..af4bc84f6 100644 --- a/src/module-elasticsuite-thesaurus/i18n/en_US.csv +++ b/src/module-elasticsuite-thesaurus/i18n/en_US.csv @@ -30,6 +30,11 @@ Synonyms,Synonyms "Total of %1 record(s) were deleted.","Total of %1 record(s) were deleted." "You saved the thesaurus %1.","You saved the thesaurus %1." "Thesaurus Configuration","Thesaurus Configuration" +"General Configuration","General Configuration" +"Max Allowed Rewrites","Max Allowed Rewrites" +"Maximum number of thesaurus rules applied at a given time to a given search query to produce alternative queries. That number applies first to the synonyms rules and then the expansion rules. For instance if the setting's value is 2, it means each alternative query will be the result of the application of at most 2 synonyms rules and at most 2 expansion rules. But if you have 10 synonym rules and 5 expansion rules, they could all end up being applied by pairs. So be careful about augmenting this setting's value, especially if you already have a lot of rules with long lists of alternative terms.","Maximum number of thesaurus rules applied at a given time to a given search query to produce alternative queries. That number applies first to the synonyms rules and then the expansion rules. For instance if the setting's value is 2, it means each alternative query will be the result of the application of at most 2 synonyms rules and at most 2 expansion rules. But if you have 10 synonym rules and 5 expansion rules, they could all end up being applied by pairs. So be careful about augmenting this setting's value, especially if you already have a lot of rules with long lists of alternative terms." +"Max Alternative Search Queries","Max Alternative Search Queries" +"Maximum number of alternative search queries taken into account. Use this setting if you have performance issues arising on your cluster related to a huge volume of thesaurus rules. Defaults to 0 (no limitation).","Maximum number of alternative search queries taken into account. Use this setting if you have performance issues arising on your cluster related to a huge volume of thesaurus rules. Defaults to 0 (no limitation)." "Synonyms Configuration","Synonyms Configuration" "Enable Synonyms Search","Enable Synonyms Search" "Synonyms Weight Divider","Synonyms Weight Divider" diff --git a/src/module-elasticsuite-thesaurus/i18n/fr_FR.csv b/src/module-elasticsuite-thesaurus/i18n/fr_FR.csv index d133accbf..13ef87326 100644 --- a/src/module-elasticsuite-thesaurus/i18n/fr_FR.csv +++ b/src/module-elasticsuite-thesaurus/i18n/fr_FR.csv @@ -30,6 +30,11 @@ Synonyms,Synonymes "Total of %1 record(s) were deleted.","%1 enregistrement(s) ont été supprimé(s)." "You saved the thesaurus %1.","Thésaurus %1 sauvegardé." "Thesaurus Configuration","Configuration du thésaurus" +"General Configuration","Configuration Générale" +"Max Allowed Rewrites","Nombre maximum de remplacements" +"Maximum number of thesaurus rules applied at a given time to a given search query to produce alternative queries. That number applies first to the synonyms rules and then the expansion rules. For instance if the setting's value is 2, it means each alternative query will be the result of the application of at most 2 synonyms rules and at most 2 expansion rules. But if you have 10 synonym rules and 5 expansion rules, they could all end up being applied by pairs. So be careful about augmenting this setting's value, especially if you already have a lot of rules with long lists of alternative terms.","Nombre maximal de règles de thésaurus appliquées consécutivement à une requête de recherche pour générer des requêtes alternatives. Ce nombre s'applique d'abord aux règles de synonymes puis aux règles d'expansion. Par exemple si la valeur du paramètre est 2, chaque requête alternative sera le résultat de l'application d'au plus 2 règles de synonymes et de 2 règles d'expansion. Mais si vous avez 10 règles de synonymes et 5 règles d'expansions, elles pourraient au final être toutes appliquées par paires. Soyez donc prudent sur l'augmentation de ce paramètre, particulièrement si vous avez déjà beaucoup de règles avec de grandes listes de termes alternatifs." +"Max Alternative Search Queries","Nombre maximum de recherches alternatives" +"Maximum number of alternative search queries taken into account. Use this setting if you have performance issues arising on your cluster related to a huge volume of thesaurus rules. Defaults to 0 (no limitation).","Nombre de recherches alternatives réellement prises en compte. Utilisez ce paramètre si vous rencontrez des problèmes de performance de votre cluster liés à un énorme volume de règles de thésaurus. Valeur par défaut: 0 (pas de limitation)." "Synonyms Configuration","Configuration des synonymes" "Enable Synonyms Search","Activer la recherche par synonyme" "Synonyms Weight Divider","Pondérateur de poids pour les synonymes" diff --git a/src/module-elasticsuite-thesaurus/i18n/nl_NL.csv b/src/module-elasticsuite-thesaurus/i18n/nl_NL.csv index 5fcbfd270..8922d5cee 100644 --- a/src/module-elasticsuite-thesaurus/i18n/nl_NL.csv +++ b/src/module-elasticsuite-thesaurus/i18n/nl_NL.csv @@ -30,6 +30,11 @@ "Total of %1 record(s) were deleted.","Totaal van %1 record(s) werden verwijderd." "You saved the thesaurus %1.","Je hebt de thesaurus %1 gered." "Thesaurus Configuration","Thesaurus configuratie" +"General Configuration","General Configuration" +"Max Allowed Rewrites","Max Allowed Rewrites" +"Maximum number of thesaurus rules applied at a given time to a given search query to produce alternative queries. That number applies first to the synonyms rules and then the expansion rules. For instance if the setting's value is 2, it means each alternative query will be the result of the application of at most 2 synonyms rules and at most 2 expansion rules. But if you have 10 synonym rules and 5 expansion rules, they could all end up being applied by pairs. So be careful about augmenting this setting's value, especially if you already have a lot of rules with long lists of alternative terms.","Maximum number of thesaurus rules applied at a given time to a given search query to produce alternative queries. That number applies first to the synonyms rules and then the expansion rules. For instance if the setting's value is 2, it means each alternative query will be the result of the application of at most 2 synonyms rules and at most 2 expansion rules. But if you have 10 synonym rules and 5 expansion rules, they could all end up being applied by pairs. So be careful about augmenting this setting's value, especially if you already have a lot of rules with long lists of alternative terms." +"Max Alternative Search Queries","Max Alternative Search Queries" +"Maximum number of alternative search queries taken into account. Use this setting if you have performance issues arising on your cluster related to a huge volume of thesaurus rules. Defaults to 0 (no limitation).","Maximum number of alternative search queries taken into account. Use this setting if you have performance issues arising on your cluster related to a huge volume of thesaurus rules. Defaults to 0 (no limitation)." "Synonyms Configuration","Configuratie synoniemen" "Enable Synonyms Search","Synoniemen zoeken inschakelen" "Synonyms Weight Divider","Synoniemen Gewicht Verdeler" diff --git a/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php b/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php index c22689d77..1020153d0 100644 --- a/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php +++ b/src/module-elasticsuite-tracker/Cron/IndexLogEvent.php @@ -47,8 +47,8 @@ class IndexLogEvent * Constructor. * * @param \Smile\ElasticsuiteTracker\Api\EventQueueInterface $eventQueue Pending events queue. - * @param \Smile\ElasticsuiteTracker\Api\SessionIndexInterface $eventIndex Event index. - * @param \Smile\ElasticsuiteTracker\Api\EventIndexInterface $sessionIndex Session index. + * @param \Smile\ElasticsuiteTracker\Api\EventIndexInterface $eventIndex Event index. + * @param \Smile\ElasticsuiteTracker\Api\SessionIndexInterface $sessionIndex Session index. * @param integer $chunkSize Size of the chunk of events to index. */ public function __construct( diff --git a/src/module-elasticsuite-tracker/Helper/Data.php b/src/module-elasticsuite-tracker/Helper/Data.php index 361770217..bd2012b89 100644 --- a/src/module-elasticsuite-tracker/Helper/Data.php +++ b/src/module-elasticsuite-tracker/Helper/Data.php @@ -230,7 +230,7 @@ public function getAnonymizationDelay() } /** - * Return the tracking data retention delay, in days + * Return the tracking data retention delay, in months * * @return int */ diff --git a/src/module-elasticsuite-tracker/Model/Customer/TrackingService.php b/src/module-elasticsuite-tracker/Model/Customer/TrackingService.php index c44730070..087c625c6 100644 --- a/src/module-elasticsuite-tracker/Model/Customer/TrackingService.php +++ b/src/module-elasticsuite-tracker/Model/Customer/TrackingService.php @@ -76,8 +76,8 @@ public function hit($eventData): void public function addEvent($eventData) { if ($this->helper->isEnabled()) { - $this->eventQueue->addEvent($eventData); $this->addCustomerLink($eventData); + $this->eventQueue->addEvent($eventData); } } @@ -119,11 +119,11 @@ public function getVisitorIds(int $customerId) * * @param array $eventData Event */ - private function addCustomerLink($eventData) + private function addCustomerLink(&$eventData) { - // The customerId is set in session if the Magento_Persistent module is enabled and a persistent session exists. - if ($this->customerSession->getCustomerId() !== null) { - $customerId = $this->customerSession->getCustomerId(); + // The customerId should be sent by the frontend, if any. + $customerId = $eventData['customer']['id'] ?? null; + if ($customerId !== null && ((int) $customerId > 0)) { $sessionId = $eventData['session']['uid'] ?? null; $visitorId = $eventData['session']['vid'] ?? null; @@ -142,6 +142,7 @@ private function addCustomerLink($eventData) $this->customerLinkResource->saveLink($data); } + unset($eventData['customer']['id']); // Do not persist the customer_id in ES index to preserve anonymization. } } } diff --git a/src/module-elasticsuite-tracker/Model/CustomerDataTrackingManager.php b/src/module-elasticsuite-tracker/Model/CustomerDataTrackingManager.php index 2119987d1..960df6c6e 100644 --- a/src/module-elasticsuite-tracker/Model/CustomerDataTrackingManager.php +++ b/src/module-elasticsuite-tracker/Model/CustomerDataTrackingManager.php @@ -79,6 +79,7 @@ public function getCustomerDataToTrack() $customer = $this->customerSession->getCustomer(); $variables['group_id'] = (int) $customer->getGroupId() ?? \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID; + $variables['id'] = (int) $customer->getId(); if ($this->customerSession->isLoggedIn() && (null !== $this->companyManagement)) { try { diff --git a/src/module-elasticsuite-tracker/Model/ResourceModel/EventQueue.php b/src/module-elasticsuite-tracker/Model/ResourceModel/EventQueue.php index a531e8136..e097315d2 100644 --- a/src/module-elasticsuite-tracker/Model/ResourceModel/EventQueue.php +++ b/src/module-elasticsuite-tracker/Model/ResourceModel/EventQueue.php @@ -249,8 +249,8 @@ protected function isEventInvalid($data) if (array_key_exists('session', $data)) { if (array_key_exists('uid', $data['session']) && array_key_exists('vid', $data['session'])) { $isEventInvalid = false; - $sessionUid = trim($data['session']['uid']); - $sessionVid = trim($data['session']['vid']); + $sessionUid = trim($data['session']['uid'] ?? ''); + $sessionVid = trim($data['session']['vid'] ?? ''); if (empty($sessionUid) || ("null" === $sessionUid)) { $isEventInvalid = true; } diff --git a/src/module-elasticsuite-tracker/Plugin/SessionStartCheckerPlugin.php b/src/module-elasticsuite-tracker/Plugin/SessionStartCheckerPlugin.php new file mode 100644 index 000000000..81e994147 --- /dev/null +++ b/src/module-elasticsuite-tracker/Plugin/SessionStartCheckerPlugin.php @@ -0,0 +1,70 @@ + + * @copyright 2024 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteTracker\Plugin; + +use Magento\Framework\App\Request\Http; +use Magento\Framework\Session\SessionStartChecker; + +/** + * Prevent session creation when going through a tracker hit URL. + * Session creation can have performance issues when several ajax calls are sent in parallel. + * + * @category Smile + * @package Smile\ElasticsuiteTracker + * @author Romain Ruaud + */ +class SessionStartCheckerPlugin +{ + /** + * @var Http + */ + private $request; + + /** + * @param Http $request HTTP Request + * @SuppressWarnings(PHPMD.BooleanArgumentFlag) + */ + public function __construct( + Http $request + ) { + $this->request = $request; + } + + /** + * Prevents session starting when going through a tracker hit URL. + * + * @param SessionStartChecker $subject Session start checker + * @param bool $result Legacy result + * + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterCheck(SessionStartChecker $subject, bool $result): bool + { + if ($result === false) { + return false; + } + + $requestPath = trim($this->request->getPathInfo(), '/'); + + if ($requestPath === 'elasticsuite/tracker/hit/image/h.png') { + $result = false; + } elseif ($requestPath === 'rest/V1/elasticsuite-tracker/hit') { + $result = false; + } + + return $result; + } +} diff --git a/src/module-elasticsuite-tracker/etc/di.xml b/src/module-elasticsuite-tracker/etc/di.xml index 1a25f59f1..0c1ffe92e 100644 --- a/src/module-elasticsuite-tracker/etc/di.xml +++ b/src/module-elasticsuite-tracker/etc/di.xml @@ -117,4 +117,9 @@ + + + + + diff --git a/src/module-elasticsuite-tracker/view/frontend/templates/config.phtml b/src/module-elasticsuite-tracker/view/frontend/templates/config.phtml index c13adcb4c..9867de89a 100644 --- a/src/module-elasticsuite-tracker/view/frontend/templates/config.phtml +++ b/src/module-elasticsuite-tracker/view/frontend/templates/config.phtml @@ -38,7 +38,7 @@ if ($block->isEnabled()) { $userConsentScript = $this->escapeJsQuote($block->getUserConsentScript()); $userConsentConfig = $jsonHelper->jsonEncode($block->getUserConsentConfig()); - $scriptString = "//isEnabled()) { $scriptString .= "}"; $scriptString .= "});"; $scriptString .= "}catch(err){;}\n"; - $scriptString .= "//]]>\n"; echo /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false); } diff --git a/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js b/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js index c5089e646..cbbda5c6e 100644 --- a/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js +++ b/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js @@ -175,7 +175,7 @@ const smileTracker = (function () { } function getCustomerDataCodeToTrack() { - return ['age', 'gender', 'zipcode', 'state', 'country', 'group_id', 'company_id']; + return ['id', 'group_id', 'company_id']; } function setTrackerStyle(imgNode) { diff --git a/src/module-elasticsuite-virtual-category/Model/Preview.php b/src/module-elasticsuite-virtual-category/Model/Preview.php index b195e094c..c56002f5f 100644 --- a/src/module-elasticsuite-virtual-category/Model/Preview.php +++ b/src/module-elasticsuite-virtual-category/Model/Preview.php @@ -68,7 +68,7 @@ class Preview extends AbstractPreview /** * @var string */ - private $sortBy; + private $sortBy = null; /** * Constructor. @@ -120,7 +120,11 @@ protected function prepareProductCollection(Collection $collection) : Collection $sortBy = $this->getSortBy() ?? 'position'; $directionFallback = $sortBy !== 'position' ? Collection::SORT_ORDER_ASC : Collection::SORT_ORDER_DESC; - $collection->setOrder($sortBy, $this->request->getParam('sort_direction', $directionFallback)); + $direction = $this->request->getParam('sort_direction', $directionFallback); + if (empty($direction) || ((string) $direction === '')) { + $direction = $directionFallback; + } + $collection->setOrder($sortBy, $direction); $collection->addPriceData(self::DEFAULT_CUSTOMER_GROUP_ID, $this->category->getStore()->getWebsiteId()); return $collection; @@ -218,7 +222,12 @@ private function getSortBy() : string $useConfig = $this->request->getParam('use_config', []); $useConfig = array_key_exists('default_sort_by', $useConfig) && $useConfig['default_sort_by'] == 'true'; $defaultSortBy = $this->categoryConfig->getProductListDefaultSortBy(); - $this->sortBy = $useConfig ? $defaultSortBy : $this->request->getParam('default_sort_by'); + $sortBy = $this->request->getParam('default_sort_by', $defaultSortBy); + if (empty($sortBy) || ((string) $sortBy === '')) { + $sortBy = $defaultSortBy; + } + + $this->sortBy = $useConfig ? $defaultSortBy : $sortBy; } return $this->sortBy; diff --git a/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/SaveProductsPositions.php b/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/SaveProductsPositions.php index 331ac5220..f7b3d9df2 100644 --- a/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/SaveProductsPositions.php +++ b/src/module-elasticsuite-virtual-category/Plugin/Catalog/Category/SaveProductsPositions.php @@ -26,6 +26,11 @@ */ class SaveProductsPositions extends AbstractIndexerPlugin { + /** + * @var \Magento\Framework\Message\ManagerInterface + */ + protected $messageManager; + /** * @var \Magento\Framework\Json\Helper\Data */ @@ -44,17 +49,20 @@ class SaveProductsPositions extends AbstractIndexerPlugin * @param \Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Category\Product\Position $saveHandler Product position * save handler. * @param \Magento\Framework\Json\Helper\Data $jsonHelper JSON Helper. + * @param \Magento\Framework\Message\ManagerInterface $messageManager Message Manager. */ public function __construct( \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry, FullIndexer $fullIndexer, \Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\Category\Product\Position $saveHandler, - \Magento\Framework\Json\Helper\Data $jsonHelper + \Magento\Framework\Json\Helper\Data $jsonHelper, + \Magento\Framework\Message\ManagerInterface $messageManager ) { parent::__construct($indexerRegistry, $fullIndexer); $this->jsonHelper = $jsonHelper; $this->saveHandler = $saveHandler; + $this->messageManager = $messageManager; } /** @@ -145,17 +153,23 @@ private function getAffectedProductIds($category) * * @param \Magento\Catalog\Model\Category $category Category * - * @return array + * @return $this */ private function unserializeProductPositions(\Magento\Catalog\Model\Category $category) { + // Get product positions from the category. $productPositions = $category->getSortedProducts() ? $category->getSortedProducts() : []; if (is_string($productPositions)) { try { $productPositions = $this->jsonHelper->jsonDecode($productPositions); } catch (\Exception $e) { - $productPositions = []; + $this->messageManager->addWarningMessage( + __('Something went wrong while saving your product positions, they have been switched back to their last known state.') + ); + + // Fallback to the last known valid product positions. + $productPositions = $this->saveHandler->getProductPositionsByCategory($category); } } diff --git a/src/module-elasticsuite-virtual-category/etc/di.xml b/src/module-elasticsuite-virtual-category/etc/di.xml index 035839fd0..08a0eeda8 100644 --- a/src/module-elasticsuite-virtual-category/etc/di.xml +++ b/src/module-elasticsuite-virtual-category/etc/di.xml @@ -134,7 +134,7 @@ Smile\ElasticsuiteCatalog\Model\Widget\Product\CollectionFactory Smile\ElasticsuiteVirtualCategory\Model\Condition\ElasticsearchBuilder - +