self::INDEX_PRODUCTS, FulltextElastic::INDEX_SECTIONS => self::INDEX_SECTIONS, FulltextElastic::INDEX_PRODUCERS => self::INDEX_PRODUCERS, FulltextElastic::INDEX_ARTICLES => self::INDEX_ARTICLES, FulltextElastic::INDEX_PAGES => self::INDEX_PAGES, ]; protected int $curlTimeout = 60; protected int $totalProductsCount; protected array $filters = []; protected FulltextElastic $elastic; protected array $dynamicFilters = []; protected SearchRequestInterface $searchRequest; protected ConfigProvider $configProvider; public function setCurlTimeout($timeout): FulltextInterface { $this->curlTimeout = $timeout; return $this; } public function setDynamicFilters(array $filters): void { $this->dynamicFilters = $filters; } public function search(string $term, array $config, array $types = []): array { $types ??= $this->getIndexTypes(); $mainConfig = $config[FulltextElastic::INDEX_PRODUCTS]; $page = $mainConfig['offset'] / $mainConfig['count'] + 1; $urlBuilder = new SearchUrlBuilder($page); $urlBuilder ->setQuery($term) ->setSize(min($mainConfig['count'], 200)) ->setQuicksearchTypes($this->getLBTypes(array_filter($types, fn ($x) => $x != FulltextElastic::INDEX_PRODUCTS))) ->setDynamicFacetsSize(3) ->setFacets(['price_amount', 'category', 'brand']) ->addFilter('type', 'item'); $this->addDynamicFilters($urlBuilder); $searchResponse = $this->searchRequest->search($urlBuilder); $this->filters = $this->transformFacets($searchResponse->getFacets()); return $this->processSearchResults($searchResponse); } protected function addDynamicFilters(SearchUrlBuilder $urlBuilder) { foreach ($this->formatDynamicFilters() as $name => $filter) { switch ($filter['type'] ?? '') { case 'float': $urlBuilder->addFilter($name, ($filter['values']['min'] ?? '').'|'.($filter['values']['max'] ?? '')); break; case 'text': foreach ($filter['values'] ?? [] as $value) { $urlBuilder->addFilter($name, $value); } break; } } } protected function formatDynamicFilters(): array { $result = []; foreach ($this->dynamicFilters['parameters'] ?? [] as $name => $filter) { switch ($filter['type'] ?? '') { case 'float': $matches = []; if (preg_match("/^\s?(\d*\.?\d*)(\*?)\ ?\-\ ?(\d*\.?\d*)(\*?)\s?$/", $filter['value'], $matches)) { $result[$name] = [ 'type' => $filter['type'], 'values' => [ 'min' => $matches[1], 'max' => $matches[3], ], ]; } break; default: case 'text': $result[$name] = [ 'type' => $filter['type'], 'values' => $filter['value'], ]; break; } } return $result; } protected function getLBTypes($types): array { $types = array_filter($types, fn ($x) => array_key_exists($x, self::LB_TYPES)); return array_map(fn ($x) => self::LB_TYPES[$x], $types); } protected function processSearchResults(SearchResponse $response) { $result = []; $this->totalProductsCount = $response->getTotalHits(); $result[FulltextElastic::INDEX_PRODUCTS] = Mapping::mapKeys($response->getHits(), function ($index, Hit $x) { $hit = $this->processHit($x); return [$hit['id'], $hit]; }); $reverseTypes = array_flip(self::LB_TYPES); foreach ($response->getQuickSearchHits() as $hit) { $result[$reverseTypes[$hit->getType()]][] = $this->processHit($hit); } return $result; } protected function processHit(Hit $hit): array { $result = $hit->getAttributes(); if (!isset($result['id']) && isset($result['identity'])) { $result['id'] = $result['identity']; } if (!isset($result['id']) && isset($result['original_url'])) { $result['id'] = $result['original_url']; } if (is_array($result['id'])) { $result['id'] = reset($result['id']); } switch ($hit->getType()) { case self::INDEX_PRODUCTS: $result['id'] = explode('_', $result['id'])[0]; break; case self::INDEX_PAGES: case self::INDEX_PRODUCERS: $result['name'] = $result['title']; break; case self::INDEX_SECTIONS: $result['name'] = $result['title']; $result['photo'] = getImage($result['id'], null, null, 'section', 1); $result['photo_src'] = $result['image_link'] ?? ''; // no break default: } return $result; } public function searchProducts($term, $count, $offset, $order = null, $filter = '') { return $this->search($term, [])[FulltextElastic::INDEX_PRODUCTS]; } public function searchProductsExact($term, $count, $offset, $order = null, $filter = '') { // TODO: Implement searchProductsExact() method. } public function getRowsCount() { return $this->totalProductsCount; } public function suggestTerm($term) { return null; } public function updateProduct($id_product) { return $this->elastic->updateProduct($id_product); } public function loadSynonyms(): array { return []; } public function loadSynonymsFromIndex(): ?array { return null; } public function updateSynonyms(array $synonyms, bool $merge = false): void { // TODO: Implement updateSynonyms() method. } public function saveSynonyms(array $synonyms): void { // TODO: Implement saveSynonyms() method. } public function updateIndex(string $type = 'all', $clean = true, array $exceptTypes = []): void { $this->elastic->updateIndex($type); } /** * @required */ public function setElastic(FulltextElastic $elastic): void { $this->elastic = $elastic; } public function getIndexTypes(): array { return $this->elastic->getIndexTypes(); } /** @required */ public function setSearchRequest(SearchRequestInterface $searchRequest): void { $this->searchRequest = $searchRequest; } /** @required */ public function setConfigProvider(ConfigProvider $configProvider): void { $dbcfg = \Settings::getDefault(); if (($dbcfg->analytics['luigis_box']['id'] ?? false) && ($dbcfg->analytics['luigis_box']['key'] ?? false)) { $configProvider->addConfig('kupshop', new ConfigDTO($dbcfg->analytics['luigis_box']['id'], $dbcfg->analytics['luigis_box']['key'])); $configProvider->setConfig('kupshop'); } $this->configProvider = $configProvider; } public function getFilters(): array { return $this->filters; } /** * @param Facet[] $facets * * @return array */ protected function transformFacets(array $facets) { $filters = []; $dynamic = $this->formatDynamicFilters(); foreach ($facets as $facet) { if (in_array($facet->getName(), ['pcs_store', 'identity', 'Index ziskovosti', 'labels', 'Značka'])) { continue; } switch ($facet->getType()) { case 'float': $values = $facet->getValues(); $getRange = fn ($x) => $x ? explode('|', $x->getValue()) : [0, 0]; $filters[] = [ 'id' => $facet->getName(), 'type' => 'float', 'value_type' => 'float', 'name' => $this->getFilterName($facet->getName()), 'values' => [ 'min' => $getRange($values[0] ?? 0)[0], 'max' => $getRange($values[count($values) - 1] ?? 0)[1], ], 'active_filter' => $dynamic[$facet->getName()] ?? [], ]; break; case 'text': $values = []; foreach ($facet->getValues() as $val) { $values[$val->getValue()] = [ 'id' => $val->getValue(), 'name' => $val->getValue(), 'count' => $val->getHitsCount(), ]; } $filters[] = [ 'id' => $facet->getName(), 'type' => 'text', 'value_type' => 'text', 'name' => $this->getFilterName($facet->getName()), 'values' => $values, 'active_filter' => $dynamic[$facet->getName()] ?? [], ]; break; } } return $filters; } protected function getFilterName($name): string { return match ($name) { 'price_amount' => translate('price', 'lbx_filter_name'), 'category' => translate('category', 'lbx_filter_name'), 'brand' => translate('producer', 'lbx_filter_name'), default => $name, }; } public function getFulltextLanguages(): array { $languages = []; foreach (Contexts::get(LanguageContext::class)->getAll() as $language) { if (!$language->isActive()) { continue; } $languages[$language->getId()] = $language; } return $languages; } public function supportsFilters(): bool { return true; } }