Your IP : 127.0.0.1


Current Path : /home/dev2.destoffenstraat.com/app/code/Mirasvit/SearchElastic/InstantProvider/
Upload File :
Current File : /home/dev2.destoffenstraat.com/app/code/Mirasvit/SearchElastic/InstantProvider/EngineProvider.php

<?php
/**
 * Mirasvit
 *
 * This source file is subject to the Mirasvit Software License, which is available at https://mirasvit.com/license/.
 * Do not edit or add to this file if you wish to upgrade the to newer versions in the future.
 * If you wish to customize this module for your needs.
 * Please refer to http://www.magentocommerce.com for more information.
 *
 * @category  Mirasvit
 * @package   mirasvit/module-search-ultimate
 * @version   2.3.2
 * @copyright Copyright (C) 2024 Mirasvit (https://mirasvit.com/)
 */


declare(strict_types=1);

namespace Mirasvit\SearchElastic\InstantProvider;

use Mirasvit\Search\Model\ConfigProvider;
use Mirasvit\SearchAutocomplete\InstantProvider\InstantProvider;
use Mirasvit\SearchElastic\SearchAdapter\QueryBuilder;

class  EngineProvider extends InstantProvider
{
    private $query          = [];

    private $activeFilters  = [];

    private $applyFilter    = false;

    private $filtersToApply = [];

    private $buckets        = [];

    public function getResults(string $indexIdentifier): array
    {
        $queryBuilder = new QueryBuilder($this->queryService);

        $storeId = $this->getStoreId();
        $groupId = $this->getCustomerGroupId();

        $this->query = [
            'index'            => $this->configProvider->getIndexName($indexIdentifier),
            'body'             => [
                'from'          => $this->getFrom($indexIdentifier),
                'size'          => $this->getLimit($indexIdentifier),
                'stored_fields' => [
                    '_id',
                    '_score',
                    '_source',
                ],
                'sort'          => [
                    [
                        '_score' => [
                            'order' => 'desc',
                        ],
                    ],
                ],
                'query'         => [
                    'bool' => [
                        'minimum_should_match' => 0,
                    ],
                ],
            ],
            'track_total_hits' => true,
        ];

        $fields = $this->configProvider->getIndexFields($indexIdentifier);

        $fields[ConfigProvider::MISC_FIELD] = 1;

        $this->query['body']['query'] = $queryBuilder->build($this->query['body']['query'], $this->getQueryText(), $fields);

        $this->setMustCondition($indexIdentifier);

        if ($indexIdentifier === 'magento_catalog_product') {
            $this->setBuckets();

            if ($this->getCategoryId()) {
                $this->query['body']['query']['bool']['must'][] = [
                    'term' => [
                        'category_ids' => $this->getCategoryId(),
                    ],
                ];
            }

            if ($this->configProvider->isCatalogPermissionsFeatureEnabled()) {
                $this->query['body']['query']['bool']['must_not'][] = [
                    'term' => [
                        'category_permission_' . $storeId . '_' . $groupId => -2,
                    ],
                ];
            }

            $this->query['body']['query']['script_score']['query']  = $this->query['body']['query'];
            $this->query['body']['query']['script_score']['script'] = [
                'source' => ConfigProvider::DEFAULT_SCORE . " + _score * doc['" . ConfigProvider::MULTIPLY_ATTRIBUTE . "'].value + doc['" . ConfigProvider::SUM_ATTRIBUTE . "'].value",
            ];

            if ($this->configProvider->isExcludeOutOfStock()) {
                if (!empty($this->query['body']['query']['script_score']['query']['bool'])
                    && isset($this->query['body']['query']['script_score']['query']['bool']['must'])) {
                    $this->query['body']['query']['script_score']['query']['bool']['must'][] = [
                        'term' => [
                            'is_out_of_stock'  => 1,
                        ]
                    ];
                }
            }

            unset($this->query['body']['query']['bool']);
        }

        if ($indexIdentifier === 'magento_catalog_category' && $this->configProvider->isCatalogPermissionsFeatureEnabled()) {
            $this->query['body']['query']['bool']['must_not'][] = [
                'term' => [
                    'grant_catalog_category_view_'. $storeId . '_' . $groupId => -2,
                ],
            ];
        }

        try {
            $rawResponse = $this->getClient()->search($this->query);
        } catch (\Exception $e) {
            $correctedQuery = $this->suggest();
            if ($correctedQuery && $correctedQuery != $this->getQueryText()) {
                $this->setQueryText($correctedQuery);

                return $this->getResults($indexIdentifier);
            } else {
                return [
                    'totalItems' => 0,
                    'items'      => [],
                    'buckets'    => [],
                ];
            }
        }

        if ($this->isDebug()) {
            print_r('<pre>');
            print_r($this->query);
            print_r($rawResponse);
            // @codingStandardsIgnoreStart
            die();
            // @codingStandardsIgnoreEnd
        }

        if ($this->configProvider->getEngine() == 'elasticsearch6') {
            $totalItems = (int)$rawResponse['hits']['total'];
        } else {
            $totalItems = (int)$rawResponse['hits']['total']['value'];
        }

        $correctedQuery = $this->suggest();
        if ($totalItems < 1 && $correctedQuery && $correctedQuery != $this->getQueryText()) {
            $this->setQueryText($correctedQuery);

            return $this->getResults($indexIdentifier);
        }

        $items   = [];

        foreach ($rawResponse['hits']['hits'] as $data) {
            if (!isset($data['_source']['_instant'])) {
                continue;
            }

            $items[] = $data['_source']['_instant'];
        }

        $buckets = [];


        if (isset($rawResponse['aggregations'])) {
            foreach ($this->configProvider->getBuckets() as $code => $bucket) {
                if (!isset($rawResponse['aggregations'][$code])) {
                    continue;
                }
                $data = $rawResponse['aggregations'][$code];

                if ($code == 'price') {
                    $bucketData = $this->configProvider->getBucketOptionsData($code, []);

                    $bucketData['min']   = $data['min'];
                    $bucketData['max']   = $data['max'];
                    $bucketData['items'] = [];

                    $buckets[$code]       = $bucketData;
                    $this->buckets[$code] = $bucketData;
                } else {
                    $bucketData = $this->configProvider->getBucketOptionsData($code, $data['buckets']);
                    if (empty($bucketData)) {
                        continue;
                    }
                    if (in_array($code, $this->filtersToApply)) {
                        continue;
                    }

                    $buckets[$code]       = $bucketData;
                    $this->buckets[$code] = $bucketData;
                }
            }


            if (!empty($this->filtersToApply)) {
                foreach (array_diff(array_keys($this->buckets), array_keys($buckets)) as $bucketCode) {
                    if (!in_array($bucketCode, $this->filtersToApply)) {
                        unset($this->buckets[$bucketCode]);
                    }
                }
            } else {
                $this->buckets = $buckets;
            }
        }

        if (count($this->getActiveFilters()) > 0 && !$this->applyFilter) {
            $this->applyFilter = true;

            foreach ($this->getActiveFilters() as $filterKey => $value) {
                $this->filtersToApply[] = $filterKey;

                $result  = $this->getResults($indexIdentifier);
                $buckets = $this->prepareBuckets($buckets);
                foreach ($result['buckets'] as $bucketKey => $bucket) {
                    if (in_array($bucketKey, $this->filtersToApply)) {
                        continue;
                    }

                    $this->buckets[$bucketKey] = $bucket;
                }

                $totalItems = $result['totalItems'];
                $items      = $result['items'];
            }
        }

        foreach ($items as $key => $item) {
            if (isset($item['name'])) {
                $items[$key]['name'] = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $item['name']);
            }
        }

        return [
            'totalItems' => count($items) > 0 ? $totalItems : 0,
            'items'      => $items,
            'buckets'    => $this->buckets,
        ];
    }

    public function suggest(): ?string
    {
        if (!in_array('mst_misspell_index', $this->configProvider->getIndexes())) {
            return null;
        }

        $query    = preg_split('/[\s]+/', $this->getQueryText());
        $response = [];

        if (!is_array($query)) {
            $query = [$query];
        }

        try {
            foreach ($query as $term) {
                $result = $this->getClient()->search($this->prepareTermSuggestQuery($term));
                if (is_object($result)) { // ES8
                    $result = $result->asArray();
                }
                $processedResponse = $this->processResponse($result);

                if (empty($processedResponse)) {
                    $result = $this->getClient()->search($this->preparePhraseSuggestQuery($term));
                    if (is_object($result)) { // ES8
                        $result = $result->asArray();
                    }
                    $processedResponse = $this->processResponse($result);
                }

                $response[] = $processedResponse;
            }
        } catch (\Exception $e) {
        }

        $response = array_filter($response);
        $response = array_unique($response);

        if (empty($response)) {
            return null;
        }

        return implode(' ', $response);
    }

    private function getActiveFilters(): array
    {
        if (empty($this->activeFilters)) {
            $this->activeFilters = $this->configProvider->getActiveFilters();
        }

        if (!empty($this->filtersToApply)) {
            return array_intersect_key($this->activeFilters, array_flip($this->filtersToApply));
        }

        return $this->activeFilters;
    }

    private function setMustCondition(string $indexIdentifier): void
    {
        if ($indexIdentifier === 'magento_catalog_product') {
            $this->query['body']['query']['bool']['must'][] = [
                'terms' => [
                    'visibility' => ['3', '4'],
                ],
            ];

            if ($this->applyFilter) {
                foreach ($this->getActiveFilters() as $filterCode => $filterValue) {
                    if ($filterCode == 'price') {
                        $priceFilter = [];
                        foreach ($filterValue as $value) {
                            [$from, $to] = explode('_', $value);
                            $priceFilter['bool']['should'][] = [
                                'range' => [
                                    'price_0_1' => [
                                        'gte' => $from,
                                        'lte' => $to,
                                    ],
                                ],
                            ];
                        }

                        $this->query['body']['query']['bool']['must'] = array_merge($this->query['body']['query']['bool']['must'], [$priceFilter]);
                    } else {
                        $termStatement = is_array($filterValue) ? 'terms' : 'term';

                        $this->query['body']['query']['bool']['must'][] = [
                            $termStatement => [
                                $filterCode => $filterValue,
                            ],
                        ];
                    }
                }
            }
        }
    }

    private function setBuckets(): void
    {
        foreach ($this->getBuckets() as $fieldName) {
            if ($fieldName == 'price') {
                $this->query['body']['aggregations'][$fieldName] = ['extended_stats' => ['field' => 'price_0_1']];
            } else {
                $this->query['body']['aggregations'][$fieldName] = ['terms' => ['field' => $fieldName, 'size' => 500]];
            }
        }
    }

    private function prepareBuckets($buckets): array
    {
        foreach ($buckets as $key => $bucket) {
            if ($key == 'price') {
                continue;
            }

            foreach ($bucket['items'] as $optionKey => $option) {
                $buckets[$key]['items'][$optionKey]['count'] = 0;
            }
        }

        return $buckets;
    }

    private function getClient()
    {
        $esConfig = $this->configProvider->getEngineConnection();

        if (class_exists('Elastic\Elasticsearch\ClientBuilder')) { // ES8
            return \Elastic\Elasticsearch\ClientBuilder::fromConfig($esConfig, true);
        }

        return \Elasticsearch\ClientBuilder::fromConfig($esConfig, true);
    }

    private function prepareTermSuggestQuery(string $query): array
    {
        return [
            'index' => $this->getIndexName(),
            'body'  => [
                'suggest' => [
                    'suggestion' => [
                        'text' => $query,
                        'term' => [
                            'field'         => 'keyword',
                            'size'          => 1,
                            'prefix_length' => 0,
                        ],
                    ],
                ],
            ],
        ];
    }

    private function preparePhraseSuggestQuery(string $query): array
    {
        return [
            'index' => $this->getIndexName(),
            'body'  => [
                'suggest' => [
                    'text'       => $query,
                    'suggestion' => [
                        'phrase' => [
                            'field'            => 'keyword.trigram',
                            'size'             => 1,
                            'gram_size'        => 3,
                            'max_errors'       => 100,
                            'direct_generator' => [
                                [
                                    'field'        => 'keyword.trigram',
                                    'suggest_mode' => 'always',
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ];
    }

    private function getIndexName(): string
    {
        return $this->configProvider->getIndexName('mst_misspell_index');
    }

    private function processResponse(array $response): ?string
    {
        $result = null;
        if (isset($response['suggest']['suggestion'][0]['options'][0]['text'])) {
            $result = $response['suggest']['suggestion'][0]['options'][0]['text'];
        } else {
            if (isset($response['suggest']['suggestion'][0]['text'])) {
                $result = $response['suggest']['suggestion'][0]['text'];
            }
        }

        return $result;
    }
}