├── README.md
├── composer.json
└── src
├── module-elasticsuite-autocomplete
├── Controller
│ └── Index
│ │ └── Index.php
├── Model
│ ├── Indexer
│ │ ├── Category
│ │ │ ├── Breadcrumbs.php
│ │ │ └── Fulltext
│ │ │ │ └── Datasource
│ │ │ │ ├── Breadcrumbs.php
│ │ │ │ └── Url.php
│ │ └── Product
│ │ │ └── Fulltext
│ │ │ └── Datasource
│ │ │ ├── AdditionalAttributes.php
│ │ │ ├── ConfigurablePrice.php
│ │ │ └── Url.php
│ ├── Query.php
│ ├── Query
│ │ ├── Category.php
│ │ └── Product.php
│ └── Render
│ │ ├── Category.php
│ │ └── Product.php
├── Plugin
│ └── ImproveSearch.php
├── Provider
│ └── Config.php
├── ViewModel
│ └── Form.php
├── composer.json
├── etc
│ ├── adminhtml
│ │ └── system.xml
│ ├── config.xml
│ ├── di.xml
│ ├── frontend
│ │ └── routes.xml
│ └── module.xml
├── registration.php
└── view
│ └── frontend
│ └── layout
│ └── default.xml
└── pub
└── search.php
/README.md:
--------------------------------------------------------------------------------
1 | # Magento 2 Elastisuite Autocomplete speed up
2 |
3 | Magento 2 Module to speed autocomplete search with elasticsuite
4 |
5 | ## Features
6 |
7 | This module use :
8 | - Elasticsearch response
9 | - Load the minimum magento class to display product (price and image helper)
10 | - Return only products (with additional_attributes), categories
11 |
12 | To improve speed I try two ways of routing :
13 | - Default magento 2 routing way (by declaring routes.xml and use controller)
14 | - No routing way (use a search.php file in /pub directory)
15 |
16 | ## Installation
17 |
18 | ### Composer
19 | ```
20 | $ composer require "web200/magento-elasticsuite-autocomplete":"*"
21 | ```
22 |
23 | ### Or Github
24 | ```
25 | git clone git@github.com:Web200/magento-elasticsuite-autocomplete.git
26 | ```
27 |
28 | Copy
29 | ```
30 | $MAGENTO_ROOT/app/code/Web200/ElasticsuiteAutocomplete/pub/search.php
31 | ```
32 | in
33 | ```
34 | $MAGENTO_ROOT/pub/
35 | ```
36 |
37 | ### Nginx specification
38 |
39 | ```
40 | # Defaut magento installation (Nginx) protect php script execution, you need to edit your virtualhost like this :
41 | location ~ ^/(index|get|static|errors/report|errors/404|errors/503|health_check)\.php$ {
42 | # =>
43 | location ~ ^/(index|get|static|errors/report|errors/404|errors/503|health_check|search)\.php$ {
44 | ```
45 |
46 | ## Use
47 |
48 | - Reindex Magento Catalog
49 | ```
50 | php bin/magento indexer:reindex
51 | ```
52 |
53 | If you clone the repository don't forget to copy /pub/search.php file (file is copied automatically if you use composer install)
54 |
55 | ```
56 | # To test the module you only need to install it, and edit this file (by overriding in your theme module):
57 | # smile/elasticsuite/src/module-elasticsuite-core/view/frontend/templates/search/form.mini.phtml
58 |
59 | # Default elasticsuite module :
60 | "url":"getUrl('search/ajax/suggest'); ?>",
61 | # This Module with default magento routing :
62 | "url":"getUrl('autocomplete'); ?>",
63 | # This Module without magento routing :
64 | "url":"= $block->getFormViewModel()->getSearchUrl() ?>",
65 | ```
66 |
67 | ## Benchmarks
68 |
69 | For benchmarking, I use Magento 2.4.2 with sample data and all cache are active.
70 | Local ubuntu with mysql / elasticsearch / apache (no docker use)
71 |
72 | Results :
73 |
74 | * Elasticsuite last version (2.10.3) : 360ms
75 | * This Module with default magento routing : 120ms
76 | * This Module without magento routing : 80ms
77 |
78 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web200/magento-elasticsuite-autocomplete",
3 | "description": "Magento 2 Module to speed autocomplete search",
4 | "require": {
5 | "php": "^7.0|^8.0"
6 | },
7 | "type": "magento2-component",
8 | "license": [
9 | "OSL-3.0",
10 | "AFL-3.0"
11 | ],
12 | "replace": {
13 | "web200/module-elasticsuite-autocomplete": "self.version"
14 | },
15 | "autoload": {
16 | "files": [
17 | "src/module-elasticsuite-autocomplete/registration.php"
18 | ],
19 | "psr-4": {
20 | "Web200\\ElasticsuiteAutocomplete\\": "src/module-elasticsuite-autocomplete"
21 | }
22 | },
23 | "extra": {
24 | "map": [
25 | [
26 | "src/pub/search.php",
27 | "pub/search.php"
28 | ]
29 | ]
30 | },
31 | "minimum-stability": "dev",
32 | "prefer-stable": true
33 | }
34 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Controller/Index/Index.php:
--------------------------------------------------------------------------------
1 |
18 | * @copyright 2021 Web200
19 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
20 | * @link https://www.web200.fr/
21 | */
22 | class Index implements HttpGetActionInterface
23 | {
24 | /**
25 | * Query
26 | *
27 | * @var Query $query
28 | */
29 | protected $query;
30 | /**
31 | * Result json factory
32 | *
33 | * @var $resultJsonFactory
34 | */
35 | protected $resultJsonFactory;
36 |
37 | /**
38 | * Index constructor.
39 | *
40 | * @param Query $query
41 | * @param JsonFactory $resultJsonFactory
42 | */
43 | public function __construct(
44 | Query $query,
45 | JsonFactory $resultJsonFactory
46 | ) {
47 | $this->query = $query;
48 | $this->resultJsonFactory = $resultJsonFactory;
49 | }
50 |
51 | /**
52 | * Execute action based on request and return result
53 | *
54 | * @return ResultInterface|ResponseInterface
55 | */
56 | public function execute()
57 | {
58 | $resultJson = $this->resultJsonFactory->create();
59 | $resultJson->setData($this->query->execute());
60 |
61 | return $resultJson;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Indexer/Category/Breadcrumbs.php:
--------------------------------------------------------------------------------
1 |
18 | * @copyright 2021 Web200
19 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
20 | * @link https://www.web200.fr/
21 | */
22 | class Breadcrumbs
23 | {
24 | /**
25 | * Cache id categories
26 | *
27 | * @var string CACHE_ID_CATEGORIES
28 | */
29 | protected const CACHE_ID_CATEGORIES = 'elasticautocomplete_categories';
30 | /**
31 | * Store categories
32 | *
33 | * @var string[] $storeCategories
34 | */
35 | protected $storeCategories;
36 | /**
37 | * Cache
38 | *
39 | * @var CacheInterface $cache
40 | */
41 | protected $cache;
42 | /**
43 | * Category collection factory
44 | *
45 | * @var CollectionFactory $categoryCollectionFactory
46 | */
47 | protected $categoryCollectionFactory;
48 | /**
49 | * Json
50 | *
51 | * @var Json $json
52 | */
53 | protected $json;
54 | /**
55 | * StoreManager Interface
56 | *
57 | * @var StoreManagerInterface $storeManager
58 | */
59 | protected $storeManager;
60 |
61 | /**
62 | * Breadcrumbs constructor.
63 | *
64 | * @param StoreManagerInterface $storeManager
65 | * @param Json $json
66 | * @param CollectionFactory $categoryCollectionFactory
67 | * @param CacheInterface $cache
68 | */
69 | public function __construct(
70 | StoreManagerInterface $storeManager,
71 | Json $json,
72 | CollectionFactory $categoryCollectionFactory,
73 | CacheInterface $cache
74 | ) {
75 | $this->categoryCollectionFactory = $categoryCollectionFactory;
76 | $this->cache = $cache;
77 | $this->json = $json;
78 | $this->storeManager = $storeManager;
79 | }
80 |
81 | /**
82 | * buildCategoryPath
83 | *
84 | * @param $categoryPath
85 | * @param int $storeId
86 | *
87 | * @return string
88 | */
89 | public function getCategoryFromPath($categoryPath, int $storeId): string
90 | {
91 | /* first 2 categories can be ignored */
92 | $categoryIds = array_slice(explode('/', $categoryPath), 2);
93 | $categoriesWithNames = [];
94 | $storeCategories = $this->getStoreCategories($storeId);
95 | foreach ($categoryIds as $categoryId) {
96 | if (isset($storeCategories[$categoryId])) {
97 | $categoriesWithNames[] = $storeCategories[$categoryId]['name'];
98 | }
99 | }
100 |
101 | return implode('/', $categoriesWithNames);
102 | }
103 |
104 | /**
105 | * Get store categories
106 | *
107 | * @param int $storeId
108 | *
109 | * @return string[]|mixed
110 | */
111 | protected function getStoreCategories(int $storeId): array
112 | {
113 | if ($this->storeCategories === null) {
114 | $this->storeCategories = [];
115 | $rootCategoryId = $this->storeManager->getStore($storeId)->getRootCategoryId();
116 | $cacheKey = self::CACHE_ID_CATEGORIES . '-' . $rootCategoryId . '-' . $storeId;
117 | $cacheCategory = $this->cache->load($cacheKey);
118 | if (!$cacheCategory) {
119 | $categories = $this->categoryCollectionFactory->create()
120 | ->setStoreId($storeId)
121 | ->addAttributeToFilter('path', array('like' => "1/{$rootCategoryId}/%"))
122 | ->addAttributeToSelect('name');
123 | /** @var Category $categ */
124 | foreach ($categories as $categ) {
125 | $this->storeCategories[$categ->getData('entity_id')] = [
126 | 'name' => $categ->getData('name'),
127 | 'path' => $categ->getData('path')
128 | ];
129 | }
130 | $cacheCategory = $this->json->serialize($this->storeCategories);
131 | $this->cache->save($cacheCategory, $cacheKey);
132 | }
133 |
134 | $this->storeCategories = $this->json->unserialize($cacheCategory, true);
135 | }
136 |
137 | return $this->storeCategories;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Indexer/Category/Fulltext/Datasource/Breadcrumbs.php:
--------------------------------------------------------------------------------
1 |
16 | * @copyright 2021 Web200
17 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
18 | * @link https://www.web200.fr/
19 | */
20 | class Breadcrumbs implements DatasourceInterface
21 | {
22 | /**
23 | * Resource connection
24 | *
25 | * @var ResourceConnection $resource
26 | */
27 | protected $resource;
28 | /**
29 | * categoryBreadcrumbs
30 | *
31 | * @var CategoryBreadcrumbs $categoryBreadcrumbs
32 | */
33 | protected $categoryBreadcrumbs;
34 |
35 | /**
36 | * Breadcrumbs constructor.
37 | *
38 | * @param ResourceConnection $resource
39 | * @param CategoryBreadcrumbs $categoryBreadcrumbs
40 | */
41 | public function __construct(
42 | ResourceConnection $resource,
43 | CategoryBreadcrumbs $categoryBreadcrumbs
44 | ) {
45 | $this->resource = $resource;
46 | $this->categoryBreadcrumbs = $categoryBreadcrumbs;
47 | }
48 |
49 | /**
50 | * Add categories data to the index data.
51 | *
52 | * {@inheritdoc}
53 | */
54 | public function addData($storeId, array $indexData)
55 | {
56 | /** @var string[] $data */
57 | foreach ($indexData as $categoryId => $data) {
58 | if (!isset($data['path'])) {
59 | continue;
60 | }
61 | $indexData[$categoryId]['breadcrumb'] = $this->categoryBreadcrumbs->getCategoryFromPath(
62 | $data['path'],
63 | $storeId
64 | );
65 | }
66 |
67 | return $indexData;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Indexer/Category/Fulltext/Datasource/Url.php:
--------------------------------------------------------------------------------
1 |
15 | * @copyright 2021 Web200
16 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17 | * @link https://www.web200.fr/
18 | */
19 | class Url implements DatasourceInterface
20 | {
21 | /**
22 | * Resource connection
23 | *
24 | * @var ResourceConnection $resource
25 | */
26 | protected $resource;
27 |
28 | /**
29 | * Url constructor.
30 | *
31 | * @param ResourceConnection $resource
32 | */
33 | public function __construct(
34 | ResourceConnection $resource
35 | ) {
36 | $this->resource = $resource;
37 | }
38 |
39 | /**
40 | * Add categories data to the index data.
41 | *
42 | * {@inheritdoc}
43 | */
44 | public function addData($storeId, array $indexData)
45 | {
46 | $requestPathData = $this->getRequestPathData((int)$storeId);
47 | /** @var string[] $data */
48 | foreach ($requestPathData as $data) {
49 | if (isset($indexData[(int)$data['entity_id']])) {
50 | $indexData[(int)$data['entity_id']]['request_path'] = $data['request_path'];
51 | }
52 | }
53 |
54 | return $indexData;
55 | }
56 |
57 | /**
58 | * Get request path data
59 | *
60 | * @param int $storeId
61 | *
62 | * @return string[]
63 | */
64 | protected function getRequestPathData(int $storeId): array
65 | {
66 | $connection = $this->resource->getConnection();
67 | $select = $connection->select()->from(
68 | ['url_rewrite' => $connection->getTableName('url_rewrite')],
69 | ['request_path', 'entity_id']
70 | )->where('entity_type = ?', 'category')
71 | ->where('store_id = ?', $storeId)
72 | ->where('redirect_type = 0')
73 | ->where('metadata IS NULL');
74 |
75 | return $connection->fetchAll($select);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Indexer/Product/Fulltext/Datasource/AdditionalAttributes.php:
--------------------------------------------------------------------------------
1 |
23 | * @copyright 2021 Web200
24 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
25 | * @link https://www.web200.fr/
26 | */
27 | class AdditionalAttributes implements DatasourceInterface
28 | {
29 | /**
30 | * Resource connection
31 | *
32 | * @var ResourceConnection $resource
33 | */
34 | protected $resource;
35 | /**
36 | * Attribute config
37 | *
38 | * @var AttributeConfig $attributeConfig
39 | */
40 | protected $attributeConfig;
41 | /**
42 | * Attribute repository interface
43 | *
44 | * @var AttributeRepositoryInterface $eavAttributeRepositoryInterface
45 | */
46 | protected $eavAttributeRepositoryInterface;
47 | /**
48 | * Object manager interface
49 | *
50 | * @var ObjectManagerInterface $objectManager
51 | */
52 | protected $objectManager;
53 | /**
54 | * Emulation
55 | *
56 | * @var Emulation $appEmulation
57 | */
58 | protected $appEmulation;
59 | /**
60 | * Area list
61 | *
62 | * @var AreaList $areaList
63 | */
64 | protected $areaList;
65 |
66 | /**
67 | * AdditionalAttributes constructor.
68 | *
69 | * @param Emulation $appEmulation
70 | * @param AreaList $areaList
71 | * @param ObjectManagerInterface $objectManager
72 | * @param AttributeConfig $attributeConfig
73 | * @param AttributeRepositoryInterface $eavAttributeRepositoryInterface
74 | * @param ResourceConnection $resource
75 | */
76 | public function __construct(
77 | Emulation $appEmulation,
78 | AreaList $areaList,
79 | ObjectManagerInterface $objectManager,
80 | AttributeConfig $attributeConfig,
81 | AttributeRepositoryInterface $eavAttributeRepositoryInterface,
82 | ResourceConnection $resource
83 | ) {
84 | $this->resource = $resource;
85 | $this->attributeConfig = $attributeConfig;
86 | $this->eavAttributeRepositoryInterface = $eavAttributeRepositoryInterface;
87 | $this->objectManager = $objectManager;
88 | $this->appEmulation = $appEmulation;
89 | $this->areaList = $areaList;
90 | }
91 |
92 | /**
93 | * Add categories data to the index data.
94 | *
95 | * {@inheritdoc}
96 | */
97 | public function addData($storeId, array $indexData)
98 | {
99 | $this->appEmulation->startEnvironmentEmulation($storeId);
100 | $area = $this->areaList->getArea(Area::AREA_FRONTEND);
101 | $area->load(Area::PART_TRANSLATE);
102 |
103 | /** @var string $attributeCode */
104 | foreach ($this->attributeConfig->getAdditionalSelectedAttributes() as $attributeCode) {
105 | foreach (array_keys($indexData) as $productId) {
106 | $indexData[$productId][$attributeCode] = '';
107 | }
108 |
109 | $attribute = $this->eavAttributeRepositoryInterface->get(Product::ENTITY, $attributeCode);
110 | $values = $this->getValueFromAttributes((int)$storeId, $attribute, array_keys($indexData));
111 | if ($attribute->getSourceModel() !== null) {
112 | $values = $this->loadSourceModelValue($values, $attribute);
113 | }
114 | foreach ($values as $value) {
115 | $indexData[(int)$value['entity_id']][$attributeCode] = $value['value'];
116 | }
117 | }
118 |
119 | $this->appEmulation->stopEnvironmentEmulation();
120 |
121 | return $indexData;
122 | }
123 |
124 | /**
125 | * Get request path data
126 | *
127 | * @param int $storeId
128 | * @param AttributeInterface $attribute
129 | * @param array $productIds
130 | *
131 | * @return string[]
132 | */
133 | protected function getValueFromAttributes(int $storeId, AttributeInterface $attribute, array $productIds): array
134 | {
135 | if (!in_array($attribute->getBackendType(), ['datetime', 'decimal', 'int', 'varchar'])) {
136 | return [];
137 | }
138 |
139 | $connection = $this->resource->getConnection();
140 | $tableName = $connection->getTableName('catalog_product_entity_' . $attribute->getBackendType());
141 | $select = $connection->select()->from(
142 | ['cpe_default' => $tableName],
143 | []
144 | )->joinLeft(
145 | ['cpe' => $tableName],
146 | 'cpe.attribute_id = cpe_default.attribute_id AND
147 | cpe.entity_id = cpe_default.entity_id AND
148 | cpe.store_id = ' . $storeId,
149 | []
150 | )->columns([
151 | 'value' => new \Zend_Db_Expr('IFNULL(`cpe`.`value`, cpe_default.value)'),
152 | 'entity_id' => new \Zend_Db_Expr('IFNULL(`cpe`.`entity_id`, cpe_default.entity_id)'),
153 | ])
154 | ->where('cpe_default.store_id = 0')
155 | ->where('cpe_default.entity_id IN (?)', $productIds)
156 | ->where('cpe_default.attribute_id = ?', $attribute->getId());
157 |
158 | return $connection->fetchAll($select);
159 | }
160 |
161 | /**
162 | * Load source model value
163 | *
164 | * @param array $values
165 | * @param AttributeInterface $attribute
166 | *
167 | * @return mixed
168 | */
169 | protected function loadSourceModelValue(array $values, AttributeInterface $attribute): array
170 | {
171 | $sourceModel = $this->objectManager->get($attribute->getSourceModel());
172 | $options = [];
173 | $optionsArray = $sourceModel->toOptionArray();
174 | foreach ($optionsArray as $row) {
175 | $options[$row['value']] = $row['label'];
176 | }
177 |
178 | foreach ($values as &$value) {
179 | if (isset($options[$value['value']])) {
180 | $value['value'] = (string)$options[$value['value']];
181 | }
182 | }
183 |
184 | return $values;
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Indexer/Product/Fulltext/Datasource/ConfigurablePrice.php:
--------------------------------------------------------------------------------
1 |
21 | * @copyright 2021 Web200
22 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
23 | * @link https://www.web200.fr/
24 | */
25 | class ConfigurablePrice extends Indexer implements DatasourceInterface
26 | {
27 | /**
28 | * Dimension collection factory
29 | *
30 | * @var DimensionCollectionFactory $dimensionCollectionFactory
31 | */
32 | protected $dimensionCollectionFactory;
33 | /**
34 | * Dimensions
35 | *
36 | * @var string[] $dimensions
37 | */
38 | protected $dimensions;
39 | /**
40 | * Dimension by website
41 | *
42 | * @var string[] $dimensionsByWebsite
43 | */
44 | protected $dimensionsByWebsite = [];
45 | /**
46 | * Resource connection
47 | *
48 | * @var ResourceConnection $resource
49 | */
50 | protected $resource;
51 | /**
52 | * Price table resolver
53 | *
54 | * @var PriceTableResolver $priceTableResolver
55 | */
56 | protected $priceTableResolver;
57 |
58 | /**
59 | * ConfigurablePrice constructor.
60 | *
61 | * @param ResourceConnection $resource
62 | */
63 | public function __construct(
64 | ResourceConnection $resource,
65 | StoreManagerInterface $storeManager,
66 | MetadataPool $metadataPool,
67 | DimensionCollectionFactory $dimensionCollectionFactory,
68 | PriceTableResolver $priceTableResolver
69 | ) {
70 | parent::__construct($resource, $storeManager, $metadataPool);
71 |
72 | $this->resource = $resource;
73 | $this->priceTableResolver = $priceTableResolver;
74 | $this->dimensionCollectionFactory = $dimensionCollectionFactory;
75 | }
76 |
77 | /**
78 | * Add categories data to the index data.
79 | *
80 | * {@inheritdoc}
81 | */
82 | public function addData($storeId, array $indexData)
83 | {
84 | $configurableIds = [];
85 | /** @var string[] $data */
86 | foreach ($indexData as $data) {
87 | if ($data['type_id'] !== 'configurable') {
88 | continue;
89 | }
90 | $configurableIds[] = $data['entity_id'];
91 | }
92 |
93 | if (empty($configurableIds)) {
94 | return $indexData;
95 | }
96 |
97 |
98 | $websiteId = (int)$this->getStore($storeId)->getWebsiteId();
99 | $minFinalPriceData = $this->getMinFinalPrice($configurableIds, $websiteId, true);
100 | if (empty($minFinalPriceData)) {
101 | $minFinalPriceData = $this->getMinFinalPrice($configurableIds, $websiteId, false);
102 | }
103 | if (empty($minFinalPriceData)) {
104 | return $indexData;
105 | }
106 |
107 | foreach ($minFinalPriceData as $row) {
108 | if (!isset($indexData[$row['entity_id']]['price'])) {
109 | continue;
110 | }
111 | foreach ($indexData[$row['entity_id']]['price'] as &$priceRow) {
112 | if (isset($priceRow['customer_group_id']) && $priceRow['customer_group_id'] == $row['customer_group_id']) {
113 | $priceRow['original_price'] = $row['original_price'];
114 | break;
115 | }
116 | }
117 | }
118 |
119 | return $indexData;
120 | }
121 |
122 | /**
123 | * Get min final price
124 | *
125 | * @param array $parentIds
126 | * @param int $websiteId
127 | * @param bool $inStock
128 | *
129 | * @return array
130 | */
131 | protected function getMinFinalPrice(array $parentIds, int $websiteId, bool $inStock): array
132 | {
133 | $connection = $this->resource->getConnection();
134 | $select = $connection->select()->from(
135 | ['parent' => $connection->getTableName('catalog_product_entity')],
136 | []
137 | )->join(
138 | ['link' => $connection->getTableName('catalog_product_relation')],
139 | 'link.parent_id = parent.entity_id',
140 | []
141 | )->join(
142 | ['t' => $connection->getTableName($this->getPriceIndexDimensionsTables($websiteId))],
143 | 't.entity_id = link.child_id ',
144 | []
145 | )->join(
146 | ['csi' => $connection->getTableName('cataloginventory_stock_item')],
147 | 'csi.product_id = link.child_id AND csi.is_in_stock=' . $inStock ? '1' : '0',
148 | []
149 | )->columns([
150 | new \Zend_Db_Expr('t.customer_group_id'),
151 | new \Zend_Db_Expr('parent.entity_id'),
152 | new \Zend_Db_Expr('MIN(price) as original_price')
153 | ])
154 | ->where('parent.entity_id IN (?)', $parentIds)
155 | ->where('t.price > 0')
156 | ->group(['parent.entity_id', 't.customer_group_id']);
157 |
158 | return $connection->fetchAll($select);
159 | }
160 |
161 | /**
162 | * Return the price index tables according to the price index dimensions for the given website.
163 | *
164 | * @param integer $websiteId Website id.
165 | *
166 | * @return string
167 | */
168 | private function getPriceIndexDimensionsTables(int $websiteId): string
169 | {
170 | $tables = [];
171 |
172 | $indexDimensions = $this->getPriceIndexDimensions($websiteId);
173 | foreach ($indexDimensions as $dimensions) {
174 | $tables[] = $this->priceTableResolver->resolve('catalog_product_index_price', $dimensions);
175 | }
176 |
177 | return $tables[0];
178 | }
179 |
180 | /**
181 | * Return price index dimensions applicable for the given website.
182 | *
183 | * @param integer $websiteId
184 | *
185 | * @return array
186 | * @SuppressWarnings(PHPMD.ElseExpression)
187 | */
188 | private function getPriceIndexDimensions(int $websiteId)
189 | {
190 | if (!array_key_exists($websiteId, $this->dimensionsByWebsite)) {
191 | $indexDimensions = $this->getAllPriceIndexDimensions();
192 |
193 | $relevantDimensions = [];
194 | foreach ($indexDimensions as $dimensions) {
195 | if (array_key_exists(WebsiteDimensionProvider::DIMENSION_NAME, $dimensions)) {
196 | $websiteDimension = $dimensions[WebsiteDimensionProvider::DIMENSION_NAME];
197 | if ((string)$websiteDimension->getValue() == $websiteId) {
198 | $relevantDimensions[] = $dimensions;
199 | }
200 | } else {
201 | $relevantDimensions[] = $dimensions;
202 | }
203 | }
204 |
205 | $this->dimensionsByWebsite[$websiteId] = $relevantDimensions;
206 | }
207 |
208 | return $this->dimensionsByWebsite[$websiteId];
209 | }
210 |
211 | /**
212 | * Return all price index dimensions.
213 | *
214 | * @return array
215 | */
216 | private function getAllPriceIndexDimensions()
217 | {
218 | if ($this->dimensions === null) {
219 | $this->dimensions = $this->dimensionCollectionFactory->create();
220 | }
221 |
222 | return $this->dimensions;
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Indexer/Product/Fulltext/Datasource/Url.php:
--------------------------------------------------------------------------------
1 |
15 | * @copyright 2021 Web200
16 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17 | * @link https://www.web200.fr/
18 | */
19 | class Url implements DatasourceInterface
20 | {
21 | /**
22 | * Resource connection
23 | *
24 | * @var ResourceConnection $resource
25 | */
26 | protected $resource;
27 |
28 | /**
29 | * Url constructor.
30 | *
31 | * @param ResourceConnection $resource
32 | */
33 | public function __construct(
34 | ResourceConnection $resource
35 | ) {
36 | $this->resource = $resource;
37 | }
38 |
39 | /**
40 | * Add categories data to the index data.
41 | *
42 | * {@inheritdoc}
43 | */
44 | public function addData($storeId, array $indexData)
45 | {
46 | $requestPathData = $this->getRequestPathData((int)$storeId, array_keys($indexData));
47 | /** @var string[] $data */
48 | foreach ($requestPathData as $data) {
49 | $indexData[(int)$data['entity_id']]['request_path'] = $data['request_path'];
50 | }
51 |
52 | return $indexData;
53 | }
54 |
55 | /**
56 | * Get request path data
57 | *
58 | * @param int $storeId
59 | * @param array $productId
60 | *
61 | * @return string[]
62 | */
63 | protected function getRequestPathData(int $storeId, array $productId): array
64 | {
65 | $connection = $this->resource->getConnection();
66 | $select = $connection->select()->from(
67 | ['url_rewrite' => $connection->getTableName('url_rewrite')],
68 | ['request_path', 'entity_id']
69 | )->where('entity_type = ?', 'product')
70 | ->where('entity_id IN (?)', $productId)
71 | ->where('store_id = ?', $storeId)
72 | ->where('redirect_type = 0')
73 | ->where('metadata IS NULL');
74 |
75 | return $connection->fetchAll($select);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Query.php:
--------------------------------------------------------------------------------
1 |
16 | * @copyright 2021 Web200
17 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
18 | * @link https://www.web200.fr/
19 | */
20 | class Query
21 | {
22 | /**
23 | * Query Product
24 | *
25 | * @var QueryProduct $productQuery
26 | */
27 | protected $productQuery;
28 | /**
29 | * Query category
30 | *
31 | * @var QueryCategory $categoryQuery
32 | */
33 | protected $categoryQuery;
34 | /**
35 | * Config
36 | *
37 | * @var Config $config
38 | */
39 | protected $config;
40 |
41 | /**
42 | * Query constructor.
43 | *
44 | * @param Config $config
45 | * @param QueryProduct $productQuery
46 | * @param QueryCategory $categoryQuery
47 | */
48 | public function __construct(
49 | Config $config,
50 | QueryProduct $productQuery,
51 | QueryCategory $categoryQuery
52 | ) {
53 | $this->productQuery = $productQuery;
54 | $this->categoryQuery = $categoryQuery;
55 | $this->config = $config;
56 | }
57 |
58 | /**
59 | * Execute
60 | *
61 | * @return mixed
62 | */
63 | public function execute()
64 | {
65 | /** @var string[] $result */
66 | $result = [];
67 |
68 | if ($this->config->isProductActive()) {
69 | $result = array_merge($result, $this->productQuery->execute());
70 | }
71 | if ($this->config->isCategoryActive()) {
72 | $result = array_merge($result, $this->categoryQuery->execute());
73 | }
74 |
75 | return $result;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Query/Category.php:
--------------------------------------------------------------------------------
1 |
22 | * @copyright 2021 Web200
23 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
24 | * @link https://www.web200.fr/
25 | */
26 | class Category
27 | {
28 | /**
29 | * Client
30 | *
31 | * @var Client $client
32 | */
33 | protected $client;
34 | /**
35 | * Mapper
36 | *
37 | * @var Mapper $requestMapper
38 | */
39 | protected $requestMapper;
40 | /**
41 | * Request builder
42 | *
43 | * @var Builder $requestBuilder
44 | */
45 | protected $requestBuilder;
46 | /**
47 | * Store manager interface
48 | *
49 | * @var StoreManagerInterface $storeManager
50 | */
51 | protected $storeManager;
52 | /**
53 | * Cache
54 | *
55 | * @var CacheInterface $cache
56 | */
57 | protected $cache;
58 | /**
59 | * Json
60 | *
61 | * @var Json $json
62 | */
63 | protected $json;
64 | /**
65 | * Category render
66 | *
67 | * @var CategoryRender $categoryRender
68 | */
69 | protected $categoryRender;
70 | /**
71 | * Query factory
72 | *
73 | * @var QueryFactory $queryFactory
74 | */
75 | protected $queryFactory;
76 | /**
77 | * Config
78 | *
79 | * @var Config $config
80 | */
81 | protected $config;
82 |
83 | /**
84 | * Category constructor.
85 | *
86 | * @param Config $config
87 | * @param Builder $requestBuilder
88 | * @param Mapper $mapper
89 | * @param CacheInterface $cache
90 | * @param StoreManagerInterface $storeManager
91 | * @param Json $json
92 | * @param CategoryRender $categoryRender
93 | * @param QueryFactory $queryFactory
94 | * @param Client $client
95 | */
96 | public function __construct(
97 | Config $config,
98 | Builder $requestBuilder,
99 | Mapper $mapper,
100 | CacheInterface $cache,
101 | StoreManagerInterface $storeManager,
102 | Json $json,
103 | CategoryRender $categoryRender,
104 | QueryFactory $queryFactory,
105 | Client $client
106 | ) {
107 | $this->client = $client;
108 | $this->requestMapper = $mapper;
109 | $this->requestBuilder = $requestBuilder;
110 | $this->storeManager = $storeManager;
111 | $this->cache = $cache;
112 | $this->json = $json;
113 | $this->categoryRender = $categoryRender;
114 | $this->queryFactory = $queryFactory;
115 | $this->config = $config;
116 | }
117 |
118 | /**
119 | * Execute
120 | *
121 | * @return mixed
122 | */
123 | public function execute()
124 | {
125 | return $this->parseQuery($this->query());
126 | }
127 |
128 | /**
129 | * BuildQuery
130 | *
131 | * @return string[]
132 | */
133 | protected function buildQuery()
134 | {
135 | /** @var int $storeId */
136 | $storeId = $this->storeManager->getStore()->getId();
137 | /** @var string $key */
138 | $key = 'smile_autocomplete_category_query:' . $storeId;
139 | /** @var string $searchRequestJson */
140 | $searchRequestJson = $this->cache->load($key);
141 | if ($searchRequestJson === false) {
142 | $request = $this->requestBuilder->create(
143 | $storeId,
144 | 'category_search_container',
145 | 0,
146 | $this->config->getCategoryAutocompleteMaxSize(),
147 | 'product',
148 | [],
149 | [],
150 | []
151 | );
152 | /** @var string[] $searchRequest */
153 | $searchRequest = [
154 | 'index' => $request->getIndex(),
155 | 'body' => $this->requestMapper->buildSearchRequest($request),
156 | ];
157 | $this->cache->save($this->json->serialize($searchRequest), $key, ['autocomplete'], 3600);
158 |
159 | return $searchRequest;
160 | }
161 |
162 | return $this->json->unserialize($searchRequestJson);
163 | }
164 |
165 | /**
166 | * Query
167 | *
168 | * @return string[]
169 | */
170 | protected function query(): array
171 | {
172 | /** @var string $searchString */
173 | $searchString = $this->queryFactory->get()->getQueryText();
174 |
175 | /** @var string[] $query */
176 | $query = $this->buildQuery();
177 | if (isset($query['body']['query']['bool']['must']['bool']['should'])) {
178 | foreach ($query['body']['query']['bool']['must']['bool']['should'] as &$should) {
179 | $should['multi_match']['query'] = $searchString;
180 | }
181 | }
182 |
183 | return $this->client->search($query);
184 | }
185 |
186 | /**
187 | * Parse query
188 | *
189 | * @param string[] $result
190 | *
191 | * @return string[]
192 | */
193 | protected function parseQuery(array $result): array
194 | {
195 | if (!isset($result['hits'])) {
196 | return [];
197 | }
198 |
199 | /** @var string[] $final */
200 | $final = [];
201 | /** @var string[] $category */
202 | foreach ($result['hits']['hits'] as $category) {
203 | if (!isset($category['_source'])) {
204 | continue;
205 | }
206 | $final[] = $this->categoryRender->render($category);
207 | }
208 |
209 | return $final;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Query/Product.php:
--------------------------------------------------------------------------------
1 |
26 | * @copyright 2021 Web200
27 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
28 | * @link https://www.web200.fr/
29 | */
30 | class Product
31 | {
32 | /**
33 | * Client
34 | *
35 | * @var Client $client
36 | */
37 | protected $client;
38 | /**
39 | * Mapper
40 | *
41 | * @var Mapper $requestMapper
42 | */
43 | protected $requestMapper;
44 | /**
45 | * Request builder
46 | *
47 | * @var Builder $requestBuilder
48 | */
49 | protected $requestBuilder;
50 | /**
51 | * Store manager interface
52 | *
53 | * @var StoreManagerInterface $storeManager
54 | */
55 | protected $storeManager;
56 | /**
57 | * Cache
58 | *
59 | * @var CacheInterface $cache
60 | */
61 | protected $cache;
62 | /**
63 | * Json
64 | *
65 | * @var Json $json
66 | */
67 | protected $json;
68 | /**
69 | * Product render
70 | *
71 | * @var RenderProduct $productRender
72 | */
73 | protected $productRender;
74 | /**
75 | * Query factory
76 | *
77 | * @var QueryFactory $queryFactory
78 | */
79 | protected $queryFactory;
80 | /**
81 | * Config
82 | *
83 | * @var Config $config
84 | */
85 | protected $config;
86 | /**
87 | * Config
88 | *
89 | * @var TermDataProvider $termDataProvider
90 | */
91 | protected $termDataProvider;
92 |
93 | /**
94 | * Product constructor.
95 | *
96 | * @param Config $config
97 | * @param Builder $requestBuilder
98 | * @param Mapper $mapper
99 | * @param CacheInterface $cache
100 | * @param StoreManagerInterface $storeManager
101 | * @param Json $json
102 | * @param RenderProduct $productRender
103 | * @param QueryFactory $queryFactory
104 | * @param Client $client
105 | * @param TermDataProvider $termDataProvider
106 | */
107 | public function __construct(
108 | Config $config,
109 | Builder $requestBuilder,
110 | Mapper $mapper,
111 | CacheInterface $cache,
112 | StoreManagerInterface $storeManager,
113 | Json $json,
114 | RenderProduct $productRender,
115 | QueryFactory $queryFactory,
116 | Client $client,
117 | TermDataProvider $termDataProvider
118 | ) {
119 | $this->client = $client;
120 | $this->requestMapper = $mapper;
121 | $this->requestBuilder = $requestBuilder;
122 | $this->storeManager = $storeManager;
123 | $this->cache = $cache;
124 | $this->json = $json;
125 | $this->productRender = $productRender;
126 | $this->queryFactory = $queryFactory;
127 | $this->config = $config;
128 | $this->termDataProvider = $termDataProvider;
129 | }
130 |
131 | /**
132 | * Execute
133 | *
134 | * @return mixed
135 | */
136 | public function execute()
137 | {
138 | return $this->parseQuery($this->query());
139 | }
140 |
141 | /**
142 | * BuildQuery
143 | *
144 | * @return string[]
145 | * @throws NoSuchEntityException
146 | */
147 | protected function buildQuery()
148 | {
149 | /** @var int $storeId */
150 | $storeId = $this->storeManager->getStore()->getId();
151 |
152 | /** @var string[] $facets */
153 | $facets = [
154 | ['name' => 'attribute_set_id', 'type' => BucketInterface::TYPE_TERM, 'size' => 0],
155 | ['name' => 'indexed_attributes', 'type' => BucketInterface::TYPE_TERM, 'size' => 0],
156 | ];
157 | $request = $this->requestBuilder->create(
158 | $storeId,
159 | 'catalog_product_autocomplete',
160 | 0,
161 | $this->config->getProductAutocompleteMaxSize(),
162 | $this->getSearchTerm(),
163 | [],
164 | [],
165 | [],
166 | $facets
167 | );
168 | /** @var string[] $searchRequest */
169 | $searchRequest = [
170 | 'index' => $request->getIndex(),
171 | 'body' => $this->requestMapper->buildSearchRequest($request),
172 | ];
173 |
174 | return $searchRequest;
175 | }
176 |
177 | /**
178 | * Get search Term
179 | *
180 | * @return array|null[]|string[]
181 | */
182 | protected function getSearchTerm()
183 | {
184 | $terms = array_map(
185 | function (TermItem $termItem) {
186 | return $termItem->getTitle();
187 | },
188 | $this->termDataProvider->getItems()
189 | );
190 |
191 | if (empty($terms)) {
192 | $terms = [$this->queryFactory->get()->getQueryText()];
193 | }
194 |
195 | return $terms;
196 | }
197 |
198 | /**
199 | * Query
200 | *
201 | * @return string[]
202 | */
203 | protected function query(): array
204 | {
205 | return $this->client->search($this->buildQuery());
206 | }
207 |
208 | /**
209 | * Parse query
210 | *
211 | * @return array
212 | */
213 | protected function parseQuery(array $result): array
214 | {
215 | if (!isset($result['hits'])) {
216 | return [];
217 | }
218 |
219 | /** @var string[] $final */
220 | $final = [];
221 | foreach ($result['hits']['hits'] as $product) {
222 | if (!isset($product['_source'])) {
223 | continue;
224 | }
225 | $final[] = $this->productRender->render($product);
226 | }
227 |
228 | return $final;
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Render/Category.php:
--------------------------------------------------------------------------------
1 |
18 | * @copyright 2021 Web200
19 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
20 | * @link https://www.web200.fr/
21 | */
22 | class Category
23 | {
24 | /**
25 | * Store manager
26 | *
27 | * @var StoreManagerInterface $storeManager
28 | */
29 | protected $storeManager;
30 | /**
31 | * ScopeConfig
32 | *
33 | * @var ScopeConfigInterface $scopeConfig
34 | */
35 | protected $scopeConfig;
36 |
37 | /**
38 | * Product constructor.
39 | *
40 | * @param ScopeConfigInterface $scopeConfig
41 | * @param StoreManagerInterface $storeManager
42 | */
43 | public function __construct(
44 | ScopeConfigInterface $scopeConfig,
45 | StoreManagerInterface $storeManager
46 | ) {
47 | $this->storeManager = $storeManager;
48 | $this->scopeConfig = $scopeConfig;
49 | }
50 |
51 | /**
52 | * Render
53 | *
54 | * @param string[] $categoryData
55 | *
56 | * @return string[]
57 | */
58 | public function render(array $categoryData)
59 | {
60 | /** @var string[] $category */
61 | $category = [];
62 |
63 | $category['type'] = 'category';
64 | $category['title'] = $this->getFirstResult($categoryData['_source']['name']);
65 | $category['url'] = $this->generateCategoryUrl($categoryData);
66 | $category['breadcrumb'] = explode('/', $categoryData['_source']['breadcrumb']);
67 |
68 | return $category;
69 | }
70 |
71 | /**
72 | * Get first result
73 | *
74 | * @param $result
75 | *
76 | * @return mixed
77 | */
78 | protected function getFirstResult($result)
79 | {
80 | if (is_array($result)) {
81 | $result = $result[0];
82 | }
83 |
84 | return $result;
85 | }
86 |
87 | /**
88 | * Generate category url
89 | *
90 | * @param array $categoryData
91 | *
92 | * @return string
93 | */
94 | protected function generateCategoryUrl(array $categoryData): string
95 | {
96 | if (isset($productData['_source']['request_path'])) {
97 | $requestPath = $this->getFirstResult($categoryData['_source']['request_path']);
98 | } else {
99 | $suffix = (string)$this->scopeConfig->getValue(CategoryUrlPathGenerator::XML_PATH_CATEGORY_URL_SUFFIX, ScopeInterface::SCOPE_STORE);
100 | $requestPath = $this->getFirstResult($categoryData['_source']['url_key']) . $suffix;
101 | }
102 |
103 | return $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_LINK) . $this->getFirstResult($requestPath);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Model/Render/Product.php:
--------------------------------------------------------------------------------
1 |
23 | * @copyright 2021 Web200
24 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
25 | * @link https://www.web200.fr/
26 | */
27 | class Product
28 | {
29 | /**
30 | * Store manager
31 | *
32 | * @var StoreManagerInterface $storeManager
33 | */
34 | protected $storeManager;
35 | /**
36 | * Image helper
37 | *
38 | * @var $imageHelper
39 | */
40 | protected $imageHelper;
41 | /**
42 | * Product factory
43 | *
44 | * @var ProductFactory $productFactory
45 | */
46 | protected $productFactory;
47 | /**
48 | * Customer session
49 | *
50 | * @var Session $customerSession
51 | */
52 | protected $customerSession;
53 | /**
54 | * Attribute config
55 | *
56 | * @var AttributeConfig
57 | */
58 | protected $attributeConfig;
59 | /**
60 | * Data
61 | *
62 | * @var Data $priceHelper
63 | */
64 | protected $priceHelper;
65 | /**
66 | * scopeConfig
67 | *
68 | * @var ScopeConfigInterface $scopeConfig
69 | */
70 | protected $scopeConfig;
71 |
72 | /**
73 | * Product constructor.
74 | *
75 | * @param AttributeConfig $attributeConfig
76 | * @param ScopeConfigInterface $scopeConfig
77 | * @param StoreManagerInterface $storeManager
78 | * @param Data $priceHelper
79 | * @param Image $imageHelper
80 | * @param Session $customerSession
81 | * @param ProductFactory $productFactory
82 | */
83 | public function __construct(
84 | AttributeConfig $attributeConfig,
85 | ScopeConfigInterface $scopeConfig,
86 | StoreManagerInterface $storeManager,
87 | Data $priceHelper,
88 | Image $imageHelper,
89 | Session $customerSession,
90 | ProductFactory $productFactory
91 | ) {
92 | $this->storeManager = $storeManager;
93 | $this->imageHelper = $imageHelper;
94 | $this->productFactory = $productFactory;
95 | $this->customerSession = $customerSession;
96 | $this->attributeConfig = $attributeConfig;
97 | $this->priceHelper = $priceHelper;
98 | $this->scopeConfig = $scopeConfig;
99 | }
100 |
101 | /**
102 | * Render
103 | *
104 | * @param string[] $productData
105 | *
106 | * @return string[]
107 | */
108 | public function render(array $productData)
109 | {
110 | /** @var string[] $product */
111 | $product = [];
112 | $product['type'] = 'product';
113 | $product['type_id'] = $productData['_source']['type_id'];
114 | $product['title'] = $productData['_source']['name'][0];
115 | $product['url'] = $this->generateProductUrl($productData);
116 | $product['sku'] = $this->getFirstResult($productData['_source']['sku']);
117 | $product['image'] = $this->generateImageUrl($this->getFirstResult($productData['_source']['image']));
118 | list($product['regular_price_value'], $product['price_value'], $product['promotion_percentage']) = $this->getPriceValue($productData);
119 | if ($product['price_value'] <= 0) {
120 | $product['price_value'] = $this->getPrice($product, 'regular_price_value');
121 | }
122 | $product['price'] = $this->getPrice($product, 'price_value');
123 | $product['regular_price'] = $this->getPrice($product, 'regular_price_value');
124 |
125 | $additionalAttributes = $this->attributeConfig->getAdditionalSelectedAttributes();
126 | foreach ($additionalAttributes as $key) {
127 | if (isset($productData['_source'][$key])) {
128 | $product[$key] = $productData['_source'][$key];
129 | }
130 | }
131 |
132 | return $product;
133 | }
134 |
135 | /**
136 | * Get price
137 | *
138 | * @param array $productData
139 | *
140 | * @return mixed
141 | */
142 | protected function getPriceValue(array $productData)
143 | {
144 | if (!isset($productData['_source']['price'])) {
145 | return ['', '', ''];
146 | }
147 |
148 | /** @var string[] $prices */
149 | $prices = [];
150 | foreach ($productData['_source']['price'] as $price) {
151 | $prices[$price['customer_group_id']] = $price;
152 | }
153 |
154 | /** @var int $customerGroupId */
155 | $customerGroupId = $this->customerSession->getCustomerGroupId();
156 | if ($customerGroupId >= 0 && isset($prices[$customerGroupId])) {
157 | $regularPrice = $prices[$customerGroupId]['original_price'];
158 | $finalPrice = $prices[$customerGroupId]['price'];
159 | if ($finalPrice <= 0) {
160 | $finalPrice = $regularPrice;
161 | }
162 | $promotion = 0;
163 | if ($regularPrice != $finalPrice && $regularPrice > 0) {
164 | $promotion = round(100 - ($finalPrice * 100 / $regularPrice), 2);
165 | }
166 |
167 | return [
168 | $regularPrice,
169 | $finalPrice,
170 | $promotion
171 | ];
172 | }
173 |
174 | return ['', '', ''];
175 | }
176 |
177 | /**
178 | * Get price
179 | *
180 | * @param string[] $product
181 | * @param string $key
182 | *
183 | * @return string
184 | */
185 | protected function getPrice(array $product, string $key): string
186 | {
187 | $price = $this->priceHelper->currency($product[$key]);
188 |
189 | if ($product['type_id'] === 'configurable') {
190 | return __('As low as') . ' ' . $price;
191 | }
192 |
193 | return $price;
194 | }
195 |
196 | /**
197 | * Get first result
198 | *
199 | * @param $result
200 | *
201 | * @return mixed
202 | */
203 | protected function getFirstResult($result)
204 | {
205 | if (is_array($result)) {
206 | $result = $result[0];
207 | }
208 |
209 | return $result;
210 | }
211 |
212 | /**
213 | * Generate product url
214 | *
215 | * @param array $productData
216 | *
217 | * @return string
218 | */
219 | protected function generateProductUrl(array $productData): string
220 | {
221 | if (isset($productData['_source']['request_path'])) {
222 | $requestPath = $this->getFirstResult($productData['_source']['request_path']);
223 | } else {
224 | $suffix = (string)$this->scopeConfig->getValue(ProductUrlPathGenerator::XML_PATH_PRODUCT_URL_SUFFIX, ScopeInterface::SCOPE_STORE);
225 | $requestPath = $this->getFirstResult($productData['_source']['url_key']) . $suffix;
226 | }
227 |
228 | return $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_LINK) . $requestPath;
229 | }
230 |
231 | /**
232 | * Generate image url
233 | *
234 | * @param null|string $imagePath
235 | *
236 | * @return string
237 | */
238 | protected function generateImageUrl(?string $imagePath): string
239 | {
240 | if ($imagePath === null) {
241 | return '';
242 | }
243 |
244 | $product = $this->productFactory->create();
245 |
246 | return $this->imageHelper->init($product, 'smile_elasticsuite_autocomplete_product_image')
247 | ->setImageFile($imagePath)
248 | ->getUrl();
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Plugin/ImproveSearch.php:
--------------------------------------------------------------------------------
1 |
22 | * @copyright 2022 Web200
23 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
24 | * @link https://www.web200.fr/
25 | */
26 | class ImproveSearch
27 | {
28 | /**
29 | * @var QueryFactory
30 | */
31 | protected $queryFactory;
32 | /**
33 | * @var FieldFilterInterface[]
34 | */
35 | protected $fieldFilters;
36 | /**
37 | * @var Config $config
38 | */
39 | protected $config;
40 |
41 | /**
42 | * Constructor.
43 | *
44 | * @param Config $config
45 | * @param QueryFactory $queryFactory Query factory (used to build subqueries.
46 | * @param FieldFilterInterface[] $fieldFilters Field filters models.
47 | */
48 | public function __construct(
49 | Config $config,
50 | QueryFactory $queryFactory,
51 | array $fieldFilters = []
52 | ) {
53 | $this->queryFactory = $queryFactory;
54 | $this->fieldFilters = $fieldFilters;
55 | $this->config = $config;
56 | }
57 |
58 | /**
59 | * @param QueryBuilder $subject
60 | * @param QueryInterface $result
61 | * @param ContainerConfigurationInterface $containerConfig
62 | * @param string $queryText
63 | * @param string $spellingType
64 | * @param float|int $boost
65 | *
66 | * @return QueryInterface
67 | */
68 | public function afterCreate(
69 | QueryBuilder $subject,
70 | QueryInterface $result,
71 | ContainerConfigurationInterface $containerConfig,
72 | $queryText,
73 | $spellingType,
74 | $boost = 1
75 | ): QueryInterface {
76 |
77 | if ($this->config->isImproveSearchActive()) {
78 | return $this->create($containerConfig, $queryText, $spellingType);
79 | }
80 |
81 | return $result;
82 | }
83 |
84 | /**
85 | * Create the fulltext search query.
86 | *
87 | * @param ContainerConfigurationInterface $containerConfig Search request container configuration.
88 | * @param string $queryText The text query.
89 | * @param string $spellingType The type of spellchecked applied.
90 | * @param float $boost Boost of the created query.
91 | *
92 | * @return QueryInterface
93 | */
94 | public function create(ContainerConfigurationInterface $containerConfig, $queryText, $spellingType, $boost = 1)
95 | {
96 | $query = null;
97 |
98 | $fuzzySpellingTypes = [
99 | SpellcheckerInterface::SPELLING_TYPE_FUZZY,
100 | SpellcheckerInterface::SPELLING_TYPE_MOST_FUZZY
101 | ];
102 |
103 | if (is_array($queryText)) {
104 | $queries = [];
105 | foreach ($queryText as $currentQueryText) {
106 | $queries[] = $this->create($containerConfig, $currentQueryText, $spellingType);
107 | }
108 | $query = $this->queryFactory->create(QueryInterface::TYPE_BOOL, ['should' => $queries, 'boost' => $boost]);
109 | } elseif ($spellingType == SpellcheckerInterface::SPELLING_TYPE_PURE_STOPWORDS) {
110 | $query = $this->getPureStopwordsQuery($containerConfig, $queryText, $boost);
111 | } elseif (in_array($spellingType, $fuzzySpellingTypes)) {
112 | $query = $this->getSpellcheckedQuery($containerConfig, $queryText, $spellingType, $boost);
113 | }
114 |
115 | if ($query === null) {
116 | $queries = [];
117 | $queries[] = $this->getWeightedSearchQuery($containerConfig, $queryText);
118 | $queries[] = $this->getPhrasePrefixSearchQuery($containerConfig, $queryText);
119 | $queries[] = $this->getFuzzyQuery($containerConfig, $queryText);
120 |
121 | $query = $this->queryFactory->create(QueryInterface::TYPE_BOOL, ['should' => $queries, 'boost' => $boost]);
122 | }
123 |
124 | return $query;
125 | }
126 |
127 | /**
128 | * Provides a weighted search query (multi match) using mapping field configuration.
129 | *
130 | * @param ContainerConfigurationInterface $containerConfig Search request container configuration.
131 | * @param string $queryText The text query.
132 | *
133 | * @return QueryInterface
134 | */
135 | private function getWeightedSearchQuery(ContainerConfigurationInterface $containerConfig, $queryText)
136 | {
137 | $relevanceConfig = $containerConfig->getRelevanceConfig();
138 | $phraseMatchBoost = $relevanceConfig->getPhraseMatchBoost();
139 | $defaultSearchField = MappingInterface::DEFAULT_SEARCH_FIELD;
140 | $searchableFieldFilter = $this->fieldFilters['searchableFieldFilter'];
141 | $sortableAnalyzer = FieldInterface::ANALYZER_SORTABLE;
142 | $phraseAnalyzer = FieldInterface::ANALYZER_WHITESPACE;
143 |
144 | if (is_string($queryText) && str_word_count($queryText) > 1) {
145 | $phraseAnalyzer = FieldInterface::ANALYZER_SHINGLE;
146 | }
147 |
148 | $searchFields = array_merge(
149 | $this->getWeightedFields($containerConfig, null, $searchableFieldFilter, $defaultSearchField),
150 | $this->getWeightedFields(
151 | $containerConfig,
152 | $phraseAnalyzer,
153 | $searchableFieldFilter,
154 | $defaultSearchField,
155 | $phraseMatchBoost
156 | ),
157 | $this->getWeightedFields(
158 | $containerConfig,
159 | $sortableAnalyzer,
160 | $searchableFieldFilter,
161 | null,
162 | 2 * $phraseMatchBoost
163 | )
164 | );
165 |
166 | $queryParams = [
167 | 'fields' => $searchFields,
168 | 'queryText' => $queryText,
169 | 'minimumShouldMatch' => 1,
170 | 'cutoffFrequency' => $relevanceConfig->getCutOffFrequency(),
171 | 'tieBreaker' => $relevanceConfig->getTieBreaker(),
172 | ];
173 |
174 | return $this->queryFactory->create(QueryInterface::TYPE_MULTIMATCH, $queryParams);
175 | }
176 |
177 | /**
178 | * Provides a weighted search query (multi match) using mapping field configuration.
179 | *
180 | * @param ContainerConfigurationInterface $containerConfig Search request container configuration.
181 | * @param string $queryText The text query.
182 | *
183 | * @return QueryInterface
184 | */
185 | private function getPhrasePrefixSearchQuery(ContainerConfigurationInterface $containerConfig, $queryText)
186 | {
187 | $defaultSearchField = MappingInterface::DEFAULT_SEARCH_FIELD;
188 | $searchableFieldFilter = $this->fieldFilters['searchableFieldFilter'];
189 |
190 | $searchFields = array_merge(
191 | $this->getWeightedFields($containerConfig, null, $searchableFieldFilter, $defaultSearchField),
192 | );
193 |
194 | $queryParams = [
195 | 'fields' => $searchFields,
196 | 'queryText' => $queryText,
197 | 'type' => 'phrase_prefix'
198 | ];
199 |
200 | return $this->queryFactory->create(QueryInterface::TYPE_MULTIMATCH, $queryParams);
201 | }
202 |
203 | /**
204 | * Build a query when the fulltext search query contains only stopwords.
205 | *
206 | * @param ContainerConfigurationInterface $containerConfig Search request container configuration.
207 | * @param string $queryText The text query.
208 | * @param float $boost Boost of the created query.
209 | *
210 | * @return QueryInterface
211 | */
212 | private function getPureStopwordsQuery(ContainerConfigurationInterface $containerConfig, $queryText, $boost)
213 | {
214 | $relevanceConfig = $containerConfig->getRelevanceConfig();
215 |
216 | $analyzer = FieldInterface::ANALYZER_WHITESPACE;
217 | if (is_string($queryText) && str_word_count($queryText) > 1) {
218 | $analyzer = FieldInterface::ANALYZER_SHINGLE;
219 | }
220 |
221 | $defaultSearchField = MappingInterface::DEFAULT_SEARCH_FIELD;
222 | $searchableFieldFilter = $this->fieldFilters['searchableFieldFilter'];
223 |
224 | $searchFields = $this->getWeightedFields(
225 | $containerConfig,
226 | $analyzer,
227 | $searchableFieldFilter,
228 | $defaultSearchField
229 | );
230 |
231 | $queryParams = [
232 | 'fields' => $searchFields,
233 | 'queryText' => $queryText,
234 | 'minimumShouldMatch' => "100%",
235 | 'tieBreaker' => $relevanceConfig->getTieBreaker(),
236 | 'boost' => $boost,
237 | ];
238 |
239 | return $this->queryFactory->create(QueryInterface::TYPE_MULTIMATCH, $queryParams);
240 | }
241 |
242 | /**
243 | * Spellcheked query building.
244 | *
245 | * @param ContainerConfigurationInterface $containerConfig Search request container configuration.
246 | * @param string $queryText The text query.
247 | * @param string $spellingType The type of spellchecked applied.
248 | * @param float $boost Boost of the created query.
249 | *
250 | * @return QueryInterface
251 | */
252 | private function getSpellcheckedQuery(
253 | ContainerConfigurationInterface $containerConfig,
254 | $queryText,
255 | $spellingType,
256 | $boost
257 | ) {
258 | $query = null;
259 |
260 | $relevanceConfig = $containerConfig->getRelevanceConfig();
261 | $queryClauses = [];
262 |
263 | if ($relevanceConfig->isFuzzinessEnabled()) {
264 | $queryClauses[] = $this->getFuzzyQuery($containerConfig, $queryText);
265 | }
266 |
267 | if ($relevanceConfig->isPhoneticSearchEnabled()) {
268 | $queryClauses[] = $this->getPhoneticQuery($containerConfig, $queryText);
269 | }
270 |
271 | if (!empty($queryClauses)) {
272 | $queryParams = ['should' => $queryClauses, 'boost' => $boost];
273 |
274 | if ($spellingType == SpellcheckerInterface::SPELLING_TYPE_MOST_FUZZY) {
275 | $queryParams['must'] = [$this->getWeightedSearchQuery($containerConfig, $queryText)];
276 | }
277 |
278 | $query = $this->queryFactory->create(QueryInterface::TYPE_BOOL, $queryParams);
279 | }
280 |
281 | return $query;
282 | }
283 |
284 | /**
285 | * Fuzzy query part.
286 | *
287 | * @param ContainerConfigurationInterface $containerConfig Search request container configuration.
288 | * @param string $queryText The text query.
289 | *
290 | * @return QueryInterface
291 | */
292 | private function getFuzzyQuery(ContainerConfigurationInterface $containerConfig, $queryText)
293 | {
294 | $relevanceConfig = $containerConfig->getRelevanceConfig();
295 | $phraseMatchBoost = $relevanceConfig->getPhraseMatchBoost();
296 |
297 | $defaultSearchField = MappingInterface::DEFAULT_SPELLING_FIELD;
298 |
299 | $standardAnalyzer = FieldInterface::ANALYZER_WHITESPACE;
300 | $phraseAnalyzer = FieldInterface::ANALYZER_WHITESPACE;
301 | if (is_string($queryText) && str_word_count($queryText) > 1) {
302 | $phraseAnalyzer = FieldInterface::ANALYZER_SHINGLE;
303 | }
304 |
305 | $fuzzyFieldFilter = $this->fieldFilters['fuzzyFieldFilter'];
306 |
307 | $searchFields = array_merge(
308 | $this->getWeightedFields($containerConfig, $standardAnalyzer, $fuzzyFieldFilter, $defaultSearchField),
309 | $this->getWeightedFields(
310 | $containerConfig,
311 | $phraseAnalyzer,
312 | $fuzzyFieldFilter,
313 | $defaultSearchField,
314 | $phraseMatchBoost
315 | )
316 | );
317 |
318 | $queryParams = [
319 | 'fields' => $searchFields,
320 | 'queryText' => $queryText,
321 | 'minimumShouldMatch' => "100%",
322 | 'tieBreaker' => $relevanceConfig->getTieBreaker(),
323 | 'fuzzinessConfig' => $relevanceConfig->getFuzzinessConfiguration(),
324 | 'cutoffFrequency' => $relevanceConfig->getCutoffFrequency(),
325 | ];
326 |
327 | return $this->queryFactory->create(QueryInterface::TYPE_MULTIMATCH, $queryParams);
328 | }
329 |
330 | /**
331 | * Phonentic query part.
332 | *
333 | * @param ContainerConfigurationInterface $containerConfig Search request container configuration.
334 | * @param string $queryText The text query.
335 | *
336 | * @return QueryInterface
337 | */
338 | private function getPhoneticQuery(ContainerConfigurationInterface $containerConfig, $queryText)
339 | {
340 | $relevanceConfig = $containerConfig->getRelevanceConfig();
341 | $analyzer = FieldInterface::ANALYZER_PHONETIC;
342 | $defaultSearchField = MappingInterface::DEFAULT_SPELLING_FIELD;
343 | $fuzzyFieldFilter = $this->fieldFilters['fuzzyFieldFilter'];
344 |
345 | $searchFields = $this->getWeightedFields($containerConfig, $analyzer, $fuzzyFieldFilter, $defaultSearchField);
346 |
347 | $queryParams = [
348 | 'fields' => $searchFields,
349 | 'queryText' => $queryText,
350 | 'minimumShouldMatch' => "100%",
351 | 'tieBreaker' => $relevanceConfig->getTieBreaker(),
352 | 'cutoffFrequency' => $relevanceConfig->getCutoffFrequency(),
353 | ];
354 |
355 | return $this->queryFactory->create(QueryInterface::TYPE_MULTIMATCH, $queryParams);
356 | }
357 |
358 | /**
359 | * Build an array of weighted fields to be searched with the ability to apply a filter callback method and a default field.
360 | *
361 | * @param ContainerConfigurationInterface $containerConfig Search request container config.
362 | * @param null $analyzer Target analyzer.
363 | * @param FieldFilterInterface|null $fieldFilter Field filter.
364 | * @param string|null $defaultField Default search field.
365 | * @param integer $boost Additional boost applied to the fields (multiplicative).
366 | *
367 | * @return array
368 | */
369 | private function getWeightedFields(
370 | ContainerConfigurationInterface $containerConfig,
371 | $analyzer = null,
372 | FieldFilterInterface $fieldFilter = null,
373 | $defaultField = null,
374 | $boost = 1
375 | ) {
376 |
377 | $mapping = $containerConfig->getMapping();
378 |
379 | return $mapping->getWeightedSearchProperties($analyzer, $defaultField, $boost, $fieldFilter);
380 | }
381 | }
382 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/Provider/Config.php:
--------------------------------------------------------------------------------
1 |
17 | * @copyright 2021 Web200
18 | * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
19 | * @link https://www.web200.fr/
20 | */
21 | class Config
22 | {
23 | public const CATEGORY_ACTIVE = 'smile_elasticsuite_autocomplete_settings/category_autocomplete/is_active';
24 | public const PRODUCT_ACTIVE = 'smile_elasticsuite_autocomplete_settings/product_autocomplete/is_active';
25 | public const IMPROVE_SEARCH_ACTIVE = 'smile_elasticsuite_autocomplete_settings/product_autocomplete/improve_search_active';
26 | public const PRODUCT_MAX_SIZE = 'smile_elasticsuite_autocomplete_settings/product_autocomplete/max_size';
27 | public const CATEGORY_MAX_SIZE = 'smile_elasticsuite_autocomplete_settings/category_autocomplete/max_size';
28 | /**
29 | * Scope config interface
30 | *
31 | * @var ScopeConfigInterface $scopeConfig
32 | */
33 | protected $scopeConfig;
34 |
35 | /**
36 | * Query constructor.
37 | *
38 | * @param ScopeConfigInterface $scopeConfig
39 | */
40 | public function __construct(
41 | ScopeConfigInterface $scopeConfig
42 | ) {
43 | $this->scopeConfig = $scopeConfig;
44 | }
45 |
46 | /**
47 | * Is category active
48 | *
49 | * @param null $store
50 | *
51 | * @return bool
52 | */
53 | public function isCategoryActive($store = null): bool
54 | {
55 | return (bool)$this->scopeConfig->getValue(
56 | self::CATEGORY_ACTIVE,
57 | ScopeInterface::SCOPE_STORES,
58 | $store
59 | );
60 | }
61 |
62 | /**
63 | * Is product active
64 | *
65 | * @param null $store
66 | *
67 | * @return bool
68 | */
69 | public function isProductActive($store = null): bool
70 | {
71 | return (bool)$this->scopeConfig->getValue(
72 | self::PRODUCT_ACTIVE,
73 | ScopeInterface::SCOPE_STORES,
74 | $store
75 | );
76 | }
77 |
78 | /**
79 | * Is improve search active
80 | *
81 | * @param null $store
82 | *
83 | * @return bool
84 | */
85 | public function isImproveSearchActive($store = null): bool
86 | {
87 | return (bool)$this->scopeConfig->getValue(
88 | self::IMPROVE_SEARCH_ACTIVE,
89 | ScopeInterface::SCOPE_STORES,
90 | $store
91 | );
92 | }
93 |
94 | /**
95 | * Get product autocomplete max size
96 | *
97 | * @param null $store
98 | *
99 | * @return int|null
100 | */
101 | public function getProductAutocompleteMaxSize($store = null): ?int
102 | {
103 | return (int)$this->scopeConfig->getValue(
104 | self::PRODUCT_MAX_SIZE,
105 | ScopeInterface::SCOPE_STORES,
106 | $store
107 | );
108 | }
109 |
110 | /**
111 | * Get category autocomplete max size
112 | *
113 | * @param null $store
114 | *
115 | * @return int|null
116 | */
117 | public function getCategoryAutocompleteMaxSize($store = null): ?int
118 | {
119 | return (int)$this->scopeConfig->getValue(
120 | self::CATEGORY_MAX_SIZE,
121 | ScopeInterface::SCOPE_STORES,
122 | $store
123 | );
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/ViewModel/Form.php:
--------------------------------------------------------------------------------
1 | storeManager = $storeManager;
54 | $this->urlBuilder = $urlBuilder;
55 | $this->scopeConfig = $scopeConfig;
56 | }
57 |
58 | /**
59 | * Get search url
60 | *
61 | * @return string
62 | * @throws NoSuchEntityException
63 | */
64 | public function getSearchUrl(): string
65 | {
66 | $useStoreInUrl = (bool)$this->scopeConfig->getValue(Store::XML_PATH_STORE_IN_URL, ScopeInterface::SCOPE_STORE);
67 | if (!$useStoreInUrl) {
68 | return $this->urlBuilder->getUrl('', ['_direct' => 'search.php']);
69 | }
70 |
71 | return '/search.php?store_code=' . $this->storeManager->getStore()->getCode();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web200/magento-elasticsuite-autocomplete",
3 | "description": "Magento 2 Module to speed autocomplete search",
4 | "require": {
5 | "php": "^7.0"
6 | },
7 | "type": "magento2-module",
8 | "license": [
9 | "OSL-3.0",
10 | "AFL-3.0"
11 | ],
12 | "autoload": {
13 | "files": [
14 | "registration.php"
15 | ],
16 | "psr-4": {
17 | "Web200\\ElasticsuiteAutocomplete\\": ""
18 | }
19 | },
20 | "extra": {
21 | "map": [
22 | [
23 | "pub/search.php",
24 | "pub/search.php"
25 | ]
26 | ]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/etc/adminhtml/system.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Magento\Config\Model\Config\Source\Yesno
10 |
11 |
12 |
13 | Magento\Config\Model\Config\Source\Yesno
14 | Add prefix and fuzziness search for queries.
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Magento\Config\Model\Config\Source\Yesno
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/etc/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 1
7 |
8 |
9 | 1
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/etc/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | -
7 |
- Web200\ElasticsuiteAutocomplete\Model\Indexer\Product\Fulltext\Datasource\Url
8 | - Web200\ElasticsuiteAutocomplete\Model\Indexer\Product\Fulltext\Datasource\ConfigurablePrice
9 | - Web200\ElasticsuiteAutocomplete\Model\Indexer\Product\Fulltext\Datasource\AdditionalAttributes
10 |
11 | -
12 |
- Web200\ElasticsuiteAutocomplete\Model\Indexer\Category\Fulltext\Datasource\Url
13 | - Web200\ElasticsuiteAutocomplete\Model\Indexer\Category\Fulltext\Datasource\Breadcrumbs
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | - Smile\ElasticsuiteCore\Search\Request\Query\Fulltext\SearchableFieldFilter
27 | - Smile\ElasticsuiteCore\Search\Request\Query\Fulltext\FuzzyFieldFilter
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/etc/frontend/routes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/etc/module.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/module-elasticsuite-autocomplete/registration.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Web200\ElasticsuiteAutocomplete\ViewModel\Form
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/pub/search.php:
--------------------------------------------------------------------------------
1 | [DirectoryList::URL_PATH => ''],
14 | DirectoryList::MEDIA => [DirectoryList::URL_PATH => 'media'],
15 | DirectoryList::STATIC_VIEW => [DirectoryList::URL_PATH => 'static'],
16 | DirectoryList::UPLOAD => [DirectoryList::URL_PATH => 'media/upload'],
17 | ]
18 | );
19 |
20 | $params[StoreManager::PARAM_RUN_CODE] = $_SERVER[StoreManager::PARAM_RUN_CODE] ?? null;
21 | if (isset($_REQUEST['store_code'])) {
22 | $params[StoreManager::PARAM_RUN_CODE] = $_REQUEST['store_code'];
23 | }
24 | $bootstrap = Bootstrap::create(BP, $params);
25 |
26 | $obj = $bootstrap->getObjectManager();
27 | $obj->get('Magento\Framework\App\State')->setAreaCode('frontend');
28 | $obj->get('Magento\Framework\View\DesignLoader')->load(Area::PART_DESIGN);
29 |
30 | header('Content-Type: application/json');
31 | echo json_encode($obj->get('Web200\ElasticsuiteAutocomplete\Model\Query')->execute());
32 |
--------------------------------------------------------------------------------