query = new \Query(); } public function handleAutocomplete() { $type = getVal('type'); // Remove '-' and '_', Capitalize each word $type = strtr(ucwords(strtr($type, ['_' => ' ', '-' => ' '])), [' ' => '']); if (empty($type)) { return new JsonResponse(); } $search = $this->prepareSearch(getVal('term')); $this->query->limit = getVal('limit', 10) ?: 100; $result = $this->handle($type, $search); return new JsonResponse($result); } public function handle($type, $search) { // Store search term to query data $this->query->data['search'] = $search; $this->query->data['search_both'] = "%{$search}%"; $this->query->data['search_left'] = "{$search}%"; if (method_exists($this, 'handle'.ucfirst($type))) { return call_user_func([$this, 'handle'.ucfirst($type)]); } return null; } public function handleProduct() { $this->query->fields = 'title as label'; $this->query->from = 'products p'; $this->query->where = 'p.figure="Y" AND p.title LIKE :search_both'; $this->query->order = 'p.title'; return $this->getQueryData(); } public function handleProductCategories() { return $this->handleProductCategoriesFulltext(); } public function handleProductCategoriesFulltext() { // Tady si schválně říkám o FulltextElastic a ne Interface, protože nechci aby mi přišel LuigisBox. Ten nepoužívá naše autocomplete $fulltext = ServiceContainer::getService(FulltextElastic::class); $fulltext->setCurlTimeout(10); $term = $this->query->data['search']; $enabledTopics = $fulltext->getIndexTypes(); if ($disabled_topics = Config::get()['Modules'][\Modules::SEARCH]['disabled_topics'] ?? null) { $enabledTopics = array_values(array_diff($enabledTopics, $disabled_topics)); } $multiSearchResult = $fulltext->search( $term, $this->getFulltextSearchConfig($enabledTopics), $enabledTopics ); return $this->processFulltextResults($multiSearchResult, $fulltext->multiSearchResultTotals); } public function handleDeliveryInPersonSellers(): array { if (!findModule(\Modules::SELLERS)) { return []; } $this->query->fields = 'id, psc as value, title as name, CONCAT_WS(", ", psc, city, street, number) as address'; $this->query->from = 'sellers'; $search = get_search_query($this->query->data['search'], [ ['field' => 'title', 'match' => 'both'], ['field' => 'psc', 'match' => 'both'], ['field' => 'city', 'match' => 'both'], ['field' => 'street', 'match' => 'both'], ]); $this->query->data = $search['data']; $this->query->where = $search['where']; return $this->getQueryData(); } public function handleDeliveryBalikDoRuky() { $this->query->fields = 'psc as value, name, region, time_zone, address'; $this->query->order = 'psc'; $this->query->from = '`kupshop_shared`.`delivery_balik_na_postu`'; $this->query->where = 'name LIKE :search_left OR psc LIKE :search_left'; return $this->getQueryData(); } public function handleDeliveryBalikovna() { $this->query->fields = 'zip as value, name, city, city_part, address'; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_balikovna`'; $this->query->where = 'name LIKE :search_left OR city LIKE :search_left OR city_part LIKE :search_left OR address LIKE :search_both OR zip LIKE :search_left'; return $this->getQueryData(); } public function handleDeliveryZasilkovna() { $this->query->fields = 'zip, name, hours, labelRouting, id as value'; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_zasilkovna`'; $this->query->where = '(name LIKE :search_both OR zip LIKE :search_both)'; $onlyVisible = getVal('onlyVisible', null, 'Y'); if ($onlyVisible == 'Y') { $this->query->where .= " AND visible = 'Y' "; } $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } if ($currency = getVal('currency')) { $currencyCode = $currency; } else { $currencyContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\CurrencyContext::class); $currencyCode = $currencyContext->getActiveId(); } if (!empty($currencyCode)) { switch ($currencyCode) { case 'CZK': $this->query->where .= ' AND country=\'cz\''; break; case 'EUR': $this->query->where .= ' AND country=\'sk\''; break; case 'HUF': $this->query->where .= ' AND country=\'hu\''; break; case 'RON': $this->query->where .= ' AND country=\'ro\''; break; case 'PLN': $this->query->where .= ' AND country=\'pl\''; break; } } return $this->getQueryData(); } public function handleDeliveryPplparcelshop() { $this->query->fields = "zip, city, street, type, id as value, CONCAT(city, ', ', street, ', ', name) AS name"; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_pplparcelshop`'; $this->query->where = '(zip LIKE :search_both OR city LIKE :search_both OR street LIKE :search_both OR name LIKE :search_both)'; $countries = "'".implode("','", array_map(function (Country $c) {return $c->getId(); }, $this->countryContext->getSupported()))."'"; if ($country = getVal('country')) { $countries = "'{$country}'"; } if (!empty($countries)) { $this->query->where .= ' AND country IN ('.$countries.')'; } $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } return $this->getQueryData(); } public function handleDeliveryUlozenka() { $this->query->fields = "zip, city, street, type, id as value, CONCAT(city, ', ', street, ', ', name) AS name"; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_ulozenka`'; $this->query->where = '(zip LIKE :search_both OR city LIKE :search_both OR street LIKE :search_both OR name LIKE :search_both)'; $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } return $this->getQueryData(); } public function handleDeliveryInTime() { $this->query->fields = "zip, city, street, type, id as value, CONCAT(city, ', ', street, ', ', name) AS name"; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_intime`'; $this->query->where = '(zip LIKE :search_both OR city LIKE :search_both OR street LIKE :search_both OR name LIKE :search_both)'; $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } return $this->getQueryData(); } public function handleDeliveryDpdpickup() { $this->query->fields = 'place, zip, city, street, id as value'; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_dpdpickup`'; $this->query->where = '(LPAD(zip, 5, 0) LIKE :search_both OR city LIKE :search_both OR street LIKE :search_both OR place LIKE :search_both)'; $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } return $this->getQueryData(); } public function handleDeliverySpbalikobox() { $this->query->fields = 'zip, city, street, type, id as value, name'; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_sp_balikobox`'; $this->query->where = '(zip LIKE :search_both OR city LIKE :search_both OR street LIKE :search_both OR name LIKE :search_both)'; $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } return $this->getQueryData(); } public function handleDeliveryGlsparcelshop() { $this->query->fields = "zip, city, street, type, id as value, CONCAT(city, ', ', street, ', ', name) AS name"; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_glsparcelshop`'; $this->query->where = '(zip LIKE :search_both OR city LIKE :search_both OR street LIKE :search_both OR name LIKE :search_both)'; $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } return $this->getQueryData(); } public function handleDeliveryGeispoint() { $this->query->fields = "zip, city, street, type, id as value, CONCAT(city, ', ', street, ', ', name) AS name"; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_geispoint`'; $this->query->where = '(zip LIKE :search_both OR city LIKE :search_both OR street LIKE :search_both OR name LIKE :search_both)'; $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } return $this->getQueryData(); } public function handleDeliveryPaczkomaty() { $this->query->fields = "zip, city, street, type, id as value, CONCAT(city, ', ', street, ', ', name) AS name"; $this->query->order = 'zip'; $this->query->from = '`kupshop_shared`.`delivery_paczkomaty`'; $this->query->where = '(zip LIKE :search_both OR city LIKE :search_both OR street LIKE :search_both OR name LIKE :search_both)'; $except = getVal('except'); if (!empty($except)) { $except = array_filter( explode(',', $except), function ($id) { return (is_numeric($id)) ? true : false; } ); $this->query->where .= ' AND id NOT IN ('.implode(',', $except).')'; } return $this->getQueryData(); } public function handleParameterValues(): array { $term = $this->query->data['search'] ?? ''; $search = [$term]; if (getVal('fulltext')) { $search = array_filter(explode(' ', $term)); } $qb = sqlQueryBuilder() ->select('pl.id as value, pl.position') ->from('parameters_list', 'pl') ->andWhere(Operator::equals(['pl.id_parameter' => getVal('parameterId')])) ->andWhere( Translation::joinTranslatedFields( ParametersListTranslation::class, function (QueryBuilder $qb, $columnName, $translatedField) use ($search) { $searchField = Operator::coalesce($translatedField, "pl.{$columnName}"); $andX = []; foreach ($search as $value) { $andX[] = Operator::like([$searchField => "%{$value}%"]); } $qb->andWhere(Operator::andX($andX)); }, ['value' => 'name'] ) ) ->orderBy('position'); if ($limit = getVal('limit')) { $qb->setMaxResults((int) $limit); } return $qb->execute()->fetchAllAssociative(); } public function getQueryData() { return sqlFetchAll($this->query->execute()); } public function prepareSearch($search) { $search = trim(urldecode($search)); return $search; } protected function processProducts(array $sphinxResult) { $productList = $this->getProductList(); $sphinxIds = array_keys($sphinxResult); $productList->andSpec(function (\Query\QueryBuilder $qb) use ($sphinxIds) { $qb->setParameter('productIds', $sphinxIds, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY); return $qb->expr()->in('p.id', ':productIds'); }); $productList->orderBy('FIELD(p.id, :productIds)'); $productList->fetchProducers(); $productList->fetchImages(2); $productList->fetchSets(); $productList->fetchStoresInStore(); return array_map(function (\Product $product) { return $this->getProductResult($product); }, $productList->getProducts()->getValues()); } protected function processSections(array $sphinxResult) { foreach ($sphinxResult as &$section) { $section['label'] = $section['path'] ?? ''; } return array_values($sphinxResult); } protected function processProducers(array $sphinxResult) { foreach ($sphinxResult as &$producer) { $producer['label'] = $producer['name'] ?? ''; } return array_values($sphinxResult); } protected function processArticles(array $result) { foreach ($result as &$article) { $article['label'] = $article['title']; } return array_values($result); } protected function processPages(array $result): array { foreach ($result as &$page) { $page['label'] = $page['name']; } return array_values($result); } protected function getProductList(): \ProductList { $productList = ServiceContainer::getService(\KupShop\CatalogBundle\ProductList\ProductList::class); $productList->applyDefaultFilterParams(); return $productList; } protected function getProductResult(\Product $product): array { $productPrice = $product['productPrice']; $price = $productPrice['price_with_vat']; $priceWithoutVat = $productPrice['price_without_vat']; $productDiscountResult = $this->productDiscountCalculator->calculate( new ProductUnified($product) ); $priceOriginal = $productPrice->getOriginalPrice()->getPriceWithoutDiscount(); return [ 'id' => $product->id, 'image' => ($product->image ? $this->urlFinder->staticUrl($product->image['src']) : ''), 'label' => $product->title, 'discount' => $product->discount->asFloat(), 'price' => printPrice($price), 'price_without_vat' => printPrice($priceWithoutVat), 'price_array' => $product->price_array, 'priceOriginal' => printPrice($priceOriginal), 'annotation' => $product->descr, 'producer' => $product->producer ?? [], 'inStore' => $product->inStore, 'deliveryTime' => $product->deliveryTime, 'deliveryTimeText' => $product->deliveryTimeText, 'deliveryTimeRaw' => $product->deliveryTimeRaw, 'storesInStore' => $product->storesInStore ?? [], 'productDiscount' => [ 'discount' => $productDiscountResult->discount->asFloat(), 'priceForDiscount' => $productDiscountResult->priceForDiscount ? printPrice($productDiscountResult->priceForDiscount) : null, 'priceOriginal' => $productDiscountResult->priceOriginal ? printPrice($productDiscountResult->priceOriginal) : null, 'priceCommon' => $productDiscountResult->priceCommon ? printPrice($productDiscountResult->priceCommon) : null, ], 'campaign_codes' => $product->campaign_codes ?? [], ]; } /** * @required */ public function setUrlFinder(UrlFinder $urlFinder): void { $this->urlFinder = $urlFinder; } public function getFulltextSearchConfig(array $enabledTopics): array { $maxResults = 6; $config = []; foreach ($enabledTopics as $type) { $config[$type] = [ 'count' => $maxResults, 'offset' => 0, ]; } $config[FulltextElastic::INDEX_PRODUCTS]['order'] = '-weight'; return $config; } public function processFulltextResults(array $multiSearchResult, array $totals): array { $result = []; foreach ($multiSearchResult as $type => $results) { if (!$results) { continue; } switch ($type) { case FulltextElastic::INDEX_PRODUCTS: $result['Produkty'] = [ 'label' => 'Produkty', 'rows' => $totals[$type] ?? 0, 'items' => $this->processProducts($results), ]; break; case FulltextElastic::INDEX_SECTIONS: $result['Kategorie'] = [ 'label' => 'Kategorie', // min(total, 20), protoze SearchView zobrazuje max 20 vysledku 'rows' => min($totals[$type] ?? 0, 20), 'items' => $this->processSections($results), ]; break; case FulltextElastic::INDEX_PRODUCERS: $result['Vyrobci'] = [ 'label' => 'Výrobci', // min(total, 20), protoze SearchView zobrazuje max 20 vysledku 'rows' => min($totals[$type] ?? 0, 20), 'items' => $this->processProducers($results), ]; break; case FulltextElastic::INDEX_ARTICLES: $result['Clanky'] = [ 'label' => 'Články', // min(total, 20), protoze SearchView zobrazuje max 20 vysledku 'rows' => min($totals[$type] ?? 0, 20), 'items' => $this->processArticles($results), ]; break; case FulltextElastic::INDEX_PAGES: $result['Stranky'] = [ 'label' => 'Stránky', // min(total, 20), protoze SearchView zobrazuje max 20 vysledku 'rows' => min($totals[$type] ?? 0, 20), 'items' => $this->processPages($results), ]; break; default: $result[$type] = [ 'label' => '$type', // min(total, 20), protoze SearchView zobrazuje max 20 vysledku 'rows' => min($totals[$type] ?? 0, 20), 'items' => $results, ]; } } return $result; } }