<?php
namespace WebBundle\Service;
use AdmBundle\Helper\Adm;
use Exception;
use FlexApp\Constant\CatalogConst;
use FlexApp\Constant\TimeConstant;
use FlexApp\DTO\FilterGroupsCatalogDTO;
use FlexApp\DTO\RequestCatalogDTO;
use FlexApp\Helper\MetaHelper;
use FlexApp\Service\Meta\MetaManager;
use FlexApp\Service\RecommendationsAiService;
use FlexApp\ValueObject\LocaleVo;
use Import1CBundle\Helper\v3\BiConst;
use Import1CBundle\Helper\v3\InteriorHelper;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouterInterface;
use WebBundle\Entity\Factory;
use WebBundle\Entity\FilterEntity;
use WebBundle\Helper\App;
use WebBundle\Helper\LocaleHelper;
use WebBundle\Helper\PathHelper;
use WebBundle\Helper\RequestHelper;
use WebBundle\Helper\StrHelper;
use WebBundle\Helper\UserHelper;
use WebBundle\Repository\ArticleRepository;
use WebBundle\Repository\CollectionAlsoViewedRepository;
use WebBundle\Repository\CollectionRepository;
use WebBundle\Repository\FactoryRepository;
use WebBundle\Repository\FilterRepository;
use WebBundle\Repository\IdeaRepository;
use WebBundle\Repository\InteriorHistoryRepository;
use WebBundle\Repository\InteriorRepository;
/**
* Сервис обработки поиска (фильтров) в каталоге
*/
class SearchService extends ExtendService
{
private string $currency;
private LocaleVo $localeVo;
// указываем редирект, если требуется
private ?RedirectResponse $redirect = null;
// параметр рекламы AdWords
private ?string $gclid = null;
private ?string $html = null;
private FiltersService $filterService;
/** @required */
public CollectionRepository $collRepo;
/** @required */
public FilterRepository $filterRepo;
/** @required */
public FactoryRepository $factoryRepository;
/** @required */
public ArticleRepository $articleRepo;
/** @required */
public InteriorRepository $interiorRepo;
/** @required */
public IdeaRepository $ideaRepository;
/** @required */
public CollectionAlsoViewedRepository $collectionAlsoViewedRepository;
/** @required */
public InteriorHistoryRepository $interiorHistoryRepository;
private bool $isDebug;
private RouterInterface $router;
private ?string $titleHtml = null;
private ?MetaManager $metaManager = null;
private ?bool $isOneFilter = null;
private ?bool $isDesignerStyle = null;
private ?bool $isSort = null;
private ?int $sortId = null;
private array $filtersGTM = [];
private ?array $result = null;
private ?array $calculate = null;
private bool $gCount = false;
private int $limit = CatalogConst::PAGE_LIMIT;
private int $page = 1;
private array $searchFilter = [];
private ?string $searchKey = null;
private ?string $searchSubKey = null;
private array $searchData = [];
private $discount = null;
private $top20 = null;
private $amount;
private string $separator = CatalogConst::SEPARATOR;
public function setFilter(RequestCatalogDTO $requestCatalogDTO, array $searchFilter = []): self
{
$this->router = App::getRouter();
$this->localeVo = $requestCatalogDTO->getLocaleVo();
$this->currency = LocaleHelper::getCurrency();
$this->setGclid($requestCatalogDTO->getGclid());
$this->setPage($requestCatalogDTO->getPage());
$this->setSearchKey($requestCatalogDTO->getKey());
$this->setSearchSubKey($requestCatalogDTO->getSubkey());
$this->addSearchFilter($searchFilter);
$this->addSearchFilter($requestCatalogDTO->getSendData());
$this->isDebug = $requestCatalogDTO->isDebug();
$this->filterService = $filterService = App::getContainer()->get('app.service.filters');
// Добавляем значения рейтинга не из кеша
/////////////////////////////////////////////////////////////
if ($requestCatalogDTO->isAjax()) {
$this->loadByData($filterService);
$this->setSearchKey($filterService->getUrlKeyStr());
} else {
$this->loadByKeyFilters($filterService, $this->getSearchKey(), $this->getSearchSubKey());
}
if ($this->isRedirect()) {
return $this;
}
$this->addSearchFilter($filterService->getSearchFilter());
$this->buildPageParams($filterService);
return $this;
}
/**
* @param FiltersService $filterService
* @throws Exception
*/
public function buildPageParams(FiltersService $filterService)
{
$filterGroups = $filterService->buildFilterGroupsDTO();
$titleStr = $filterService->buldFiltersToStr($filterGroups, false, false);
$titleHtml = $filterService->buldFiltersToStr($filterGroups, false, true);
if ($this->isOneFilter()) {
$titleHtml = $this->buildH1($filterService, $titleHtml);
}
// ссылка на расширенный поиск, только если переход был с него
$referer = RequestHelper::syRequest()->headers->get('referer');
if ($referer) {
if (StrHelper::isInStr($referer, 'advanced-search')) {
$link = App::getRouter()->generate('app_filters_advanced_search', ['c' => $this->filterService()->getUrl()]);
$link = urldecode($link);
$link = $this->render(
'@Web/Catalog/linkback-to-filterpage.html.twig',
['link' => $link]
);
$titleHtml = $link . $titleHtml;
}
}
$this->titleHtml = $titleHtml;
// описание выводим только для каталога
// https://te.remote.team/#/discus/74928FFE-2BA0-006D-7D3A-6119415936BB/
if (!$this->isBrands()) {
$this->html = $this->buildHtml($filterService);
}
$this->buildFilterForGTM($filterGroups);
$this->amount = $this->setResultAmount($this->getResultCount());
$metaTitle = $titleStr;
$list = $this->getResultList();
if ($list) {
$priceMin = array_column($list, 'pr_min');
$priceMin = min($priceMin);
} else {
$priceMin = 0;
}
$currency = html_entity_decode($this->currency);
$this->metaManager = MetaHelper::getManager($filterService, [
'_route' => 'app_catalog',
'title' => $metaTitle,
'name' => $titleStr,
'countResult' => $this->getResultCount(),
'isOneFilter' => $this->isOneFilter(),
'min_price' => $priceMin,
'currency' => $currency,
]);
}
/**
* Генерация заголовка для страницы посещения
* @param FiltersService $filterService
* @return string
* @throws Exception
*/
public function getVisitedTitle(FiltersService $filterService)
{
$factoryFilter = isset($this->searchFilter['factory']);
/** @var FilterEntity $aCurFilter */
$aCurFilters = $filterService->getCurFilters();
$aCurFilter = (count($aCurFilters) >= 1) ? $aCurFilters[0] : $aCurFilters;
if (!$aCurFilter) {
return null;
}
$name = null;
if (count($aCurFilters) == 1 and $factoryFilter) {
if ($brand = $aCurFilter->getBrand()) {
$name = $brand->getName();
}
}
$name = $name ?: $this->getMetaManager()->getTitle();
return $name;
}
/**
* Генерация HTML описания при создании новой записи в SR и SC
* @param FiltersService $filterService
* @return mixed|string
* @throws Exception
*/
private function buildHtml(FiltersService $filterService)
{
$aCurFilters = $filterService->getCurFilters();
$aCurFilter = (count($aCurFilters) >= 1) ? $aCurFilters[0] : $aCurFilters;
if (!$this->isOneFilter()) {
return '';
}
// если фильтр 1 и это сортировка, то выводим описание главной каталога
if ($this->isSort() and $aCurFilter->isSort()) {
return '';
}
if (!$aCurFilter->isHtmlShow()) {
return '';
}
$html = $aCurFilter->getHtml();
return LocaleHelper::modifeLinkForLocales($html);
}
/**
* Определяем сколько фильтров будет отображено на странице в заголовке
* @return bool
* @throws Exception
*/
public function isOneFilter(): ?bool
{
if ($this->isOneFilter === null) {
$aCurFilters = $this->filterService()->getCurFilters();
$countFilters = count($aCurFilters);
if ($countFilters > 1) {
foreach ($aCurFilters as $oCurFilter) {
if ($oCurFilter->isSort()) {
$countFilters--;
} else {
if (!$oCurFilter->isEnable() and !$oCurFilter->getBrand()) {
$countFilters--;
}
}
}
}
$this->isOneFilter = ($countFilters == 1);
}
return $this->isOneFilter;
}
/**
* массив для GTM
* https://te.remote.team/#/discus/0CA30156-F404-63A9-2515-38E75FB7947B/
* @param FilterGroupsCatalogDTO $filterGroups
*/
public function buildFilterForGTM(FilterGroupsCatalogDTO $filterGroups)
{
$arr = [];
foreach ($filterGroups->getGroups() as $gr) {
$grAltName = $gr->getGroupAltName();
if (empty($arr[$grAltName])) {
$arr[$grAltName] = [];
}
foreach ($gr->getListFilters() as $f) {
$arr[$grAltName][] = [
'id' => $f->getId(),
'keyId' => $f->getKeyId(),
'key' => $f->getKey(),
'group' => $grAltName,
'altName' => $f->getAltName(),
];
}
}
$this->filtersGTM = $arr;
}
/**
* массив для GTM
* https://te.remote.team/#/discus/0CA30156-F404-63A9-2515-38E75FB7947B/
* @return array
*/
public function getFiltersGTM()
{
return $this->filtersGTM;
}
/**
* @param $data
* @param $tpl
* @return string|Response
* @throws Exception
*/
private function renderH1($data, $tpl)
{
switch ($tpl) {
case 'factory-header':
$result = $this->render(
'@Web/Catalog/factory-header.html.twig',
$data
);
break;
case 'one-header':
$result = $this->render(
'@Web/Catalog/one-header.html.twig',
$data
);
break;
case 'new-header':
$result = $this->render(
'@Web/Catalog/new-header.html.twig',
$data
);
break;
case 'header':
$result = $this->render(
'@Web/Catalog/header.html.twig',
$data
);
break;
default:
$result = $this->render(
'@Web/Catalog/one-header.html.twig',
$data
);
}
$result = htmlspecialchars_decode($result);
return $result;
}
/**
* @param FiltersService $filterService
* @param $title
* @return mixed|string|Response
* @throws Exception
*/
private function buildH1(FiltersService $filterService, $title)
{
$aCurFilters = $filterService->getCurFilters();
$aCurFilter = (count($aCurFilters) >= 1) ? $aCurFilters[0] : $aCurFilters;
if ($this->isOneFilter()) {
if ($aCurFilter->isBrand()) {
$brand = $aCurFilter->getBrand();
$countryName = '';
$countryUrl = '';
if ($brand->getCountry()) {
$countryName = $brand->getCountry()->getName();
$countryUrl = $this->generateUrl('app_catalog', ['key' => $brand->getCountry()->getSlug()]);
}
$data = [
'factory' => $brand,
'typeKey' => Factory::TYPES_KEY[$brand->getType()],
'country' => [
'name' => $countryName,
'url' => $countryUrl,
],
'editLink' => Adm::linkEdit('adm.brand.edit', $aCurFilter->getId()),
'biLink' => Adm::linkEdit('bi.factory.edit', $brand->getId()),
];
$h1 = $this->renderH1($data, 'factory-header');
} elseif ($this->isSort()) {
$isTop = false;
if (RequestHelper::getCurRoute() == 'app_catalog') {
$isTop = (!$this->isTop('also_coll_viewed') and $this->isTop()) ? $this->isTop() : false;
}
$h1 = $this->renderH1(['header' => $title, 'isTop' => $isTop], 'one-header');
} else {
if ($aCurFilter->isRootCataloge()) {
$h1 = $this->renderH1(['header' => $title], 'new-header');
} else {
$isTop = false;
if (RequestHelper::getCurRoute() == 'app_catalog') {
$isTop = (!$this->isTop('also_coll_viewed') and $this->isTop()) ? $this->isTop() : false;
}
$h1 = $this->renderH1(['header' => $title, 'isTop' => $isTop], 'one-header');
}
}
} else {
// оборачиваем $title в описание строки фильтрации
$h1 = $this->setCountResultNew($title, $this->getResultCount());
}
return $h1;
}
/**
* Поиск по Массиву значений старого образца вида: ['getColors'=>[3], 'getFacturas'=>[5]]
* @throws Exception
*/
private function loadByData(FiltersService $filterService)
{
if (!$this->getSearchFilter()) {
// залипуха для главной каталога
$filterService->loadByUrlKey('cataloge', $this->localeVo->getCode());
} else {
// загружаем данные строки в FiltersService
$filterService->loadBySearchData($this->getSearchFilter());
// из результата получаем новый searchFilter
$searchFilter = $filterService->getSearchFilter();
$this->setSearchFilter($searchFilter);
}
}
/**
* @param FiltersService $filterService
* @param string|null $key
* @param string|null $subKey
* @return bool
* @throws Exception
*/
private function loadByKeyFilters(FiltersService $filterService, ?string $key = null, ?string $subKey = null)
{
$lc = $this->localeVo->getCode();
// залипуха для главной каталога
if (!$key) {
$filterService->loadByUrlKey('cataloge', $lc);
return true;
}
$keyNorm = $this->normaliseKey($key);
$keyNorm = $subKey ? $keyNorm . $this->separator . $subKey : $keyNorm;
// доп. залипуха для главной каталога //$keyNorm = 'cataloge';
if (!$keyNorm) {
throw new NotFoundHttpException('Not Found');
}
// фикс для "бажных" ссылок, вида /fi_it/catalogue/coll/en
// отправляем их на главную каталога
if ($keyNorm == 'coll&en') {
$newUrl = $this->router->generate('app_catalog');
$this->setRedirect($newUrl, 301);
return false;
}
// загружаем данные строки в FiltersService
$filterService->loadByUrlKey($keyNorm, $lc);
// из результата получаем новый key строки
$newKey = $filterService->getUrlKeyStr();
if (!$newKey) {// на каталог больше не перенаправляем
throw new NotFoundHttpException('Not Found');
}
if (
(strlen($newKey) != strlen($key)) or
(strlen($newKey) <= 0 and strlen($key)) or
(substr_compare($key, $newKey, 0, strlen($key)) != 0)
) {
// 301 редирект на верную страницу
$newUrl = $filterService->getFullUrlKeyStr($newKey);
$gclid = $this->getGclid();
if ($gclid) {
$newUrl = $newUrl . '?gclid=' . $gclid;
}
// формирование верного редиректа для коллекций
$request = App::getRequest();
if ($request->get('_route') == 'app_collection') {
$routeParams = array_merge($request->get('_route_params'), ['elementId' => $newKey]);
$newUrl = str_replace('%26', '&', $this->generateUrl('app_collection', $routeParams));
}
$this->setRedirect($newUrl, 301);
return false;
}
return true;
}
/**
* @return FiltersService
*/
public function filterService(): FiltersService
{
return $this->filterService;
}
/////////////////////////////////////////////////////////////////////////
///геттеры и сеттеры
/////////////////////////////////////////////////////////////////////////
/**
* Получаем количество найденных резильтатов поиска из сфинкса
* @return mixed
* @throws Exception
*/
public function getResultCount()
{
$result = $this->getResult();
return $result['count'];
}
/**
* Получаем количество найденных резильтатов поиска из сфинкса
* @return mixed
* @throws Exception
*/
public function getResultList()
{
$result = $this->getResult();
return $result['list'];
}
/**
* Результат из сфинкса берем
* @return array
* @throws Exception
*/
public function getResult(): ?array
{
if ($this->result !== null) {
return $this->result;
}
$data = $this->getSearchData();
if ($this->isTop('top20month') || $this->isTop('top_month')) {
$data['searchFilter']['c_id'] = $this->getSearchTopMonth();
} elseif ($this->isTop('top_week')) {
$data['searchFilter']['c_id'] = $this->getSearchTopWeek();
}
$limit = $this->getLimit();
$page = $this->getPage();
if ($this->isTop('also_coll_viewed')) {
$data['searchFilter']['c_id'] = $this->getAlsoCollViewed();
}
$oMemcache = App::getMemcache();
$_measure = LocaleHelper::getUserMeasure();
$_sort = 'sort_' . $this->getSearchSortId();
$view = App::getRequest()->cookies->get('view_catalog', 1);
$test = App::isRole('ROLE_TEST') ? '_test' : '';
$keyCache = 'collection_filter_5' . $this->localeVo->getCodeFull() . $this->currency . $_measure . md5(json_encode($data)) . $limit . $page . $_sort . App::getRequest()->get('items') . $view . $test;
$keyCacheAll = 'collection_filter_5' . $this->localeVo->getCodeFull() . $this->currency . $_measure . md5(json_encode($data)) . $view . $_sort;
$result = $oMemcache->get($keyCache);
// для проверки без кеша в запрос добавляем ?debug=1
if (!$result || $this->isDebug) {
$min = ($limit * $page) - $limit;
$max = $limit * $page;
$sphinxResult = $oMemcache->get($keyCacheAll);
if (!$sphinxResult || $this->isDebug) {
$sphinxResult = App::searchSphinx($data, 3, false, true, $this->gCount);
// идея не получила одобрение но без кеша будет плохо потому есть ганс что вернем после очистки
// TODO очистить от не нужных полей сфинкс то что нужно для полного пересчета только для полного пересчета
// остальное только по надобности.
// $oMemcache->set($keyCacheAll, $sphinxResult, MEMCACHE_COMPRESSED, (int) TimeConstant::MINUTE * 10);
}
//так как получили количество то
if ($this->gCount) {
$allCalculate = $sphinxResult['res']['calculate'] ?? $sphinxResult['calculate'];
$this->setCalculate($allCalculate);
$oMemcache->set($keyCache . 'calc', $allCalculate, MEMCACHE_COMPRESSED, (int) TimeConstant::DAY);
$res = $sphinxResult['res']['collections'] ?? $sphinxResult['collections'];
} else {
$res = $sphinxResult['res'] ?? $sphinxResult;
}
$resTmp = [];
// убираем из выдачи материалы без c_name
//Нужно проверить, возможно это нужно делать не тут, а внутри так как может не сходиться результат подсчета
$res = array_filter($res, static fn(array $item) => !empty($item['c_name']));
foreach ($res as $i => $item) {
$discontinued = false;
if ((!empty($data['c_satus']) && $data['c_satus'] == BiConst::STATE_DISCONTINUED)) {
$discontinued = true;
}
$item['discontinued'] = $discontinued;
if ($item['author']) {
$item['author'] = str_replace(',', ', ', $item['author']);
}
if ($i < $min) {
continue;
} else {
if ($page <= 1 && $this->isBrands() && $this->isOneFilter()) {
if ($i < $max) {
$resTmp[$i] = $item;
}
} else {
if ($i >= $max) {
break;
} else {
$resTmp[$i] = $item;
}
}
}
}
$recommendationsAiService = new RecommendationsAiService();
foreach ($resTmp as $i => $r) {
$collId = $r['c_id'];
// получение ALT для интерьеров
if (!empty($r['interiors'])) {
$interiorIds = array_column($r['interiors'], 'i_id');
$interiorsValue = $this->interiorRepo->getInteriorsForCollection($collId, ['ids' => $interiorIds]);
$interiorsValue = array_combine(array_column($interiorsValue, 'id'), $interiorsValue);
$dataColl = [
'url' => $r['c_url'],
'header' => $r['c_header'],
'name' => $r['c_name'],
'alternateName' => $r['c_name'],
'factory' => [
'url' => $r['f_url'],
'alternateName' => $r['f_name'],
],
];
foreach ($r['interiors'] as $ii => $interior) {
$alt = null;
if (!empty($interiorsValue[$interior['i_id']])) {
$interiorValue = $interiorsValue[$interior['i_id']];
$interiorAlt = InteriorHelper::getInteriorDetails($interiorValue, $dataColl, $ii);
$alt = $interiorAlt['alt'];
$alt = is_array($alt) ? array_shift($alt) : $alt;
}
$resTmp[$i]['interiors'][$ii]['alt'] = $alt;
}
}
// для артикулов добавляем аттрибуты
if (!empty($r['articles'])) {
$articleIds = array_column($r['articles'], 'a_id');
$recArticles = $recommendationsAiService->getArticles(['ids' => $articleIds]);
$recArticles = array_combine(array_column($recArticles, 'id'), $recArticles);
if (!empty($recArticles)) {
foreach ($r['articles'] as $ia => $article) {
$categories = null;
$attributes = null;
if (!empty($recArticles[$article['a_id']])) {
$recArticleValue = $recArticles[$article['a_id']];
$recArticleAttr = $recommendationsAiService->buildArticleJson(
$recArticleValue,
['code' => $this->localeVo->getCode()]
);
$categories = $recArticleAttr['categories'];
$attributes = $recArticleAttr['attributes'];
}
$resTmp[$i]['articles'][$ia]['categories'] = $categories;
$resTmp[$i]['articles'][$ia]['attributes'] = $attributes;
}
}
}
}
// собираем сами id артикулов порции
if (App::getRequest()->get('items')) {
$aIdea = [];
foreach ($resTmp as $r) {
foreach ($r['articles'] as $i) {
$aIdea[] = $i['a_id'];
}
}
$items = $this->articleRepo->getArticleNativeOpt(['items' => $aIdea]);
foreach ($resTmp as $k => $r) {
foreach ($r['articles'] as $n => $i) {
$resTmp[$k]['articles'][$n] = array_merge($i, $items[$i['a_id']]);
}
}
}
if ($this->isTop('also_coll_viewed')) {
$count = count($resTmp);
} else {
$count = count($res);
}
$result = [
'count' => $count,
'list' => $resTmp,
'view' => $view,
];
// данные по отзывам коллекций фабрики
if ($page <= 1 && $this->isBrands() && $this->isOneFilter() && $res) {
$result['fName'] = $res[0]['f_name'];
$result['fLogo'] = PathHelper::pathGenerate('factory', ['url' => $res[0]['f_url']]);
}
// данные по отзывам коллекций фабрики
if ($page <= 1 && $this->showReview()) {
$result['fReview'] = $this->get('app.service.reviews')
->getReviewForFactory($sphinxResult['codes'] ?? []);
}
// кеш на сутки для первой страницы
if ($page <= 1) {
$oMemcache->set($keyCache, $result, MEMCACHE_COMPRESSED, (int) TimeConstant::HOUR);
}
} else {
$calculate = $oMemcache->get($keyCache . 'calc');
if ($calculate) {
$this->setCalculate($calculate);
}
}
$ids = [];
foreach ($result['list'] as $item) {
if (is_numeric($item['c_id'])) {
$ids[] = (int) $item['c_id'];
}
}
$urlAttr = [];
// прикручиваем строки фильтрации к URL коллекции
$fKey = $this->getSearchKey();
if ($fKey) {
$urlAttr['type'] = 'f';
$urlAttr['elementId'] = $fKey;
}
$aColl = $this->collRepo->getRatings($ids);
foreach ($result['list'] as $i => $item) {
$urlAttr['factoryUrl'] = $item['f_url'];
$urlAttr['collectionUrl'] = $item['c_url'];
// убираем из фильтров, когда фильтрация по коллекции с которыми смотрят
// например /**/catalogue/coll/3245167
if ($fKey == 'coll') {
unset($urlAttr['type']);
unset($urlAttr['elementId']);
}
// убираем фабрику из фильтров внутри коллекции
if ($this->isBrands() && $this->isOneFilter()) {
unset($urlAttr['type']);
unset($urlAttr['elementId']);
}
$url = App::generateUrl('app_collection', $urlAttr);
$result['list'][$i]['url'] = $url;
$rating = 0;
if (!empty($aColl[$item['c_id']])) {
$rating = $aColl[$item['c_id']];
}
$result['list'][$i]['rating'] = $rating;
// проверяем наличие наград и получаем по ним данные, если есть
$result['list'][$i]['awards'] = [];
if (!empty($item['c_awards']) && is_array($item['c_awards'])) {// #todo добавил проверку на array так как была ошибка, но надо разобраться будет
$aAwards = $this->filterRepo->getAwards($item['c_awards']);
if ($aAwards) {
$result['list'][$i]['awards'] = $aAwards;
}
}
// проверяем наличие выставок и получаем по ним даныне, если есть
$exhibitions = [];
$result['list'][$i]['c_exhibition'] = $exhibitions;
}
/////////////////////////////////////////////////////////////
// данные по интерьерам добавленым в идеи пользователя
$token = UserHelper::getInstance()->getToken();
$result['favorites'] = $this->ideaRepository->getInteriorsIdeasByToken($token);
////////////////////////////////////////////////////////////
/// группируем по фабрикам
$translator = App::getTranslator();
$groups = [];
foreach ($result['list'] as $item) {
$fName = $item['f_name'];
$fUrl = $this->generateUrl('app_catalog', ['key' => StrHelper::toLower($item['f_url'])]);
if (empty($groups[$fName])) {
$countryCode = strtolower($item['country_code']);
$countryUrl = $this->generateUrl('app_catalog', ['key' => $countryCode]);
$grop = [
'f_name' => $item['f_name'],
'f_url' => $fUrl,
'country' => [
'code' => $countryCode,
'name' => $translator->trans("country.$countryCode"),
'url' => $countryUrl,
],
'list' => [],
];
$groups[$fName] = $grop;
}
$header = $translator->trans(
$item['c_header'],
[
'%collection%' => preg_replace('#\\(.*\\)#isUu', '', $item['c_name']),
'%factory%' => $fName,
'%factoryUrl%' => $fUrl,
]
);
$item['header_html'] = $header;
$groups[$fName]['list'][] = $item;
}
$result['groups'] = $groups;
$this->result = $result;
return $this->result;
}
/**
* @return int
* @throws Exception
*/
public function getLimit()
{
if ($this->isTop('also_coll_viewed')) {
$this->limit = 15;
}
return $this->limit;
}
/**
* @param mixed $limit
*/
public function setLimit($limit)
{
if ($limit && is_numeric($limit)) {
$this->limit = $limit;
}
}
/**
* Получение списка сортировки для каталога
* @return array
* @throws Exception
*/
public function getSortList()
{
/** @var FilterRepository $filterRepo */
$filterRepo = App::getRepository('WebBundle:FilterEntity');
$list = $filterRepo->getSortListCataloge();
return $list;
}
/**
* @return int
* @throws Exception
*/
public function getSearchSortId()
{
if ($this->sortId == null) {
$sort = App::getSession()->get('sort_catalog_tmp');
if ($sort == null) {
$sort = App::getRequest()->get('sort');
}
if ($sort == null) {
$sort = App::getRequest()->cookies->get('sort_catalog');
}
if ($sort == null) {
$sort = 1;
}
$this->sortId = $sort;
}
return $this->sortId;
}
public function getMetaManager(): ?MetaManager
{
return $this->metaManager;
}
/**
* @return mixed
*/
public function getAmount()
{
return $this->amount;
}
/**
* получаем H1 заголовок
*/
public function getTitleHtml(): ?string
{
return $this->titleHtml;
}
/**
* получаем Html описание
*/
public function getHtml(): ?string
{
return $this->html;
}
/**
* @return null
* @throws Exception
*/
public function getDiscount()
{
if ($this->discount === null) {
$discount = false;
$factory = $this->factoryRepository->findOneBy(
['url' => $this->getSearchKey()]
);
if (
$factory && $factory->getDiscount() > 0 &&
$factory->getDiscountDateStart() != null &&
$factory->getDiscountDateEnd() != null &&
$factory->getDiscountDateStart()->getTimestamp() <= strtotime(date('d.m.Y')) &&
$factory->getDiscountDateEnd()->getTimestamp() > strtotime(date('d.m.Y'))
) {
$discount = true;
}
$this->setDiscount($discount);
}
return $this->discount;
}
/**
* @param mixed $discount
*/
public function setDiscount($discount)
{
$this->discount = $discount;
}
/**
* @return mixed
*/
public function getTop20()
{
return $this->top20;
}
/**
* @param mixed $top20
*/
public function setTop20($top20)
{
$this->top20 = $top20;
}
/**
* @return int
*/
public function getPage()
{
return $this->page;
}
/**
* @param mixed $page
*/
public function setPage($page)
{
if ($page && is_numeric($page)) {
$this->page = $page;
}
}
/**
* @return mixed
*/
public function getSearchFilter()
{
return $this->searchFilter;
}
/**
* @param mixed $searchFilter
*/
public function setSearchFilter($searchFilter)
{
if ($searchFilter) {
$this->searchFilter = $searchFilter;
}
}
/**
* @param $searchFilter
*/
public function addSearchFilter($searchFilter)
{
if ($searchFilter) {
if (count($this->searchFilter) > 0) {
$this->searchFilter = array_merge($this->searchFilter, $searchFilter);
} else {
$this->searchFilter = $searchFilter;
}
}
}
/**
* Получение ссылки на редактирование фильтра через сонату
* @return bool
* @throws Exception
*/
public function isNoindex(): bool
{
if ($this->isSort()) {
// не реализовано
return true;
}
return false;
}
/**
* @param bool $asJson
* @return array|false|string
* @throws Exception
*/
public function getSearchData($asJson = false)
{
$this->searchData = [
'searchFilter' => $this->getSearchFilter(),
'searchSort' => $this->getSearchSortId(),
'searchPeriod' => null,
'locale' => $this->localeVo->getCode(),
];
$searchData = $asJson ? json_encode($this->searchData, true) : $this->searchData;
return $searchData;
}
/**
* @param array $searchData
*/
public function setSearchData($searchData)
{
if ($searchData) {
$this->searchData = $searchData;
}
}
/**
* @return null|RedirectResponse
*/
public function getRedirect(): ?RedirectResponse
{
return $this->redirect;
}
/**
* @return mixed
*/
public function isRedirect()
{
return $this->redirect;
}
/**
* @param $url
* @param int $status
* @throws Exception
* @internal param mixed $redirect
*/
public function setRedirect($url, int $status = 302)
{
$this->redirect = new RedirectResponse($url, $status);
// пишем лог, если это не ридирект с sort-tmp переменной или дизайнерского стиля
if (!RequestHelper::get('sort-tmp')) {
$this->writeLogRedirect($this->redirect->getTargetUrl());
}
}
/**
* @return string|null
*/
public function getSearchKey()
{
return $this->searchKey;
}
/**
* @param $searchKey
*/
public function setSearchKey($searchKey)
{
if ($searchKey) {
$this->searchKey = $searchKey;
}
}
/**
* @return null
*/
public function getSearchSubKey()
{
return $this->searchSubKey;
}
/**
* @param $searchSubKey
*/
public function setSearchSubKey($searchSubKey)
{
if ($searchSubKey) {
$this->searchSubKey = $searchSubKey;
}
}
/**
* @return array
*/
public function getReplaceParentWithChildren(): array
{
$dataFilterSearch = $this->searchFilter;
try {
$parent = $this->filterRepo->getFiltersSubRelation(false);
} catch (\Doctrine\DBAL\Exception $e) {
$parent = [];
}
foreach ($dataFilterSearch as $key => &$values) {
// Проверка: существует ли такой ключ в $Parent и он является массивом
if (isset($parent[$key]) && is_array($parent[$key])) {
foreach ($values as $val) {
// Если значение существует как ключ в $Parent[$key]
if (array_key_exists($val, $parent[$key])) {
//удаляем родителя
$dataFilterSearch[$key] = array_diff($dataFilterSearch[$key], [$val]);
//добавляем детей
$dataFilterSearch[$key] = array_merge($dataFilterSearch[$key], $parent[$key][$val]);
}
}
}
}
return $dataFilterSearch;
}
public function getCalculate(): ?array
{
return $this->calculate;
}
public function setCalculate(?array $calculate)
{
if ($calculate) {
$this->calculate = $calculate;
}
}
public function setGCount(bool $gCount)
{
$this->gCount = $gCount;
}
public function getGCount(): bool
{
return $this->gCount;
}
/**
* Проверка на дизайнерский стиль
*/
public function isDesignerStyle(): ?bool
{
if ($this->isDesignerStyle === null) {
$this->isDesignerStyle = false;
$aCurFilters = $this->filterService()->getCurFilters();
if (count($aCurFilters) > 0) {
foreach ($aCurFilters as $curFilter) {
if ($curFilter->isDesignerStyle()) {
$this->isDesignerStyle = true;
break;
}
}
}
}
return $this->isDesignerStyle;
}
/**
* Проверка на сортировку
*/
public function isSort(): bool
{
return $this->isSort ?? false;
}
public function isBrands(): bool
{
$aCurFilters = $this->filterService()->getCurFilters();
if (count($aCurFilters) > 0) {
foreach ($aCurFilters as $curFilter) {
if ($curFilter->isBrand()) {
return true;
}
}
}
return false;
}
public function isDesigner(): bool
{
$aCurFilters = $this->filterService()->getCurFilters();
if (count($aCurFilters) > 0) {
foreach ($aCurFilters as $curFilter) {
if ($curFilter->isDesignerUser()) {
return true;
}
}
}
return false;
}
public function showReview(): bool
{
// Вернули показ только при выборе коллекции
// https://te.remote.team/#/discus/4353F149-3BFD-EC18-F321-C8300492082A/
$aCurFilters = $this->filterService()->getCurFilters();
if (count($aCurFilters) > 0) {
foreach ($aCurFilters as $curFilter) {
if ($curFilter->isBrand()) {
return true;
}
}
}
return false;
}
/**
* Определяем запрос топа или нет по группе каталога
* @param null $altName
* @return bool|string
* @throws Exception
*/
private function isTop($altName = null)
{
$aCurFilters = $this->filterService()->getCurFilters();
if (count($aCurFilters) > 0) {
foreach ($aCurFilters as $curFilter) {
if ($curFilter->isTop()) {
if (!$altName) {
return $curFilter->getAltName();
}
if ($curFilter->getAltName() == $altName) {
return $altName;
}
}
}
}
return false;
}
private function getAlsoCollViewed(): array
{
$id = $this->getSearchSubKey();
return $this->collectionAlsoViewedRepository->getCollectionAlsoViewed($id);
}
/**
* Получение топ за месяц
*/
private function getSearchTopMonth(): ?array
{
return $this->interiorHistoryRepository->getTopCollections(30, 1000);
}
/**
* Получение топ за неделю
*/
private function getSearchTopWeek(): ?array
{
return $this->interiorHistoryRepository->getTopCollections(7, 1000);
}
/**
* @return string
*/
public function getGclid()
{
return $this->gclid;
}
/**
* @param string $gclid
*/
public function setGclid($gclid)
{
$this->gclid = $gclid;
}
/**
* Убираем из урл не верные символы и проводим его в нормальному виду
* @param $key
* @return mixed
*/
private function normaliseKey($key)
{
$keyNorm = trim($key);
$keyNorm = StrHelper::toLower($keyNorm);
$keyNorm = urldecode($keyNorm);
////////////////////////////////////////////////////////////////////
$aBadKey = ['spal\'nya', 'tile_for_kids', 'non-slip', 'terakota-cotto-efekt'];
$aGoodKey = ['spalnya', 'spaces-for-children', 'antislip', 'terracotta-effetto'];
$keyNorm = str_replace($aBadKey, $aGoodKey, $keyNorm);
////////////////////////////////////////////////////////////////////
$aBadSimbol = ['%C3%BC', '%26', '_', 'antypoślizgowość', ' ', '%20', 'ã±', 'ñ', 'ã³', 'å„', 'å‚', 'ã¨', 'ã©', '%C5%82', 'ł', '\'a=0'];
$aGoodSimbol = ['ü', '&', '-', 'antypoślizgowe', '-', '-', 'n', 'n', 'ó', 'ń', 'ł', 'è', 'é', 'l', 'l', ''];
$keyNorm = str_replace($aBadSimbol, $aGoodSimbol, $keyNorm);
// разбираем строку запроса и собираем ее нормальную снова
$aKeyNorm = explode($this->separator, $keyNorm);
$keyNorm = '';
foreach ($aKeyNorm as $item) {
$item = trim($item, '-');
$keyNorm .= $this->separator . $item;
}
$keyNorm = trim($keyNorm, $this->separator);
$keyNorm = explode($this->separator, $keyNorm);
if (count($keyNorm) > 1) {
$keyNormTmp = [];
foreach ($keyNorm as $keyVal) {
if ($keyVal != 'coll') {
$keyNormTmp[] = trim($keyVal);
}
}
$keyNorm = implode($this->separator, $keyNormTmp);
} else {
$keyNorm = implode($this->separator, $keyNorm);
}
return $keyNorm;
}
/**
* Формируем отображение слова collections для результата поиска со скланениями и учетом локали
* @param $settings
* @param $count
* @param null $locale
* @return string|Response
* @throws Exception
*/
public function setCountResultNew($settings, $count, $locale = null)
{
$locale = $locale ?: $this->localeVo->getCode();
$strCollCount = App::getTranslator()->trans(
'catalog.settings.count_result',
[
'%count%' => App::plural($count),
'%cnt%' => $count,
],
null,
$locale
);
$text = $this->renderH1(
[
'settings' => $settings,
'strCollCount' => $strCollCount,
'count' => $count,
],
'header'
);
return $text;
}
/**
* Формируем отображение слова collections для результата поиска со скланениями и учетом локали
* @param $count
* @param string|null $locale
* @return null
* @throws Exception
*/
public function setResultAmount($count, ?string $locale = null)
{
if ($this->isTop('novelty')) {
return null;
}
if ($this->isBrands() && $this->isOneFilter()) {
return null;
}
$locale = $locale ?: $this->localeVo->getCode();
return App::getTranslator()->trans(
'catalog.settings.count_result',
[
'%count%' => App::plural($count),
'%cnt%' => $count,
],
null,
$locale
);
}
/**
* Проверяем наличие фильтра releaseYear на странице
* @return bool
*/
public function isPageReleaseYear()
{
$aCurFilters = $this->getSearchFilter();
return !empty($aCurFilters['releaseYear']);
}
/**
* Проверяем наличие фильтра exhibition на странице
* @return bool
*/
public function isPageExhibition()
{
$aCurFilters = $this->getSearchFilter();
return !empty($aCurFilters['exhibition']);
}
/**
* ПРоверка на одиночный фильтр быстрых образцов
* @return bool
* @throws Exception
*/
public function isPageExpressSample(): bool
{
if (!$this->isOneFilter()) {
return false;
}
$aCurFilters = $this->getSearchFilter();
return !empty($aCurFilters['expressSample']);
}
/**
* Пишем лог по редиректам каталога
* @param $uriRedirect
* @throws Exception
*/
private function writeLogRedirect($uriRedirect)
{
$idTheme = '9CA6E81A-5702-F84B-94E5-27704B4CDF90';
$idUser = ['145925377856FA72121B755145925377']; //mshirnin
$unid = null;
$uriCat = $this->generateUrl('app_catalog', ['_locale' => $this->localeVo->getCode()]);
$oRequest = RequestHelper::syRequest();
$uriTo = $oRequest->getRequestUri();
// Проверяем, если редирект на главную каталога, то пишем в лог данные запроса.
if ($oRequest->getRealMethod() != 'POST' and md5($uriCat) == md5($uriRedirect)) {
$referer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
// если есть реферер
if ($referer) {
$urlParse = parse_url($referer);
if ($oRequest->getHost() == "tile.expert") {
// пишем сначала на портал
//////////////////////////////////////////////////////////////
$portalHelper = App::getPortal();
$subject = "BadRedirect $uriTo";
$body =
"<p>Битая ссылка, требуется проверка запроса и исправление, если редирект не корректный, данные:</p>" .
"<ul>" .
"<li><b>Хост</b>: " . App::getDomain() . "</li>" .
"<li><b>Method</b>: {$oRequest->getRealMethod()}</li>" .
"<li><b>Откуда</b>: {$urlParse['path']}</li>" .
"<li><b>Куда</b>: $uriTo</li>" .
"</ul>";
$comm = $portalHelper->checkExistComm($idTheme, $subject);
if (!$comm) {
$task = [
'subjectID' => $idTheme,
'subject' => $subject,
'body' => $body,
'taskPerformerLat' => $idUser,
];
$unid = $portalHelper->createTask($task);
$unid = !empty($unid['unid']) ? $unid['unid'] : null;
}
// пишем в файл
// app.INFO: [redirect_catalog] urlCur: /en/catalogue/ABITAb, urlRef: , data: {"_controller":"WebBundle\\Controller\\CatalogController::indexAction","key":"ABITAb","subkey":"","_locale":"en","_route":"app_catalog","_route_params":{"key":"ABITAb","subkey":"","_locale":"en"}} [] []
// grep -ri 'redirect_catalog' /var/www/тут домен/app/logs/ >> redirect_catalog.txt
//////////////////////////////////////////////////////////////
$logger = App::getContainer()->get('logger_public');
$requestData = json_encode(RequestHelper::get());
$logger->info("[redirect_catalog] unid: $unid, uriTo: $uriTo, uriRef: $referer, data: $requestData");
}
}
}
}
}