<?php
namespace Slivki\Controller;
use Slivki\Entity\Offer;
use Slivki\Entity\UserGroup;
use Slivki\Services\DeviceTypeService;
use Slivki\Services\Seo\SeoResourceService;
use Symfony\Component\Routing\Annotation\Route;
use Slivki\Entity\Banner;
use Slivki\Entity\Category;
use Slivki\Entity\City;
use Slivki\Entity\GeoLocation;
use Slivki\Entity\User;
use Slivki\Services\CacheService;
use Slivki\Services\SearchService;
use Slivki\Services\SolrIndex;
use Slivki\Util\CommonUtil;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Constraints\DateTime;
class SearchController extends SiteController {
const OFFERS_PER_PAGE = 44;
const ACTIVE_OFFERS_PER_PAGE = 44;
const PAST_OFFERS_ON_FIRST_PAGE = 4;
const PAST_OFFERS_PER_PAGE = 44;
const SUGGEST_MAX_RESULT = 5;
const CACHE_NAME = 'search-5-';
const DEBUG_MODE = false;
const ACTIVE_OFFERS_ROWS_PER_PAGE = 20;
const ACTIVE_OFFERS_ROWS_PER_PAGE_MOBILE = 8;
const SEARCH_OFFERS = 0;
const SEARCH_SALES = 1;
const SEARCH_PARTNER_OFFERS = 2;
const SEARCH_INSTALLMENTS = 5;
const SEARCH_FLIERS = 6;
const SEARCH_PHOTO_GUIDES = 7;
/**
* @Route("/search")
*/
public function searchAction(
Request $request,
SearchService $searchService,
DeviceTypeService $deviceTypeService
): Response {
ini_set('memory_limit', '2g');
$isMobileDevice = $deviceTypeService->isMobileDevice($request);
$searchText = $request->query->get('text');
$searchText = mb_strtolower(trim($searchText));
$response = new Response();
$redirect = $this->checkForRedirect($searchText, $request);
if ($redirect) {
return new RedirectResponse($redirect['url']);
}
$entityManager = $this->getDoctrine()->getManager();
$banners = $entityManager->getRepository(Banner::class)->findBy(['typeID' => Banner::TYPE_SEARCH_BANNER, 'active' => true, 'positionRow' => 1]);
$banner = null;
if ($banners) {
$banner = $banners[random_int(0, count($banners) - 1)];
}
$secondBanners = $entityManager->getRepository(Banner::class)->findBy(['typeID' => Banner::TYPE_SEARCH_BANNER, 'active' => true, 'positionRow' => 2]);
$secondBanner = null;
if ($secondBanners) {
$secondBanner = $secondBanners[random_int(0, count($secondBanners) - 1)];
}
$sortBy = $request->query->get('sortBy', 'default');
$distance = $request->query->getInt('distance');
$isVoice = $request->query->getBoolean('isVoice');
$cityID = null;
$currentCityID = $request->getSession()->get(City::CITY_ID_SESSION_KEY, City::DEFAULT_CITY_ID);
$userLocation = $request->cookies->get(User::CURRENT_LOCATION_COOKIE, GeoLocation::DEFAULT_LOCATION);
if ($this->getUser() && $this->getUser()->hasRole(UserGroup::ROLE_ADS_FREE)) {
$searchBanner = '';
$searchBannerSecond = '';
} else {
$searchBanner = $this->renderView($isMobileDevice ? 'Slivki/mobile/banner/banner.html.twig' : 'Slivki/banners/search_banner.html.twig', [
'banner' => $banner,
'additionalClass' => 'd-block d-sm-none',
]);
$searchBannerSecond = $this->renderView($isMobileDevice ? 'Slivki/mobile/banner/banner.html.twig' : 'Slivki/banners/search_banner.html.twig', [
'banner' => $secondBanner,
'additionalClass' => 'd-block d-sm-none',
]);
}
$searchResultHtml = str_replace(
[
'<!--::searchBanner::-->',
'<!--::searchBannerSecond::-->',
'---slivkis---',
],
$currentCityID === City::TASHKENT_CITY_ID
? ['', '', '']
: [
$searchBanner,
$searchBannerSecond,
$request->cookies->get('slivkis'),
],
$searchService->search(
$searchText,
$sortBy,
$distance,
$userLocation,
$isMobileDevice,
true,
false,
$this->getUser() ? $this->getUser()->getID() : null,
$cityID,
$currentCityID,
$isVoice,
),
);
if ($isMobileDevice) {
$searchResultHtml = str_replace(
$currentCityID === City::TASHKENT_CITY_ID
? ['', '']
: [
'<!--::searchBanner2::-->',
'<!--::searchBannerSecond2::-->',
],
[
$this->renderView('Slivki/mobile/banner/banner.html.twig', [
'banner' => $banner,
'additionalClass' => 'd-none d-sm-block',
]),
$this->renderView('Slivki/mobile/banner/banner.html.twig', [
'banner' => $secondBanner,
'additionalClass' => 'd-none d-sm-block',
]),
],
$searchResultHtml,
);
}
$content = $this->renderView($isMobileDevice ? 'Slivki/mobile/search/index.html.twig' : 'Slivki/search/index.html.twig', [
'searchResultHtml' => $searchResultHtml,
'searchText' => $searchText,
'currentLocationCookie' => $request->cookies->get('currentLocation')
]);
$response->setContent($content);
return $response;
}
/** @Route("/search/all_entities_more") */
public function searchAllEntitiesMoreAction(Request $request, SearchService $searchService, SolrIndex $solrIndex) {
ini_set('memory_limit', '2G');
$searchText = $request->query->get('text');
$searchText = mb_strtolower(trim($searchText));
$sortBy = $request->query->get('sortBy');
$page = 1 + $request->query->getInt('page');
$distance = $request->query->getInt('distance');
$cityID = $request->query->getInt('cityID');
$searchResult = $solrIndex->searchAll($searchText, $cityID);
if ($searchResult['count'] == 0) {
$searchResult = $solrIndex->searchAll($searchService->transformToCyrillic($searchText), $cityID);
}
$userLocation = $request->cookies->get(User::CURRENT_LOCATION_COOKIE, GeoLocation::DEFAULT_LOCATION);
$isMobileDevice = CommonUtil::isMobileDevice($request);
$allFoundEntities = $searchService->getAllFoundEntities($userLocation, $searchText, $searchResult, $sortBy, $distance, $isMobileDevice, $page);
$data['allFoundEntities'] = &$allFoundEntities;
$data['activeOffers'] = &$allFoundEntities;
$data['searchText'] = $searchText;
$data['totalColumnAmount'] = self::getMobileDevice($request) ? 2 : 4;
$data['cityID'] = $cityID;
return $this->render($isMobileDevice ? 'Slivki/mobile/search/result.html.twig' : 'Slivki/search/all_results.html.twig', $data);
}
public function checkForRedirect($searchText, $request) {
$data = [];
switch ($searchText) {
case "личный кабинет":
$url = "/profile";
$data['url'] = $url;
break;
case "код":
$url = "/profile";
if (self::getMobileDevice($request)) {
$url = "/profile#profilePromocodeTab";
}
$data['url'] = $url;
break;
case "коды":
$url = "/profile";
if (self::getMobileDevice($request)) {
$url = "/profile#profilePromocodeTab";
}
$data['url'] = $url;
break;
case "промокоды":
$url = "/profile";
if (self::getMobileDevice($request)) {
$url = "/profile#profilePromocodeTab";
}
$data['url'] = $url;
break;
case "мои коды":
$url = "/profile";
if (self::getMobileDevice($request)) {
$url = "/profile#profilePromocodeTab";
}
$data['url'] = $url;
break;
case "мои промокоды":
$url = "/profile";
if (self::getMobileDevice($request)) {
$url = "/profile#profilePromocodeTab";
}
$data['url'] = $url;
break;
case "код скидки":
$url = "/profile";
if (self::getMobileDevice($request)) {
$url = "/profile#profilePromocodeTab";
}
$data['url'] = $url;
break;
case "новые":
$url = SeoResourceService::NEW_OFFERS_CATEGORY_URL;
$data['url'] = $url;
break;
case "листовки":
$url = "/giper-shop?utm_source=main_menu";
$data['url'] = $url;
break;
case "гипермаркеты":
$url = "/giper-shop?utm_source=main_menu";
$data['url'] = $url;
break;
case "торговые сети":
$url = "/giper-shop?utm_source=main_menu";
$data['url'] = $url;
break;
case "салоны красоты":
$url = "/salony-krasoty";
$data['url'] = $url;
break;
case "видеогид":
case "видеогиды":
$url = "/videogidy";
$data['url'] = $url;
break;
}
if ($data) {
return $data;
}
return false;
}
/** @Route("/search/more") */
public function moreAction(Request $request, CacheService $cacheService, SearchService $searchService, SolrIndex $solrIndex) {
$isMobileDevice = CommonUtil::isMobileDevice($request);
$page = $request->query->get('activeOffersPage', 0) + 1;
$sortBy = $request->query->get('sortBy', 'default');
$distance = $request->query->get('distance', 0);
$searchText = $request->query->get('text');
$searchText = mb_strtolower(trim($searchText));
$cityID = $request->query->getInt('cityID');
$searchResult = $solrIndex->searchOffers($searchText, $cityID);
$relevantResults = [];
foreach ($searchResult['allResults'] as $item) {
$notFound = true;
foreach ($searchResult['descriptionResults'] as $result) {
if ($result['id'] == $item['id']) {
$notFound = false;
break;
}
}
if ($notFound) {
$relevantResults[] = $item;
}
}
$searchResult = $relevantResults;
if (count($searchResult) == 0) {
$searchResult = $solrIndex->searchOffers($searchService->transformToCyrillic($searchText), $cityID, false, $cityID);
$searchResult = $searchResult['allResults'];
}
$offers = ['list' => [], 'page' => $page, 'haveMore' => false];
foreach ($searchResult as $item) {
$offers['list'][] = $item['id'];
}
$userLocation = $request->cookies->get(User::CURRENT_LOCATION_COOKIE, GeoLocation::DEFAULT_LOCATION);
if (!is_array($userLocation)) {
$userLocation = explode(',', $userLocation);
}
if ($distance > 0) {
$this->filterByDistance($offers['list'], $userLocation, $distance);
}
if ($sortBy != 'default') {
$this->sortResults($offers['list'], $sortBy, $userLocation);
}
$perPage = self::OFFERS_PER_PAGE;
$offset = $perPage * ($page - 1);
$offers['haveMore'] = count($offers['list']) > $offset + $perPage;
$offers['page'] = $page;
$offers['list'] = array_slice($offers['list'], $offset, $perPage + 10);
$entityManager = $this->getDoctrine()->getManager();
$offerRepository = $entityManager->getRepository(Offer::class);
$offerList = [];
foreach ($offers['list'] as $offerID) {
$offer = $cacheService->getTeaser($offerID, $isMobileDevice);
if (!$offer) {
$offer = $offerRepository->getAnyWay($offerID);
}
if ($offer) {
$offerList[] = $offer;
}
if (count($offerList) >= $perPage) {
break;
}
}
$offers['list'] = $offerList;
$data['totalColumnAmount'] = $isMobileDevice ? 2 : 4;
$data['activeOffers'] = $offers;
$data['searchText'] = $searchText;
$data['offset'] = $offset + $perPage;
$data['cityID'] = $cityID;
return $this->render($isMobileDevice ? 'Slivki/mobile/search/result.html.twig' : 'Slivki/search/results.html.twig', $data);
}
/**
* @Route("/search/suggest")
*/
public function suggestAction(Request $request, SearchService $searchService) {
$response = new JsonResponse();
$response->setCharset('UTF-8');
$response->setEncodingOptions(JSON_UNESCAPED_UNICODE);
$response->setData($searchService->getSuggest($request->get('term', '')));
return $response;
}
public function luceneSearchAction(Request $request) {
/** @var \Slivki\Util\LuceneIndex $luceneIndex */
$luceneIndex = $this->get('slivki.lucene_index');
$activeOfferList = [];
$pastOfferList = [];
$saleList = [];
$searchText = trim($request->get('text'));
if ($searchText != '') {
$result = $luceneIndex->search($searchText);
foreach ($result as $item) {
if ($item['type'] == Category::OFFER_CATEGORY_ID) {
/** @var \Slivki\Entity\Offer $offer */
$offer = $this->getOfferRepository()->find($item['id']);
if ($offer) {
if ($offer->getActiveTill() > new DateTime()) {
$activeOfferList[] = $offer;
} else {
$pastOfferList[] = $offer;
}
}
} else {
$sale = $this->getSaleRepository()->find($item['id']);
if ($sale) {
$saleList[] = $sale;
}
}
}
}
$data['expanded'] = false;
$data['offerList'] = $pastOfferList;
$activeOffers = $this->get('twig')->render('Slivki/offers/teasers.html.twig', $data);
return new Response($activeOffers);
}
private function filterByDistance(&$offerList, $location, $distance) {
if (count($offerList) == 0) {
return;
}
$sql = "select geo_location_relation.entity_id from geo_location_relation inner join geo_location on geo_location_relation.location_id = geo_location.id
where geo_location.latitude <> '' and geo_location.longitude <> '' and geo_location_relation.entity_id in (" . implode(',', $offerList) . ")
and earth_distance(ll_to_earth($location[0], $location[1]), ll_to_earth(latitude::float8, longitude::float8)) * 1.25 <= $distance * 1000";
$result = $this->getDoctrine()->getConnection()->executeQuery($sql)->fetchAll(\PDO::FETCH_COLUMN);
$offerListFiltered = [];
foreach ($offerList as $offerID) {
if (in_array($offerID, $result)) {
$offerListFiltered[] = $offerID;
}
}
$offerList = $offerListFiltered;
}
private function sortResults(&$data, $sortBy, $location) {
if ($sortBy == 'popularity') {
if (isset($data['list'])) {
$this->sortOffersByPopularity($data['list']);
} else {
$this->sortSalesByPopularity($data);
}
} else {
if (isset($data['list'])) {
$this->sortOffersByDistance($data['list'], $location);
} else {
$this->sortOffersByDistance($data, $location);
}
}
}
private function sortOffersByDistance(&$offerList, $location) {
if (count($offerList) == 0) {
return;
}
$sql = "select geo_location_relation.entity_id
from geo_location_relation inner join geo_location on geo_location_relation.location_id = geo_location.id where geo_location.latitude <> '' and geo_location.longitude <> ''
and geo_location_relation.entity_id in (" . implode(',', $offerList) . ") order by earth_distance(ll_to_earth($location[0], $location[1]), ll_to_earth(latitude::float8, longitude::float8))";
$result = $this->getDoctrine()->getConnection()->executeQuery($sql)->fetchAll(\PDO::FETCH_COLUMN);
$offerList = array_unique(array_merge($result, array_diff($result, $offerList)));
}
private function sortOffersByPopularity(&$offerList) {
if (count($offerList) == 0) {
return;
}
$sql = 'select entity_id, purchase_count_recent as purchase_count from purchase_count where entity_id in (' . implode(',', $offerList) . ')';
$result = $this->getDoctrine()->getManager()->getConnection()->executeQuery($sql)->fetchAll(\PDO::FETCH_ASSOC);
$purchaseCountList = [];
foreach ($result as $item) {
$purchaseCountList[$item['entity_id']] = $item['purchase_count'];
}
usort($offerList, function($offerID, $offerID1) use(&$purchaseCountList) {
$purchaseCount = isset($purchaseCountList[$offerID]) ? $purchaseCountList[$offerID] : 0;
$purchaseCount1 = isset($purchaseCountList[$offerID1]) ? $purchaseCountList[$offerID1] : 0;
return $purchaseCount > $purchaseCount1 ? -1 : 1;
});
}
private function sortSalesByPopularity(&$saleList) {
if (count($saleList) == 0) {
return 0;
}
$sql = 'select entity_id, visit_count from visit_count_aggregated where entity_id in (' . implode(',', $saleList) . ') and days_count = 7';
$result = $this->getDoctrine()->getManager()->getConnection()->executeQuery($sql)->fetchAll(\PDO::FETCH_ASSOC);
$visitCountList = [];
foreach ($result as $item) {
$visitCountList[$item['entity_id']] = $item['visit_count'];
}
usort($saleList, function($saleID, $saleID1) use($visitCountList) {
$visitCount = isset($visitCountList[$saleID]) ? $visitCountList[$saleID] : 0;
$visitCount1 = isset($visitCountList[$saleID1]) ? $visitCountList[$saleID1] : 0;
return $visitCount > $visitCount1 ? -1 : 1;
});
}
}