src/WebBundle/Service/SearchService.php line 577

Open in your IDE?
  1. <?php
  2. namespace WebBundle\Service;
  3. use AdmBundle\Helper\Adm;
  4. use Exception;
  5. use FlexApp\Constant\CatalogConst;
  6. use FlexApp\Constant\TimeConstant;
  7. use FlexApp\DTO\FilterGroupsCatalogDTO;
  8. use FlexApp\DTO\RequestCatalogDTO;
  9. use FlexApp\Helper\MetaHelper;
  10. use FlexApp\Service\Meta\MetaManager;
  11. use FlexApp\Service\RecommendationsAiService;
  12. use FlexApp\ValueObject\LocaleVo;
  13. use Import1CBundle\Helper\v3\BiConst;
  14. use Import1CBundle\Helper\v3\InteriorHelper;
  15. use Symfony\Component\HttpFoundation\RedirectResponse;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  18. use Symfony\Component\Routing\RouterInterface;
  19. use WebBundle\Entity\Factory;
  20. use WebBundle\Entity\FilterEntity;
  21. use WebBundle\Helper\App;
  22. use WebBundle\Helper\LocaleHelper;
  23. use WebBundle\Helper\PathHelper;
  24. use WebBundle\Helper\RequestHelper;
  25. use WebBundle\Helper\StrHelper;
  26. use WebBundle\Helper\UserHelper;
  27. use WebBundle\Repository\ArticleRepository;
  28. use WebBundle\Repository\CollectionAlsoViewedRepository;
  29. use WebBundle\Repository\CollectionRepository;
  30. use WebBundle\Repository\FactoryRepository;
  31. use WebBundle\Repository\FilterRepository;
  32. use WebBundle\Repository\IdeaRepository;
  33. use WebBundle\Repository\InteriorHistoryRepository;
  34. use WebBundle\Repository\InteriorRepository;
  35. /**
  36.  * Сервис обработки поиска (фильтров) в каталоге
  37.  */
  38. class SearchService extends ExtendService
  39. {
  40.     private string $currency;
  41.     private LocaleVo $localeVo;
  42.     // указываем редирект, если требуется
  43.     private ?RedirectResponse $redirect null;
  44.     // параметр рекламы AdWords
  45.     private ?string $gclid null;
  46.     private ?string $html null;
  47.     private FiltersService $filterService;
  48.     /** @required */
  49.     public SliderService $sliderService;
  50.     /** @required */
  51.     public CollectionRepository $collRepo;
  52.     /** @required */
  53.     public FilterRepository $filterRepo;
  54.     /** @required */
  55.     public FactoryRepository $factoryRepository;
  56.     /** @required */
  57.     public ArticleRepository $articleRepo;
  58.     /** @required */
  59.     public InteriorRepository $interiorRepo;
  60.     /** @required */
  61.     public IdeaRepository $ideaRepository;
  62.     /** @required */
  63.     public CollectionAlsoViewedRepository $collectionAlsoViewedRepository;
  64.     /** @required */
  65.     public InteriorHistoryRepository $interiorHistoryRepository;
  66.     private bool $isDebug;
  67.     private RouterInterface $router;
  68.     private ?string $titleHtml null;
  69.     private ?MetaManager $metaManager null;
  70.     private ?bool $isOneFilter null;
  71.     private ?bool $isDesignerStyle null;
  72.     private ?bool $isSort null;
  73.     private ?int $sortId null;
  74.     private array $filtersGTM = [];
  75.     private ?array $result null;
  76.     private ?array $calculate null;
  77.     private ?bool $NoWithArticles false;
  78.     private bool $gCount false;
  79.     private int $limit CatalogConst::PAGE_LIMIT;
  80.     private int $page 1;
  81.     private array $searchFilter = [];
  82.     private ?string $searchKey null;
  83.     private ?string $searchSubKey null;
  84.     private array $searchData = [];
  85.     private $discount null;
  86.     private $top20 null;
  87.     private $amount;
  88.     private string $separator CatalogConst::SEPARATOR;
  89.     private ?string $separatorOld null;
  90.     public function setFilter(RequestCatalogDTO $requestCatalogDTO, array $searchFilter = []): self
  91.     {
  92.         $this->router App::getRouter();
  93.         $this->localeVo $requestCatalogDTO->getLocaleVo();
  94.         $this->currency LocaleHelper::getCurrency();
  95.         // Разделитель ключей фильтров: берём из параметров (по умолчанию '&')
  96.         try {
  97.             $newSeparator App::getParameter('filters.separator.new');
  98.             if (is_string($newSeparator) && $newSeparator !== '') {
  99.                 $this->separator $newSeparator;
  100.             }
  101.         } catch (\Throwable $e) {
  102.             // noop
  103.         }
  104.         try {
  105.             $oldSeparator App::getParameter('filters.separator.old');
  106.             if (is_string($oldSeparator) && $oldSeparator !== '') {
  107.                 $this->separatorOld $oldSeparator;
  108.             }
  109.         } catch (\Throwable $e) {
  110.             // noop
  111.         }
  112.         $this->setGclid($requestCatalogDTO->getGclid());
  113.         $this->setPage($requestCatalogDTO->getPage());
  114.         $this->setSearchKey($requestCatalogDTO->getKey());
  115.         $this->setSearchSubKey($requestCatalogDTO->getSubkey());
  116.         $this->addSearchFilter($searchFilter);
  117.         $this->addSearchFilter($requestCatalogDTO->getSendData());
  118.         $this->isDebug $requestCatalogDTO->isDebug();
  119.         $this->filterService $filterService App::getContainer()
  120.             ->get('app.service.filters');
  121.         // Добавляем значения рейтинга не из кеша
  122.         /////////////////////////////////////////////////////////////
  123.         if ($requestCatalogDTO->isAjax()) {
  124.             $this->loadByData($filterService);
  125.             $this->setSearchKey($filterService->getUrlKeyStr());
  126.         } else {
  127.             $this->loadByKeyFilters($filterService$this->getSearchKey(), $this->getSearchSubKey());
  128.         }
  129.         if ($this->isRedirect()) {
  130.             return $this;
  131.         }
  132.         $this->addSearchFilter($filterService->getSearchFilter());
  133.         $this->buildPageParams($filterService);
  134.         return $this;
  135.     }
  136.     /**
  137.      * @param FiltersService $filterService
  138.      *
  139.      * @throws Exception
  140.      */
  141.     public function buildPageParams(FiltersService $filterService)
  142.     {
  143.         $filterGroups $filterService->buildFilterGroupsDTO();
  144.         $titleStr $filterService->buldFiltersToStr($filterGroupsfalsefalse);
  145.         $titleHtml $filterService->buldFiltersToStr($filterGroupsfalsetrue);
  146.         if ($this->isOneFilter()) {
  147.             $titleHtml $this->buildH1($filterService$titleHtml);
  148.         }
  149.         $titleHtml $this->getLinkToAdvancedSearchIfNecessary($titleHtml);
  150.         $this->titleHtml $titleHtml;
  151.         // описание выводим только для каталога
  152.         // https://te.remote.team/#/discus/74928FFE-2BA0-006D-7D3A-6119415936BB/
  153.         if (!$this->isBrands()) {
  154.             $this->html $this->buildHtml($filterService);
  155.         }
  156.         $this->buildFilterForGTM($filterGroups);
  157.         $this->amount $this->setResultAmount($this->getResultCount());
  158.         $metaTitle $titleStr;
  159.         $list $this->getResultList();
  160.         if ($list) {
  161.             $priceMin array_column($list'pr_min');
  162.             $priceMin min($priceMin);
  163.         } else {
  164.             $priceMin 0;
  165.         }
  166.         $currency html_entity_decode($this->currency);
  167.         $this->metaManager MetaHelper::getManager($filterService, [
  168.             '_route' => 'app_catalog',
  169.             'title' => $metaTitle,
  170.             'name' => $titleStr,
  171.             'countResult' => $this->getResultCount(),
  172.             'isOneFilter' => $this->isOneFilter(),
  173.             'min_price' => $priceMin,
  174.             'currency' => $currency,
  175.         ]);
  176.     }
  177.     /**
  178.      * Генерация заголовка для страницы посещения
  179.      *
  180.      * @param FiltersService $filterService
  181.      *
  182.      * @return string
  183.      * @throws Exception
  184.      */
  185.     public function getVisitedTitle(FiltersService $filterService)
  186.     {
  187.         $factoryFilter = isset($this->searchFilter['factory']);
  188.         /** @var FilterEntity $aCurFilter */
  189.         $aCurFilters $filterService->getCurFilters();
  190.         $aCurFilter = (count($aCurFilters) >= 1) ? $aCurFilters[0] : $aCurFilters;
  191.         if (!$aCurFilter) {
  192.             return null;
  193.         }
  194.         $name null;
  195.         if (count($aCurFilters) == and $factoryFilter) {
  196.             if ($brand $aCurFilter->getBrand()) {
  197.                 $name $brand->getName();
  198.             }
  199.         }
  200.         $name $name ?: $this->getMetaManager()
  201.             ->getTitle();
  202.         return $name;
  203.     }
  204.     /**
  205.      * Генерация HTML описания при создании новой записи в SR и SC
  206.      *
  207.      * @param FiltersService $filterService
  208.      *
  209.      * @return mixed|string
  210.      * @throws Exception
  211.      */
  212.     private function buildHtml(FiltersService $filterService)
  213.     {
  214.         $aCurFilters $filterService->getCurFilters();
  215.         $aCurFilter = (count($aCurFilters) >= 1) ? $aCurFilters[0] : $aCurFilters;
  216.         if (!$this->isOneFilter()) {
  217.             return '';
  218.         }
  219.         // если фильтр 1 и это сортировка, то выводим описание главной каталога
  220.         if ($this->isSort() and $aCurFilter->isSort()) {
  221.             return '';
  222.         }
  223.         if (!$aCurFilter->isHtmlShow()) {
  224.             return '';
  225.         }
  226.         $html $aCurFilter->getHtml();
  227.         return LocaleHelper::modifeLinkForLocales($html);
  228.     }
  229.     /**
  230.      * Определяем сколько фильтров будет отображено на странице в заголовке
  231.      * @return bool
  232.      * @throws Exception
  233.      */
  234.     public function isOneFilter(): ?bool
  235.     {
  236.         if ($this->isOneFilter === null) {
  237.             $aCurFilters $this->filterService()
  238.                 ->getCurFilters();
  239.             $countFilters count($aCurFilters);
  240.             if ($countFilters 1) {
  241.                 foreach ($aCurFilters as $oCurFilter) {
  242.                     if ($oCurFilter->isSort()) {
  243.                         $countFilters--;
  244.                     } elseif (!$oCurFilter->isEnable() and !$oCurFilter->getBrand()) {
  245.                         $countFilters--;
  246.                     }
  247.                 }
  248.             }
  249.             $this->isOneFilter = ($countFilters == 1);
  250.         }
  251.         return $this->isOneFilter;
  252.     }
  253.     /**
  254.      * массив для GTM
  255.      * https://te.remote.team/#/discus/0CA30156-F404-63A9-2515-38E75FB7947B/
  256.      *
  257.      * @param FilterGroupsCatalogDTO $filterGroups
  258.      */
  259.     public function buildFilterForGTM(FilterGroupsCatalogDTO $filterGroups)
  260.     {
  261.         $arr = [];
  262.         foreach ($filterGroups->getGroups() as $gr) {
  263.             $grAltName $gr->getGroupAltName();
  264.             if (empty($arr[$grAltName])) {
  265.                 $arr[$grAltName] = [];
  266.             }
  267.             foreach ($gr->getListFilters() as $f) {
  268.                 $arr[$grAltName][] = [
  269.                     'id' => $f->getId(),
  270.                     'keyId' => $f->getKeyId(),
  271.                     'key' => $f->getKey(),
  272.                     'group' => $grAltName,
  273.                     'altName' => $f->getAltName(),
  274.                 ];
  275.             }
  276.         }
  277.         $this->filtersGTM $arr;
  278.     }
  279.     /**
  280.      * массив для GTM
  281.      * https://te.remote.team/#/discus/0CA30156-F404-63A9-2515-38E75FB7947B/
  282.      * @return array
  283.      */
  284.     public function getFiltersGTM()
  285.     {
  286.         return $this->filtersGTM;
  287.     }
  288.     /**
  289.      * @param $data
  290.      * @param $tpl
  291.      *
  292.      * @return string|Response
  293.      * @throws Exception
  294.      */
  295.     private function renderH1($data$tpl)
  296.     {
  297.         switch ($tpl) {
  298.             case 'factory-header':
  299.                 $result $this->render(
  300.                     '@Web/Catalog/factory-header.html.twig',
  301.                     $data
  302.                 );
  303.                 break;
  304.             case 'one-header':
  305.                 $result $this->render(
  306.                     '@Web/Catalog/one-header.html.twig',
  307.                     $data
  308.                 );
  309.                 break;
  310.             case 'new-header':
  311.                 $result $this->render(
  312.                     '@Web/Catalog/new-header.html.twig',
  313.                     $data
  314.                 );
  315.                 break;
  316.             case 'header':
  317.                 $result $this->render(
  318.                     '@Web/Catalog/header.html.twig',
  319.                     $data
  320.                 );
  321.                 break;
  322.             default:
  323.                 $result $this->render(
  324.                     '@Web/Catalog/one-header.html.twig',
  325.                     $data
  326.                 );
  327.         }
  328.         $result htmlspecialchars_decode($result);
  329.         return $result;
  330.     }
  331.     /**
  332.      * @param FiltersService $filterService
  333.      * @param $title
  334.      *
  335.      * @return mixed|string|Response
  336.      * @throws Exception
  337.      */
  338.     private function buildH1(FiltersService $filterService$title)
  339.     {
  340.         $aCurFilters $filterService->getCurFilters();
  341.         $aCurFilter = (count($aCurFilters) >= 1) ? $aCurFilters[0] : $aCurFilters;
  342.         if ($this->isOneFilter()) {
  343.             if ($aCurFilter->isBrand()) {
  344.                 $brand $aCurFilter->getBrand();
  345.                 $countryName '';
  346.                 $countryUrl '';
  347.                 if ($brand->getCountry()) {
  348.                     $countryName $brand->getCountry()
  349.                         ->getName();
  350.                     $countryUrl $this->generateUrl(
  351.                         'app_catalog',
  352.                         [
  353.                             'key' => $brand->getCountry()
  354.                                 ->getSlug()
  355.                         ]
  356.                     );
  357.                 }
  358.                 $data = [
  359.                     'factory' => $brand,
  360.                     'typeKey' => Factory::TYPES_KEY[$brand->getType()],
  361.                     'country' => [
  362.                         'name' => $countryName,
  363.                         'url' => $countryUrl,
  364.                     ],
  365.                     'editLink' => Adm::linkEdit('adm.brand.edit'$aCurFilter->getId()),
  366.                     'biLink' => Adm::linkEdit('bi.factory.edit'$brand->getId()),
  367.                 ];
  368.                 $h1 $this->renderH1($data'factory-header');
  369.             } elseif ($this->isSort()) {
  370.                 $isTop false;
  371.                 if (RequestHelper::getCurRoute() == 'app_catalog') {
  372.                     $isTop = (!$this->isTop('also_coll_viewed') and $this->isTop()) ? $this->isTop() : false;
  373.                 }
  374.                 $h1 $this->renderH1(['header' => $title'isTop' => $isTop], 'one-header');
  375.             } elseif ($aCurFilter->isRootCataloge()) {
  376.                 $h1 $this->renderH1(['header' => $title], 'new-header');
  377.             } else {
  378.                 $isTop false;
  379.                 if (RequestHelper::getCurRoute() == 'app_catalog') {
  380.                     $isTop = (!$this->isTop('also_coll_viewed') and $this->isTop()) ? $this->isTop() : false;
  381.                 }
  382.                 $h1 $this->renderH1(['header' => $title'isTop' => $isTop], 'one-header');
  383.             }
  384.         } else {
  385.             // оборачиваем $title в описание строки фильтрации
  386.             $h1 $this->setCountResultNew($title$this->getResultCount());
  387.         }
  388.         return $h1;
  389.     }
  390.     /**
  391.      * Поиск по Массиву значений старого образца вида: ['getColors'=>[3], 'getFacturas'=>[5]]
  392.      * @throws Exception
  393.      */
  394.     private function loadByData(FiltersService $filterService)
  395.     {
  396.         if (!$this->getSearchFilter()) {
  397.             // залипуха для главной каталога
  398.             $filterService->loadByUrlKey('cataloge'$this->localeVo->getCode());
  399.         } else {
  400.             // загружаем данные строки в FiltersService
  401.             $filterService->loadBySearchData($this->getSearchFilter());
  402.             // из результата получаем новый searchFilter
  403.             $searchFilter $filterService->getSearchFilter();
  404.             $this->setSearchFilter($searchFilter);
  405.         }
  406.     }
  407.     /**
  408.      * @param FiltersService $filterService
  409.      * @param string|null $key
  410.      * @param string|null $subKey
  411.      *
  412.      * @return bool
  413.      * @throws Exception
  414.      */
  415.     private function loadByKeyFilters(FiltersService $filterService, ?string $key null, ?string $subKey null)
  416.     {
  417.         $lc $this->localeVo->getCode();
  418.         // залипуха для главной каталога
  419.         if (!$key) {
  420.             $filterService->loadByUrlKey('cataloge'$lc);
  421.             return true;
  422.         }
  423.         $keyNorm $this->normaliseKey($key);
  424.         $keyNorm $subKey $keyNorm $this->separator $subKey $keyNorm;
  425.         // доп залипуха для главной каталога //$keyNorm = 'cataloge';
  426.         if (!$keyNorm) {
  427.             throw new NotFoundHttpException('Not Found');
  428.         }
  429.         // фикс для "бажных" ссылок, вида /fi_it/catalogue/coll/en
  430.         // отправляем их на главную каталога
  431.         if ($keyNorm == 'coll' $this->separator 'en') {
  432.             $newUrl $this->router->generate('app_catalog');
  433.             $this->setRedirect($newUrl301);
  434.             return false;
  435.         }
  436.         // загружаем данные строки в FiltersService
  437.         $filterService->loadByUrlKey($keyNorm$lc);
  438.         // из результата получаем новый key строки
  439.         $newKey $filterService->getUrlKeyStr();
  440.         if (!$newKey) {// на каталог больше не перенаправляем
  441.             throw new NotFoundHttpException('Not Found');
  442.         }
  443.         if (
  444.             (strlen($newKey) != strlen($key)) or (strlen($newKey) <= and strlen($key)) or (substr_compare(
  445.                 $key,
  446.                 $newKey,
  447.                 0,
  448.                 strlen($key)
  449.             ) != 0)
  450.         ) {
  451.             // 301 редирект на верную страницу
  452.             $newUrl $filterService->getFullUrlKeyStr($newKey);
  453.             $gclid $this->getGclid();
  454.             if ($gclid) {
  455.                 $newUrl $newUrl '?gclid=' $gclid;
  456.             }
  457.             // формирование верного редиректа для коллекций
  458.             $request App::getRequest();
  459.             if ($request->get('_route') == 'app_collection') {
  460.                 $routeParams array_merge($request->get('_route_params'), ['elementId' => $newKey]);
  461.                 $newUrl str_replace('%26''&'$this->generateUrl('app_collection'$routeParams));
  462.             }
  463.             $this->setRedirect($newUrl301);
  464.             return false;
  465.         }
  466.         return true;
  467.     }
  468.     /**
  469.      * @return FiltersService
  470.      */
  471.     public function filterService(): FiltersService
  472.     {
  473.         return $this->filterService;
  474.     }
  475.     /////////////////////////////////////////////////////////////////////////
  476.     ///геттеры и сеттеры
  477.     /////////////////////////////////////////////////////////////////////////
  478.     /**
  479.      * Получаем количество найденных резильтатов поиска из сфинкса
  480.      * @return mixed
  481.      * @throws Exception
  482.      */
  483.     public function getResultCount()
  484.     {
  485.         $result $this->getResult();
  486.         return $result['count'];
  487.     }
  488.     /**
  489.      * Получаем количество найденных резильтатов поиска из сфинкса
  490.      * @return mixed
  491.      * @throws Exception
  492.      */
  493.     public function getResultList()
  494.     {
  495.         $result $this->getResult();
  496.         return $result['list'];
  497.     }
  498.     /**
  499.      * Результат из сфинкса берем
  500.      * @return array
  501.      * @throws Exception
  502.      */
  503.     public function getResult(): ?array
  504.     {
  505.         if ($this->result !== null) {
  506.             return $this->result;
  507.         }
  508.         $data $this->getSearchData();
  509.         if ($this->isTop('top20month') || $this->isTop('top_month')) {
  510.    //         $data['searchFilter']['c_id'] = $this->getSearchTopMonth();
  511.         } elseif ($this->isTop('top_week')) {
  512.      //       $data['searchFilter']['c_id'] = $this->getSearchTopWeek();
  513.         }
  514. //APP::debugExit($data);
  515.         $limit $this->getLimit();
  516.         $page $this->getPage();
  517.         if ($this->isTop('also_coll_viewed')) {
  518.             $data['searchFilter']['c_id'] = $this->getAlsoCollViewed();
  519.         }
  520.         $oMemcache App::getMemcache();
  521.         $_measure LocaleHelper::getUserMeasure();
  522.         $_sort 'sort_' $this->getSearchSortId();
  523.         $view App::getRequest()->cookies->get('view_catalog'1);
  524.         $test App::isRole('ROLE_TEST') ? '_test' '';
  525.         $keyCache 'collection_filter_5' $this->localeVo->getCodeFull() . $this->currency $_measure md5(
  526.             json_encode($data)
  527.         ) . $limit $page $_sort App::getRequest()
  528.                 ->get('items') . $view $test;
  529.         $keyCacheAll 'collection_filter_5' $this->localeVo->getCodeFull() . $this->currency $_measure md5(
  530.             json_encode($data)
  531.         ) . $view $_sort;
  532.         $result $oMemcache->get($keyCache);
  533.         // для проверки без кеша в запрос добавляем ?debug=1
  534.         if (!$result || $this->isDebug) {
  535.             $min = ($limit $page) - $limit;
  536.             $max $limit $page;
  537.             if (!empty($data['searchFilter']['c_id'])) {
  538.                 $sphinxResult_cIds['c_ids'] = $data['searchFilter']['c_id'];
  539.                 $sphinxResult_cIds['count'] = count($data['searchFilter']['c_id']);
  540.                 if (!empty($data['searchFilter']['inside'])) {
  541.                     $limit 0;
  542.                 }
  543.             } else {
  544.                 $limit 3;
  545.                 $sphinxResult_cIds $oMemcache->get($keyCacheAll);
  546.             }      //если нет списка всех коллекций - то берем все данные выборки
  547.             $resTmp = [];
  548.             if (empty($sphinxResult_cIds)) {
  549.                 $c_id_data $data;
  550.                 $c_id_data['only_c_id'] = 1;
  551.                 $sphinxResult App::searchSphinx($c_id_data1falsetrue$this->gCounttrue$this->NoWithArticles);
  552.                 //так как получили количество то
  553.                 if ($this->gCount) {
  554.                     $allCalculate $sphinxResult['res']['calculate'] ?? $sphinxResult['calculate'];
  555.                     $this->setCalculate($allCalculate);
  556.                     $oMemcache->set($keyCache 'calc'$allCalculateMEMCACHE_COMPRESSED, (int)TimeConstant::DAY);
  557.                     $res $sphinxResult['res']['collections'] ?? $sphinxResult['collections'];
  558.                 } else {
  559.                     $res $sphinxResult['res'] ?? $sphinxResult;
  560.                 }
  561.                 $c_ids array_column($res'c_id');
  562.                 $sphinxResult_cIds = ['c_ids' => $c_ids'count' => count($res)];
  563.                 $oMemcache->set($keyCacheAll$sphinxResult_cIdsMEMCACHE_COMPRESSED, (int)TimeConstant::DAY);
  564.             }
  565.             if (1) {
  566.                 // уже есть то берем только нужную порцию если не выбрана уже
  567.                 if (empty($data['searchFilter']['c_id'])) {
  568.                     $data['searchFilter']['c_id'] = array_slice($sphinxResult_cIds['c_ids'], $min$max $min);
  569.                 }
  570.                 $count $sphinxResult_cIds['count'];
  571.                 $sphinxResult App::searchSphinx($data$limitfalsetrue$this->gCountfalse$this->NoWithArticles);
  572.                 //так как получили количество то
  573.                 if ($this->gCount) {
  574.                     $res $sphinxResult['res']['collections'] ?? $sphinxResult['collections'];
  575.                 } else {
  576.                     $res $sphinxResult['res'] ?? $sphinxResult;
  577.                 }
  578.                 foreach ($res as $i => $item) {
  579.                     $discontinued false;
  580.                     if ((!empty($data['c_satus']) && $data['c_satus'] == BiConst::STATE_DISCONTINUED)) {
  581.                         $discontinued true;
  582.                     }
  583.                     $item['discontinued'] = $discontinued;
  584.                     if ($item['author']) {
  585.                         $item['author'] = str_replace(','', '$item['author']);
  586.                     }
  587.                     $resTmp[$i] = $item;
  588.                 }
  589.             }
  590.             $recommendationsAiService = new RecommendationsAiService();
  591.             foreach ($resTmp as $i => $r) {
  592.                 $collId $r['c_id'];
  593.                 // получение ALT для интерьеров
  594.                 if (!empty($r['interiors'])) {
  595.                     $interiorIds array_column($r['interiors'], 'i_id');
  596.                     $interiorsValue $this->interiorRepo->getInteriorsForCollection($collId, ['ids' => $interiorIds]);
  597.                     $interiorsValue array_combine(array_column($interiorsValue'id'), $interiorsValue);
  598.                     $dataColl = [
  599.                         'url' => $r['c_url'],
  600.                         'header' => $r['c_header'],
  601.                         'name' => $r['c_name'],
  602.                         'alternateName' => $r['c_name'],
  603.                         'factory' => [
  604.                             'url' => $r['f_url'],
  605.                             'alternateName' => $r['f_name'],
  606.                         ],
  607.                     ];
  608.                     foreach ($r['interiors'] as $ii => $interior) {
  609.                         $alt null;
  610.                         if (!empty($interiorsValue[$interior['i_id']])) {
  611.                             $interiorValue $interiorsValue[$interior['i_id']];
  612.                             $interiorAlt InteriorHelper::getInteriorDetails($interiorValue$dataColl$ii);
  613.                             $alt $interiorAlt['alt'];
  614.                             $alt is_array($alt) ? array_shift($alt) : $alt;
  615.                         }
  616.                         $resTmp[$i]['interiors'][$ii]['alt'] = $alt;
  617.                     }
  618.                 }
  619.                 // для артикулов добавляем аттрибуты
  620.                 if (!empty($r['articles'])) {
  621.                     $articleIds array_column($r['articles'], 'a_id');
  622.                     $recArticles $recommendationsAiService->getArticles(['ids' => $articleIds]);
  623.                     $recArticles array_combine(array_column($recArticles'id'), $recArticles);
  624.                     if (!empty($recArticles)) {
  625.                         foreach ($r['articles'] as $ia => $article) {
  626.                             $categories null;
  627.                             $attributes null;
  628.                             if (!empty($recArticles[$article['a_id']])) {
  629.                                 $recArticleValue $recArticles[$article['a_id']];
  630.                                 $recArticleAttr $recommendationsAiService->buildArticleJson(
  631.                                     $recArticleValue,
  632.                                     ['code' => $this->localeVo->getCode()]
  633.                                 );
  634.                                 $categories $recArticleAttr['categories'];
  635.                                 $attributes $recArticleAttr['attributes'];
  636.                             }
  637.                             $resTmp[$i]['articles'][$ia]['categories'] = $categories;
  638.                             $resTmp[$i]['articles'][$ia]['attributes'] = $attributes;
  639.                         }
  640.                     }
  641.                 }
  642.             }
  643.             // собираем сами id артикулов порции
  644.             if (
  645.                 App::getRequest()
  646.                 ->get('items')
  647.             ) {
  648.                 $aIdea = [];
  649.                 foreach ($resTmp as $r) {
  650.                     foreach ($r['articles'] as $i) {
  651.                         $aIdea[] = $i['a_id'];
  652.                     }
  653.                 }
  654.                 $items $this->articleRepo->getArticleNativeOpt(['items' => $aIdea]);
  655.                 $items $this->sliderService->prepareDiscountAmountForEachArticle($items);
  656.                 foreach ($resTmp as $k => $r) {
  657.                     foreach ($r['articles'] as $n => $i) {
  658.                         $resTmp[$k]['articles'][$n] = array_merge($i$items[$i['a_id']]);
  659.                     }
  660.                 }
  661.             }
  662.             $result = [
  663.                 'count' => $count,
  664.                 'list' => $resTmp,
  665.                 'view' => $view,
  666.             ];
  667.             // данные по отзывам коллекций фабрики
  668.             if ($page <= && $this->isBrands() && $this->isOneFilter() && $res) {
  669.                 $result['fName'] = $res[0]['f_name'];
  670.                 $result['fLogo'] = PathHelper::pathGenerate('factory', ['url' => $res[0]['f_url']]);
  671.             }
  672.             // данные по отзывам коллекций фабрики
  673.             if ($page <= && $this->showReview()) {
  674.                 $result['fReview'] = $this->get('app.service.reviews')
  675.                     ->getReviewForFactory($sphinxResult['codes'] ?? []);
  676.             }
  677.             // кеш на сутки для первой страницы
  678.             if ($page <= 1) {
  679.                 $oMemcache->set($keyCache$resultMEMCACHE_COMPRESSED, (int)TimeConstant::DAY);
  680.             }
  681.         } else {
  682.             $calculate $oMemcache->get($keyCache 'calc');
  683.             if ($calculate) {
  684.                 $this->setCalculate($calculate);
  685.             }
  686.         }
  687.         $ids = [];
  688.         foreach ($result['list'] as $item) {
  689.             if (is_numeric($item['c_id'])) {
  690.                 $ids[] = (int)$item['c_id'];
  691.             }
  692.         }
  693.         $urlAttr = [];
  694.         // прикручиваем строки фильтрации к URL коллекции
  695.         $fKey $this->getSearchKey();
  696.         if ($fKey) {
  697.             $urlAttr['type'] = 'f';
  698.             $urlAttr['elementId'] = $fKey;
  699.         }
  700.         $aColl $this->collRepo->getRatings($ids);
  701.         foreach ($result['list'] as $i => $item) {
  702.             $urlAttr['factoryUrl'] = $item['f_url'];
  703.             $urlAttr['collectionUrl'] = $item['c_url'];
  704.             // убираем из фильтров, когда фильтрация по коллекции с которыми смотрят
  705.             // например /**/catalogue/coll/3245167
  706.             if ($fKey == 'coll') {
  707.                 unset($urlAttr['type']);
  708.                 unset($urlAttr['elementId']);
  709.             }
  710.             // убираем фабрику из фильтров внутри коллекции
  711.             if ($this->isBrands() && $this->isOneFilter()) {
  712.                 unset($urlAttr['type']);
  713.                 unset($urlAttr['elementId']);
  714.             }
  715.             $url App::generateUrl('app_collection'$urlAttr);
  716.             $result['list'][$i]['url'] = $url;
  717.             $rating 0;
  718.             if (!empty($aColl[$item['c_id']])) {
  719.                 $rating $aColl[$item['c_id']];
  720.             }
  721.             $result['list'][$i]['rating'] = $rating;
  722.             // проверяем наличие наград и получаем по ним данные, если есть
  723.             $result['list'][$i]['awards'] = [];
  724.             if (
  725.                 !empty($item['c_awards']) && is_array(
  726.                     $item['c_awards']
  727.                 )
  728.             ) {// #todo добавил проверку на array так как была ошибка, но надо разобраться будет
  729.                 $aAwards $this->filterRepo->getAwards($item['c_awards']);
  730.                 if ($aAwards) {
  731.                     $result['list'][$i]['awards'] = $aAwards;
  732.                 }
  733.             }
  734.             // проверяем наличие выставок и получаем по ним даныне, если есть
  735.             $exhibitions = [];
  736.             $result['list'][$i]['c_exhibition'] = $exhibitions;
  737.         }
  738.         /////////////////////////////////////////////////////////////
  739.         // данные по интерьерам добавленым в идеи пользователя
  740.         $token UserHelper::getInstance()
  741.             ->getToken();
  742.         $result['favorites'] = $this->ideaRepository->getInteriorsIdeasByToken($token);
  743.         ////////////////////////////////////////////////////////////
  744.         /// группируем по фабрикам
  745.         $translator App::getTranslator();
  746.         $groups = [];
  747.         foreach ($result['list'] as $item) {
  748.             $fName $item['f_name'];
  749.             $fUrl $this->generateUrl('app_catalog', ['key' => StrHelper::toLower($item['f_url'])]);
  750.             if (empty($groups[$fName])) {
  751.                 $countryCode strtolower($item['country_code']);
  752.                 $countryUrl $this->generateUrl('app_catalog', ['key' => $countryCode]);
  753.                 $grop = [
  754.                     'f_name' => $item['f_name'],
  755.                     'f_url' => $fUrl,
  756.                     'country' => [
  757.                         'code' => $countryCode,
  758.                         'name' => $translator->trans("country.$countryCode"),
  759.                         'url' => $countryUrl,
  760.                     ],
  761.                     'list' => [],
  762.                 ];
  763.                 $groups[$fName] = $grop;
  764.             }
  765.             $header $translator->trans(
  766.                 $item['c_header'],
  767.                 [
  768.                     '%collection%' => preg_replace('#\\(.*\\)#isUu'''$item['c_name']),
  769.                     '%factory%' => $fName,
  770.                     '%factoryUrl%' => $fUrl,
  771.                 ]
  772.             );
  773.             $item['header_html'] = $header;
  774.             $groups[$fName]['list'][] = $item;
  775.         }
  776.         $result['groups'] = $groups;
  777.         $this->result $result;
  778.         return $this->result;
  779.     }
  780.     /**
  781.      * @return int
  782.      * @throws Exception
  783.      */
  784.     public function getLimit()
  785.     {
  786.         if ($this->isTop('also_coll_viewed')) {
  787.             $this->limit 16;
  788.         }
  789.         return $this->limit;
  790.     }
  791.     /**
  792.      * @param mixed $limit
  793.      */
  794.     public function setLimit($limit)
  795.     {
  796.         if ($limit && is_numeric($limit)) {
  797.             $this->limit $limit;
  798.         }
  799.     }
  800.     /**
  801.      * Получение списка сортировки для каталога
  802.      * @return array
  803.      * @throws Exception
  804.      */
  805.     public function getSortList()
  806.     {
  807.         /** @var FilterRepository $filterRepo */
  808.         $filterRepo App::getRepository('WebBundle:FilterEntity');
  809.         $list $filterRepo->getSortListCataloge();
  810.         return $list;
  811.     }
  812.     /**
  813.      * @return int
  814.      * @throws Exception
  815.      */
  816.     public function getSearchSortId()
  817.     {
  818.         if ($this->sortId == null) {
  819.             $sort App::getSession()
  820.                 ->get('sort_catalog_tmp');
  821.             if ($sort == null) {
  822.                 $sort App::getRequest()
  823.                     ->get('sort');
  824.             }
  825.             if ($sort == null) {
  826.                 $sort App::getRequest()->cookies->get('sort_catalog');
  827.             }
  828.             if ($sort == null) {
  829.                 $sort 1;
  830.             }
  831.             $this->sortId $sort;
  832.         }
  833.         return $this->sortId;
  834.     }
  835.     public function getMetaManager(): ?MetaManager
  836.     {
  837.         return $this->metaManager;
  838.     }
  839.     /**
  840.      * @return mixed
  841.      */
  842.     public function getAmount()
  843.     {
  844.         return $this->amount;
  845.     }
  846.     /**
  847.      * Получаем H1 заголовок
  848.      */
  849.     public function getTitleHtml(): ?string
  850.     {
  851.         return $this->titleHtml;
  852.     }
  853.     /**
  854.      * получаем Html описание
  855.      */
  856.     public function getHtml(): ?string
  857.     {
  858.         return $this->html;
  859.     }
  860.     /**
  861.      * @return null
  862.      * @throws Exception
  863.      */
  864.     public function getDiscount()
  865.     {
  866.         if ($this->discount === null) {
  867.             $discount false;
  868.             $factory $this->factoryRepository->findOneBy(
  869.                 ['url' => $this->getSearchKey()]
  870.             );
  871.             if (
  872.                 $factory && $factory->getDiscount() > && $factory->getDiscountDateStart(
  873.                 ) != null && $factory->getDiscountDateEnd() != null && $factory->getDiscountDateStart()
  874.                     ->getTimestamp() <= strtotime(date('d.m.Y')) && $factory->getDiscountDateEnd()
  875.                     ->getTimestamp() > strtotime(date('d.m.Y'))
  876.             ) {
  877.                 $discount true;
  878.             }
  879.             $this->setDiscount($discount);
  880.         }
  881.         return $this->discount;
  882.     }
  883.     /**
  884.      * @param mixed $discount
  885.      */
  886.     public function setDiscount($discount)
  887.     {
  888.         $this->discount $discount;
  889.     }
  890.     /**
  891.      * @return mixed
  892.      */
  893.     public function getTop20()
  894.     {
  895.         return $this->top20;
  896.     }
  897.     /**
  898.      * @param mixed $top20
  899.      */
  900.     public function setTop20($top20)
  901.     {
  902.         $this->top20 $top20;
  903.     }
  904.     /**
  905.      * @return int
  906.      */
  907.     public function getPage()
  908.     {
  909.         return $this->page;
  910.     }
  911.     /**
  912.      * @param mixed $page
  913.      */
  914.     public function setPage($page)
  915.     {
  916.         if ($page && is_numeric($page)) {
  917.             $this->page $page;
  918.         }
  919.     }
  920.     /**
  921.      * @return mixed
  922.      */
  923.     public function getSearchFilter()
  924.     {
  925.         return $this->searchFilter;
  926.     }
  927.     /**
  928.      * @param mixed $searchFilter
  929.      */
  930.     public function setSearchFilter($searchFilter)
  931.     {
  932.         if ($searchFilter) {
  933.             $this->searchFilter $searchFilter;
  934.         }
  935.     }
  936.     /**
  937.      * @param $searchFilter
  938.      */
  939.     public function addSearchFilter($searchFilter)
  940.     {
  941.         if ($searchFilter) {
  942.             if (count($this->searchFilter) > 0) {
  943.                 $this->searchFilter array_merge($this->searchFilter$searchFilter);
  944.             } else {
  945.                 $this->searchFilter $searchFilter;
  946.             }
  947.         }
  948.     }
  949.     /**
  950.      * Получение ссылки на редактирование фильтра через сонату
  951.      * @return bool
  952.      * @throws Exception
  953.      */
  954.     public function isNoindex(): bool
  955.     {
  956.         if ($this->isSort()) {
  957.             // не реализовано
  958.             return true;
  959.         }
  960.         return false;
  961.     }
  962.     /**
  963.      * @param bool $asJson
  964.      *
  965.      * @return array|false|string
  966.      * @throws Exception
  967.      */
  968.     public function getSearchData($asJson false)
  969.     {
  970.         $this->searchData = [
  971.             'searchFilter' => $this->getSearchFilter(),
  972.             'searchSort' => $this->getSearchSortId(),
  973.             'searchPeriod' => null,
  974.             'locale' => $this->localeVo->getCode(),
  975.         ];
  976.         $searchData $asJson json_encode($this->searchDatatrue) : $this->searchData;
  977.         return $searchData;
  978.     }
  979.     /**
  980.      * @param array $searchData
  981.      */
  982.     public function setSearchData($searchData)
  983.     {
  984.         if ($searchData) {
  985.             $this->searchData $searchData;
  986.         }
  987.     }
  988.     /**
  989.      * @return null|RedirectResponse
  990.      */
  991.     public function getRedirect(): ?RedirectResponse
  992.     {
  993.         return $this->redirect;
  994.     }
  995.     /**
  996.      * @return mixed
  997.      */
  998.     public function isRedirect()
  999.     {
  1000.         return $this->redirect;
  1001.     }
  1002.     /**
  1003.      * @param     $url
  1004.      * @param int $status
  1005.      *
  1006.      * @throws Exception
  1007.      * @internal param mixed $redirect
  1008.      */
  1009.     public function setRedirect($urlint $status 302)
  1010.     {
  1011.         $this->redirect = new RedirectResponse($url$status);
  1012.         //  пишем лог, если это не ридирект с sort-tmp переменной или дизайнерского стиля
  1013.         if (!RequestHelper::get('sort-tmp')) {
  1014.             $this->writeLogRedirect($this->redirect->getTargetUrl());
  1015.         }
  1016.     }
  1017.     /**
  1018.      * @return string|null
  1019.      */
  1020.     public function getSearchKey()
  1021.     {
  1022.         return $this->searchKey;
  1023.     }
  1024.     /**
  1025.      * @param $searchKey
  1026.      */
  1027.     public function setSearchKey($searchKey)
  1028.     {
  1029.         if ($searchKey) {
  1030.             $this->searchKey $searchKey;
  1031.         }
  1032.     }
  1033.     /**
  1034.      * @return null
  1035.      */
  1036.     public function getSearchSubKey()
  1037.     {
  1038.         return $this->searchSubKey;
  1039.     }
  1040.     /**
  1041.      * @param $searchSubKey
  1042.      */
  1043.     public function setSearchSubKey($searchSubKey)
  1044.     {
  1045.         if ($searchSubKey) {
  1046.             $this->searchSubKey $searchSubKey;
  1047.         }
  1048.     }
  1049.     /**
  1050.      * @return array
  1051.      */
  1052.     public function getReplaceParentWithChildren(): array
  1053.     {
  1054.         $dataFilterSearch $this->searchFilter;
  1055.         try {
  1056.             $parent $this->filterRepo->getFiltersSubRelation(false);
  1057.         } catch (\Doctrine\DBAL\Exception $e) {
  1058.             $parent = [];
  1059.         }
  1060.         foreach ($dataFilterSearch as $key => &$values) {
  1061.             // Проверка: существует ли такой ключ в $Parent и он является массивом
  1062.             if (isset($parent[$key]) && is_array($parent[$key])) {
  1063.                 foreach ($values as $val) {
  1064.                     // Если значение существует как ключ в $Parent[$key]
  1065.                     if (array_key_exists($val$parent[$key])) {
  1066.                         //удаляем родителя
  1067.                         $dataFilterSearch[$key] = array_diff($dataFilterSearch[$key], [$val]);
  1068.                         //добавляем детей
  1069.                         $dataFilterSearch[$key] = array_merge($dataFilterSearch[$key], $parent[$key][$val]);
  1070.                     }
  1071.                 }
  1072.             }
  1073.         }
  1074.         return $dataFilterSearch;
  1075.     }
  1076.     public function getCalculate(): ?array
  1077.     {
  1078.         return $this->calculate;
  1079.     }
  1080.     public function setCalculate(?array $calculate)
  1081.     {
  1082.         if ($calculate) {
  1083.             $this->calculate $calculate;
  1084.         }
  1085.     }
  1086.     public function setGCount(bool $gCount)
  1087.     {
  1088.         $this->gCount $gCount;
  1089.     }
  1090.     public function getGCount(): bool
  1091.     {
  1092.         return $this->gCount;
  1093.     }
  1094.     public function setNoWithArticles(bool $NoWithArticles): void
  1095.     {
  1096.         $this->NoWithArticles $NoWithArticles;
  1097.     }
  1098.     public function getNoWithArticles()
  1099.     {
  1100.         return $this->NoWithArticles;
  1101.     }
  1102.     /**
  1103.      * Проверка на дизайнерский стиль
  1104.      */
  1105.     public function isDesignerStyle(): ?bool
  1106.     {
  1107.         if ($this->isDesignerStyle === null) {
  1108.             $this->isDesignerStyle false;
  1109.             $aCurFilters $this->filterService()
  1110.                 ->getCurFilters();
  1111.             if (count($aCurFilters) > 0) {
  1112.                 foreach ($aCurFilters as $curFilter) {
  1113.                     if ($curFilter->isDesignerStyle()) {
  1114.                         $this->isDesignerStyle true;
  1115.                         break;
  1116.                     }
  1117.                 }
  1118.             }
  1119.         }
  1120.         return $this->isDesignerStyle;
  1121.     }
  1122.     /**
  1123.      * Проверка на сортировку
  1124.      */
  1125.     public function isSort(): bool
  1126.     {
  1127.         return $this->isSort ?? false;
  1128.     }
  1129.     public function isBrands(): bool
  1130.     {
  1131.         $aCurFilters $this->filterService()
  1132.             ->getCurFilters();
  1133.         if (count($aCurFilters) > 0) {
  1134.             foreach ($aCurFilters as $curFilter) {
  1135.                 if ($curFilter->isBrand()) {
  1136.                     return true;
  1137.                 }
  1138.             }
  1139.         }
  1140.         return false;
  1141.     }
  1142.     public function isDesigner(): bool
  1143.     {
  1144.         $aCurFilters $this->filterService()
  1145.             ->getCurFilters();
  1146.         if (count($aCurFilters) > 0) {
  1147.             foreach ($aCurFilters as $curFilter) {
  1148.                 if ($curFilter->isDesignerUser()) {
  1149.                     return true;
  1150.                 }
  1151.             }
  1152.         }
  1153.         return false;
  1154.     }
  1155.     public function showReview(): bool
  1156.     {
  1157.         // Вернули показ только при выборе коллекции
  1158.         // https://te.remote.team/#/discus/4353F149-3BFD-EC18-F321-C8300492082A/
  1159.         $aCurFilters $this->filterService()
  1160.             ->getCurFilters();
  1161.         if (count($aCurFilters) > 0) {
  1162.             foreach ($aCurFilters as $curFilter) {
  1163.                 if ($curFilter->isBrand()) {
  1164.                     return true;
  1165.                 }
  1166.             }
  1167.         }
  1168.         return false;
  1169.     }
  1170.     /**
  1171.      * Определяем запрос топа или нет по группе каталога
  1172.      *
  1173.      * @param null $altName
  1174.      *
  1175.      * @return bool|string
  1176.      * @throws Exception
  1177.      */
  1178.     private function isTop($altName null)
  1179.     {
  1180.         $aCurFilters $this->filterService()
  1181.             ->getCurFilters();
  1182.         if (count($aCurFilters) > 0) {
  1183.             foreach ($aCurFilters as $curFilter) {
  1184.                 if ($curFilter->isTop()) {
  1185.                     if (!$altName) {
  1186.                         return $curFilter->getAltName();
  1187.                     }
  1188.                     if ($curFilter->getAltName() == $altName) {
  1189.                         return $altName;
  1190.                     }
  1191.                 }
  1192.             }
  1193.         }
  1194.         return false;
  1195.     }
  1196.     private function getAlsoCollViewed(): array
  1197.     {
  1198.         $id $this->getSearchSubKey();
  1199.         return $this->collectionAlsoViewedRepository->getCollectionAlsoViewed($id);
  1200.     }
  1201.     /**
  1202.      * Получение топ за месяц
  1203.      */
  1204.     private function getSearchTopMonth(): ?array
  1205.     {
  1206.         return $this->interiorHistoryRepository->getTopCollections(301000);
  1207.     }
  1208.     /**
  1209.      * Получение топ за неделю
  1210.      */
  1211.     private function getSearchTopWeek(): ?array
  1212.     {
  1213.         return $this->interiorHistoryRepository->getTopCollections(71000);
  1214.     }
  1215.     /**
  1216.      * @return string
  1217.      */
  1218.     public function getGclid()
  1219.     {
  1220.         return $this->gclid;
  1221.     }
  1222.     /**
  1223.      * @param string $gclid
  1224.      */
  1225.     public function setGclid($gclid)
  1226.     {
  1227.         $this->gclid $gclid;
  1228.     }
  1229.     /**
  1230.      * Убираем из урл не верные символы и проводим его в нормальному виду
  1231.      *
  1232.      * @param $key
  1233.      *
  1234.      * @return mixed
  1235.      */
  1236.     private function normaliseKey($key)
  1237.     {
  1238.         $keyNorm trim($key);
  1239.         $keyNorm StrHelper::toLower($keyNorm);
  1240.         $keyNorm urldecode($keyNorm);
  1241.         // Совместимость: старые URL с '&' приводим к новому разделителю (например '--')
  1242.         if ($this->separatorOld && $this->separatorOld !== $this->separator) {
  1243.             $keyNorm str_replace($this->separatorOld$this->separator$keyNorm);
  1244.         }
  1245.         ////////////////////////////////////////////////////////////////////
  1246.         $aBadKey = ['spal\'nya''tile_for_kids''non-slip''terakota-cotto-efekt'];
  1247.         $aGoodKey = ['spalnya''spaces-for-children''antislip''terracotta-effetto'];
  1248.         $keyNorm str_replace($aBadKey$aGoodKey$keyNorm);
  1249.         ////////////////////////////////////////////////////////////////////
  1250.         $aBadSimbol = [
  1251.             '%C3%BC',
  1252.             '%26',
  1253.             '_',
  1254.             'antypoślizgowość',
  1255.             ' ',
  1256.             '%20',
  1257.             'ã±',
  1258.             'ñ',
  1259.             'ã³',
  1260.             'å„',
  1261.             'å‚',
  1262.             'ã¨',
  1263.             'ã©',
  1264.             '%C5%82',
  1265.             'ł',
  1266.             '\'a=0'
  1267.         ];
  1268.         $aGoodSimbol = ['ü''&''-''antypoślizgowe''-''-''n''n''ó''ń''ł''è''é''l''l'''];
  1269.         $keyNorm str_replace($aBadSimbol$aGoodSimbol$keyNorm);
  1270.         // разбираем строку запроса и собираем ее нормальную снова
  1271.         $aKeyNorm explode($this->separator$keyNorm);
  1272.         $keyNorm '';
  1273.         foreach ($aKeyNorm as $item) {
  1274.             $item trim($item'-');
  1275.             $keyNorm .= $this->separator $item;
  1276.         }
  1277.         $keyNorm trim($keyNorm$this->separator);
  1278.         $keyNorm explode($this->separator$keyNorm);
  1279.         if (count($keyNorm) > 1) {
  1280.             $keyNormTmp = [];
  1281.             foreach ($keyNorm as $keyVal) {
  1282.                 if ($keyVal != 'coll') {
  1283.                     $keyNormTmp[] = trim($keyVal);
  1284.                 }
  1285.             }
  1286.             $keyNorm implode($this->separator$keyNormTmp);
  1287.         } else {
  1288.             $keyNorm implode($this->separator$keyNorm);
  1289.         }
  1290.         return $keyNorm;
  1291.     }
  1292.     /**
  1293.      * Формируем отображение слова collections для результата поиска со скланениями и учетом локали
  1294.      *
  1295.      * @param $settings
  1296.      * @param $count
  1297.      * @param null $locale
  1298.      *
  1299.      * @return string|Response
  1300.      * @throws Exception
  1301.      */
  1302.     public function setCountResultNew($settings$count$locale null)
  1303.     {
  1304.         $locale $locale ?: $this->localeVo->getCode();
  1305.         $strCollCount App::getTranslator()
  1306.             ->trans(
  1307.                 'catalog.settings.count_result',
  1308.                 [
  1309.                     '%count%' => App::plural($count),
  1310.                     '%cnt%' => $count,
  1311.                 ],
  1312.                 null,
  1313.                 $locale
  1314.             );
  1315.         $text $this->renderH1(
  1316.             [
  1317.                 'settings' => $settings,
  1318.                 'strCollCount' => $strCollCount,
  1319.                 'count' => $count,
  1320.             ],
  1321.             'header'
  1322.         );
  1323.         return $text;
  1324.     }
  1325.     /**
  1326.      * Формируем отображение слова collections для результата поиска со скланениями и учетом локали
  1327.      *
  1328.      * @param        $count
  1329.      * @param string|null $locale
  1330.      *
  1331.      * @return null
  1332.      * @throws Exception
  1333.      */
  1334.     public function setResultAmount($count, ?string $locale null)
  1335.     {
  1336.         if ($this->isTop('novelty')) {
  1337.             return null;
  1338.         }
  1339.         if ($this->isBrands() && $this->isOneFilter()) {
  1340.             return null;
  1341.         }
  1342.         $locale $locale ?: $this->localeVo->getCode();
  1343.         return App::getTranslator()
  1344.             ->trans(
  1345.                 'catalog.settings.count_result',
  1346.                 [
  1347.                     '%count%' => App::plural($count),
  1348.                     '%cnt%' => $count,
  1349.                 ],
  1350.                 null,
  1351.                 $locale
  1352.             );
  1353.     }
  1354.     /**
  1355.      * Проверяем наличие фильтра releaseYear на странице
  1356.      * @return bool
  1357.      */
  1358.     public function isPageReleaseYear()
  1359.     {
  1360.         $aCurFilters $this->getSearchFilter();
  1361.         return !empty($aCurFilters['releaseYear']);
  1362.     }
  1363.     /**
  1364.      * Проверяем наличие фильтра exhibition на странице
  1365.      * @return bool
  1366.      */
  1367.     public function isPageExhibition()
  1368.     {
  1369.         $aCurFilters $this->getSearchFilter();
  1370.         return !empty($aCurFilters['exhibition']);
  1371.     }
  1372.     /**
  1373.      * ПРоверка на одиночный фильтр быстрых образцов
  1374.      * @return bool
  1375.      * @throws Exception
  1376.      */
  1377.     public function isPageExpressSample(): bool
  1378.     {
  1379.         if (!$this->isOneFilter()) {
  1380.             return false;
  1381.         }
  1382.         $aCurFilters $this->getSearchFilter();
  1383.         return !empty($aCurFilters['expressSample']);
  1384.     }
  1385.     /**
  1386.      * Пишем лог по редиректам каталога
  1387.      *
  1388.      * @param $uriRedirect
  1389.      *
  1390.      * @throws Exception
  1391.      */
  1392.     private function writeLogRedirect($uriRedirect)
  1393.     {
  1394.         $idTheme '9CA6E81A-5702-F84B-94E5-27704B4CDF90';
  1395.         $idUser = ['145925377856FA72121B755145925377']; //mshirnin
  1396.         $unid null;
  1397.         $uriCat $this->generateUrl('app_catalog', ['_locale' => $this->localeVo->getCode()]);
  1398.         $oRequest RequestHelper::syRequest();
  1399.         $uriTo $oRequest->getRequestUri();
  1400.         // Проверяем, если редирект на главную каталога, то пишем в лог данные запроса.
  1401.         if ($oRequest->getRealMethod() != 'POST' and md5($uriCat) == md5($uriRedirect)) {
  1402.             $referer = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
  1403.             // если есть реферер
  1404.             if ($referer) {
  1405.                 $urlParse parse_url($referer);
  1406.                 if ($oRequest->getHost() == "tile.expert") {
  1407.                     // пишем сначала на портал
  1408.                     //////////////////////////////////////////////////////////////
  1409.                     $portalHelper App::getPortal();
  1410.                     $subject "BadRedirect $uriTo";
  1411.                     $body "<p>Битая ссылка, требуется проверка запроса и исправление, если редирект не корректный, данные:</p>" "<ul>" "<li><b>Хост</b>: " App::getDomain(
  1412.                     ) . "</li>" "<li><b>Method</b>: {$oRequest->getRealMethod()}</li>" "<li><b>Откуда</b>: {$urlParse['path']}</li>" "<li><b>Куда</b>: $uriTo</li>" "</ul>";
  1413.                     $comm $portalHelper->checkExistComm($idTheme$subject);
  1414.                     if (!$comm) {
  1415.                         $task = [
  1416.                             'subjectID' => $idTheme,
  1417.                             'subject' => $subject,
  1418.                             'body' => $body,
  1419.                             'taskPerformerLat' => $idUser,
  1420.                         ];
  1421.                         $unid $portalHelper->createTask($task);
  1422.                         $unid = !empty($unid['unid']) ? $unid['unid'] : null;
  1423.                     }
  1424.                     // пишем в файл
  1425.                     // 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"}} [] []
  1426.                     // grep -ri  'redirect_catalog' /var/www/тут домен/app/logs/ >> redirect_catalog.txt
  1427.                     //////////////////////////////////////////////////////////////
  1428.                     $logger App::getContainer()
  1429.                         ->get('logger_public');
  1430.                     $requestData json_encode(RequestHelper::get());
  1431.                     $logger->info(
  1432.                         "[redirect_catalog] unid: $unid, uriTo: $uriTo, uriRef: $referer, data: $requestData"
  1433.                     );
  1434.                 }
  1435.             }
  1436.         }
  1437.     }
  1438.     /**
  1439.      * Ссылка на расширенный поиск, только если переход был с него, либо установлен флаг showBackToAdvanced
  1440.      */
  1441.     private function getLinkToAdvancedSearchIfNecessary(?string $titleHtml): ?string
  1442.     {
  1443.         $referer RequestHelper::syRequest()->headers->get('referer');
  1444.         $showBackToAdvanced RequestHelper::syRequest()->query->get('showBackToAdvanced');
  1445.         if (($referer && strpos($referer'advanced-search') !== false) || $showBackToAdvanced) {
  1446.             $link App::getRouter()
  1447.                 ->generate(
  1448.                     'app_filters_advanced_search',
  1449.                     [
  1450.                         'c' => $this->filterService()
  1451.                             ->getUrl()
  1452.                     ]
  1453.                 );
  1454.             $link urldecode($link);
  1455.             $link $this->render(
  1456.                 '@Web/Catalog/linkback-to-filterpage.html.twig',
  1457.                 ['link' => $link]
  1458.             );
  1459.             $titleHtml $link $titleHtml;
  1460.         }
  1461.         return $titleHtml;
  1462.     }
  1463. }