src/AtechNet/CoreBundle/Controller/WizardController.php line 240

Open in your IDE?
  1. <?php
  2. namespace App\AtechNet\CoreBundle\Controller;
  3. use App\AtechNet\CoreBundle\Entity\Product;
  4. use App\AtechNet\CoreBundle\Helper\Furbify\Image;
  5. use App\AtechNet\CoreBundle\Helper\Furbify\ProductHelper;
  6. use Symfony\Component\HttpFoundation\JsonResponse;
  7. use Symfony\Component\HttpFoundation\Request;
  8. use Symfony\Component\HttpFoundation\Response;
  9. use Symfony\Component\Routing\Annotation\Route;
  10. class WizardController extends AbstractController
  11. {
  12.     public const MAIN_CATEGORY_LAPTOP = 5;
  13.     // 1.
  14.     public const L_OFFICE = 21;
  15.     // 2.
  16.     public const L_TRAVEL = 22;
  17.     public const L_INTERNET = 87;
  18.     public const L_SENIOR = 43;
  19.     // 3.
  20.     public const L_WORKSTATION = 113;
  21.     public const L_GAMER = 24;
  22.     public const MAIN_CATEGORY_PC = 4;
  23.     // 1.
  24.     public const PC_OFFICE = 116;
  25.     public const PC_AIO = 84;
  26.     // 2.
  27.     public const PC_SENIOR = 15;
  28.     public const PC_INTERNET = 86;
  29.     // 3.
  30.     public const PC_WORKSTATION = 14;
  31.     // + AIO
  32.     // 4.
  33.     public const PC_GAMER = 17;
  34.     private const WIZARD_ELEMENTS = [
  35.         'title' => 'wizard_pc_or_laptop_question',
  36.         'description' => 'wizard_select_one',
  37.         'nextSteps' => [
  38.             'title' => 'wizard_question_for_using',
  39.             'description' => 'wizard_select_max_two',
  40.             'items' => [
  41.                 'laptop' => [
  42.                     'cId' => self::MAIN_CATEGORY_LAPTOP,   // cID => category ID
  43.                     'type' => 'mc', // mc => main category
  44.                     'value' => 'laptop',    // value => for checkbox value attribute
  45.                     'title' => 'wizard_c_laptop',   // this will be passed to translator->trans()
  46.                     'description' => 'product count goes here', // in maic category select, this string will be replaced to product count number
  47.                     'icon' => 'wizard_laptop_default.svg',   // checkbox icon
  48.                     'nextSteps' => [
  49.                         'office' => [
  50.                             'cId' => [self::L_OFFICE],
  51.                             'badges' => ['work'],
  52.                             'type' => 'sc', // sc => sub category
  53.                             'value' => 'office',
  54.                             'title' => 'wizard_sc_laptop_office_title',
  55.                             'description' => 'wizard_sc_laptop_office_description',
  56.                             'icon' => 'wizard_laptop_office_work.svg',
  57.                             'filters' => [
  58.                                 // pl:  cpu => [i3, i7] <== any filter can be added in this format
  59.                                 142 => ['1357', '1359'],
  60.                             ],
  61.                             // at last page what questions to ask before price filtering,
  62.                             // these values are references for this->lastParams object items
  63.                             'lastParams' => ['screen_size'],
  64.                         ],
  65.                         'standard' => [
  66.                             'cId' => [self::L_TRAVEL, self::L_INTERNET, self::L_SENIOR],
  67.                             'badges' => ['travel', 'internet', 'senior'],
  68.                             'type' => 'sc',
  69.                             'value' => 'standard',
  70.                             'title' => 'wizard_sc_laptop_standard_title',
  71.                             'description' => 'wizard_sc_laptop_standard_description',
  72.                             'icon' => 'wizard_laptop_pc_home_use-2.svg',
  73.                             'filters' => [],
  74.                             'lastParams' => ['screen_size'],
  75.                         ],
  76.                         'powerful' => [
  77.                             'cId' => [self::L_WORKSTATION, self::L_GAMER],
  78.                             'badges' => ['workstation', 'gamer'],
  79.                             'type' => 'sc',
  80.                             'value' => 'powerful',
  81.                             'title' => 'wizard_sc_laptop_powerful_title',
  82.                             'description' => 'wizard_sc_laptop_powerful_description',
  83.                             'icon' => 'wizard-laptop-icon-workstation.svg',
  84.                             'filters' => [],
  85.                             'lastParams' => ['screen_size'],
  86.                         ],
  87.                     ],
  88.                 ],
  89.                 'pc' => [
  90.                     'cId' => self::MAIN_CATEGORY_PC,
  91.                     'type' => 'mc',
  92.                     'value' => 'pc',
  93.                     'title' => 'wizard_c_pc',
  94.                     'description' => 'count',
  95.                     'icon' => 'wizard_pc_default.svg',
  96.                     'nextSteps' => [
  97.                         'standard' => [
  98.                             'cId' => [self::PC_SENIOR, self::PC_INTERNET],
  99.                             'badges' => ['senior', 'internet'],
  100.                             'type' => 'sc',
  101.                             'value' => 'standard',
  102.                             'title' => 'wizard_sc_pc_standard_title',
  103.                             'description' => 'wizard_sc_pc_standard_description',
  104.                             'icon' => 'wizard_laptop_pc_home_use-2.svg',
  105.                             'filters' => [],
  106.                             'lastParams' => ['case_size'],
  107.                         ],
  108.                         'office' => [
  109.                             'cId' => [self::PC_OFFICE, self::PC_AIO],
  110.                             'badges' => ['work'],
  111.                             'type' => 'sc',
  112.                             'value' => 'office',
  113.                             'title' => 'wizard_sc_pc_office_title',
  114.                             'description' => 'wizard_sc_pc_office_description',
  115.                             'icon' => 'wizard_pc_default.svg',
  116.                             'filters' => [],
  117.                             'lastParams' => ['case_size'],
  118.                         ],
  119.                         'powerFul' => [
  120.                             'cId' => [self::PC_WORKSTATION, self::PC_AIO],
  121.                             'badges' => ['workstation'],
  122.                             'type' => 'sc',
  123.                             'value' => 'powerFul',
  124.                             'title' => 'wizard_sc_pc_powerful_title',
  125.                             'description' => 'wizard_sc_pc_powerful_description',
  126.                             'icon' => 'wizard-pc-workstation.svg',
  127.                             'filters' => [],
  128.                             'lastParams' => ['case_size'],
  129.                         ],
  130.                         'gamer' => [
  131.                             'cId' => [self::PC_GAMER],
  132.                             'badges' => ['gamer'],
  133.                             'type' => 'sc',
  134.                             'value' => 'gamer',
  135.                             'title' => 'wizard_sc_pc_gamer_title',
  136.                             'description' => 'wizard_sc_pc_gamer_description',
  137.                             'icon' => 'wizard-laptop-pc-gamer.svg',   // if empty, placeholder will be inserted
  138.                             'filters' => [],
  139.                             'lastParams' => ['case_size'],
  140.                         ],
  141.                     ],
  142.                 ],
  143.             ],
  144.         ],
  145.     ];
  146.     private const LAST_PARAMS = [
  147.         'screen_size' => [
  148.             'title' => 'wizard_screen_size_question', // for translator->trans()
  149.             'description' => 'wizard_screen_size_description', // for translator->trans()
  150.             'filterKey' => '12', // the screen filtering key
  151.             'items' => [
  152.                 'colTo14' => [
  153.                     'type' => 'screen_size',
  154.                     'value' => 'colTo14', // checkbox value
  155.                     'title' => 'wizard_smaller_screen',
  156.                     'description' => 'wizard_smaller_screen_description',
  157.                     'icon' => 'wizard-laptop-icon-mini.svg',
  158.                     'filters' => [609, 1219, 193, 398, 194],
  159.                 ],
  160.                 'col14' => [
  161.                     'type' => 'screen_size',
  162.                     'value' => 'col14',
  163.                     'title' => 'wizard_col14_screen',
  164.                     'description' => 'wizard_col14_screen_description',
  165.                     'icon' => 'wizard-laptop-icon-medium.svg',
  166.                     'filters' => [195, 196],
  167.                 ],
  168.                 'colFrom15' => [
  169.                     'type' => 'screen_size',
  170.                     'value' => 'colFrom15',
  171.                     'title' => 'wizard_from_col15_screen',
  172.                     'description' => 'wizard_from_col15_screen_description',
  173.                     'icon' => 'wizard-laptop-icon-large.svg',
  174.                     'filters' => [251, 674],
  175.                 ],
  176.             ],
  177.         ],
  178.         'case_size' => [
  179.             'title' => 'wizard_case_size_question',
  180.             'description' => 'wizard_screen_size_description',
  181.             'filterKey' => '1',
  182.             'items' => [
  183.                 'mini' => [
  184.                     'type' => 'case_size',
  185.                     'value' => 'mini',
  186.                     'title' => 'wizard_minipc',
  187.                     'description' => 'wizard_minipc_description',
  188.                     'icon' => 'wizard-pc-mini-pc.svg',
  189.                     'filters' => [1273, 3, 685],
  190.                 ],
  191.                 'sff' => [
  192.                     'type' => 'case_size',
  193.                     'value' => 'sff',
  194.                     'title' => 'wizard_sffpc',
  195.                     'description' => 'wizard_sffpc_description',
  196.                     'icon' => 'wizard-pc-sff.svg',
  197.                     'filters' => [1, 2],
  198.                 ],
  199.                 'tower' => [
  200.                     'type' => 'case_size',
  201.                     'value' => 'tower',
  202.                     'title' => 'wizard_towerpc',
  203.                     'description' => 'wizard_towerpc_description',
  204.                     'icon' => 'wizard-pc-tower.svg',
  205.                     'filters' => [4, 5, 250],
  206.                 ],
  207.             ],
  208.         ],
  209.     ];
  210.     private const MAX_STEP_NUMBER = 4;
  211.     /**
  212.      * @Route("/wizard", name="wizard_home", defaults={"_locale":""})
  213.      * @Route("/{_locale<%supported_locales%>?}/wizard", name="wizard_home_locale", defaults={"_locale":""})
  214.      * @param Request $request
  215.      * @return Response
  216.      * @throws \Psr\SimpleCache\InvalidArgumentException
  217.      */
  218.     public function indexAction(Request $request): Response
  219.     {
  220.         return $this->redirectToRoute('wizard_steps', [
  221.             'step' => 1,
  222.         ]);
  223.     }
  224.     /**
  225.      * @Route("/wizard/steps", name="wizard_steps", defaults={"_locale":""})
  226.      * @Route("/{_locale<%supported_locales%>?}/wizard/steps", name="wizard_steps_locale", defaults={"_locale":""})
  227.      * @param Request $request
  228.      * @return Response
  229.      * @throws \Psr\SimpleCache\InvalidArgumentException
  230.      */
  231.     public function stepsAction(Request $request): Response
  232.     {
  233.         $step = $request->query->all()['step'] ?? 0;
  234.         if ($step === 0) {
  235.             return $this->redirectToRoute('wizard_home');
  236.         }
  237.         if ($this->getEnvironment() == 'prod') {
  238.             \Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
  239.                 $scope->setUser([
  240.                     'ip_address' => $_SERVER['REMOTE_ADDR'],
  241.                 ]);
  242.             });
  243.         }
  244.         $data['seoTitle'] = 'Furbify Wizard ' . $step;
  245.         $data['header'] = $this->getHeader($request);
  246.         $data['footer'] = $this->getFooter($request);
  247.         $data['step'] = $step;
  248.         $data['stepsOptions'] = [
  249.             'currentStep' => $step,
  250.             'count' => self::MAX_STEP_NUMBER,
  251.         ];
  252.         $multiStore = $this->getMultiStoreHelper($request);
  253.         $domain = $multiStore->getDomain();
  254.         $translator = $this->getTranslator();
  255.         $data['currentWizardPage'] = [];
  256.         switch ($step) {
  257.             case 1:
  258.                 $laptopCount = $this->getProductHelper($request)->getTotalByFilters([
  259.                     'filterCategoryId' => self::MAIN_CATEGORY_LAPTOP,
  260.                 ]);
  261.                 $pcCount = $this->getProductHelper($request)->getTotalByFilters([
  262.                     'filterCategoryId' => self::MAIN_CATEGORY_PC,
  263.                 ]);
  264.                 $wizardElements = self::WIZARD_ELEMENTS;
  265.                 // Insert each main categories prouduct count
  266.                 $wizardElements['nextSteps']['items']['laptop']['description'] = $laptopCount . ' ' . $translator->trans('wizard_count_product');
  267.                 $wizardElements['nextSteps']['items']['pc']['description'] = $pcCount . ' ' . $translator->trans('wizard_count_product');
  268.                 $data['currentWizardPage'] = $wizardElements;
  269.                 break;
  270.             case 2:
  271.                 $mainCategory = $request->query->all()['mc'];
  272.                 $data['currentWizardPage']['title'] = self::WIZARD_ELEMENTS['nextSteps']['title'];
  273.                 $data['currentWizardPage']['description'] = self::WIZARD_ELEMENTS['nextSteps']['description'];
  274.                 $data['currentWizardPage']['nextSteps']['items'] = self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'];
  275.                 $data['currentWizardPage']['mc'] = $mainCategory;
  276.                 break;
  277.             case 3:
  278.                 $mainCategory = $request->query->all()['mc'];
  279.                 $subCategory = $request->query->all()['sc'];
  280.                 $data['currentWizardPage']['title'] = self::WIZARD_ELEMENTS['nextSteps']['title'];
  281.                 $data['currentWizardPage']['description'] = self::WIZARD_ELEMENTS['nextSteps']['description'];
  282.                 $data['currentWizardPage']['mc'] = $mainCategory;
  283.                 $data['currentWizardPage']['sc'] = $subCategory;
  284.                 $lastParams = self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'][$subCategory]['lastParams'];
  285.                 $priceFilters = [
  286.                     'domainId' => $domain->getId(),
  287.                     'categoryIds' => self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'][$subCategory]['cId'],
  288.                     'filterPriceMin' => null,
  289.                     'filterPriceMax' => null,
  290.                 ];
  291.                 $subCategory = self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'][$subCategory];
  292.                 $priceFilters['categoryIds'] = $subCategory['cId'];
  293.                 $priceFilters['filterBadge'] = implode(',', $subCategory['badges']);
  294.                 foreach ($lastParams as $key => $value) {
  295.                     $data['currentWizardPage']['lastParams'][$value] = self::LAST_PARAMS[$value];
  296.                 }
  297.                 $data['currentWizardPage']['nextSteps']['items'] = self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'];
  298.                 break;
  299.             case 4:
  300.                 $mainCategory = $request->query->get('mc');
  301.                 $subCategory = $request->query->get('sc');
  302.                 $selectedAttr = $request->query->get('filterAttr');
  303.                 $selectedParam = $request->query->get('filter')[0];
  304.                 $filters = [];
  305.                 $data['currentWizardPage']['mc'] = $mainCategory;
  306.                 $data['currentWizardPage']['sc'] = $subCategory;
  307.                 $data['currentWizardPage']['selectedAttribute'] = $selectedAttr;
  308.                 $data['currentWizardPage']['attributeValue'] = $selectedParam;
  309.                 $lastParams = self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'][$subCategory]['lastParams'];
  310.                 $priceFilters = [
  311.                     'domainId' => $domain->getId(),
  312.                     'categoryIds' => self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'][$subCategory]['cId'],
  313.                     'filterPriceMin' => null,
  314.                     'filterPriceMax' => null,
  315.                     'filterAttribute' => [
  316.                         self::LAST_PARAMS[$selectedAttr]['filterKey'] => implode(',', self::LAST_PARAMS[$selectedAttr]['items'][$selectedParam]['filters']),
  317.                     ],
  318.                 ];
  319.                 $subCategory = self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'][$subCategory];
  320.                 $priceFilters['categoryIds'] = $subCategory['cId'];
  321.                 $priceFilters['filterBadge'] = implode(',', $subCategory['badges']);
  322.                 $productRepo = $this->getEntityManager()->getRepository(Product::class);
  323.                 $priceFilters['filterType'] = $productRepo->getProductTypeIds($priceFilters['categoryIds']);
  324.                 $filters = $this->getProductHelper($request)->buildPriceFilterForWizard($priceFilters);
  325.                 $data['filterPriceMin'] = null;
  326.                 $data['filterPriceMax'] = null;
  327.                 $data['filtersAttrs'] = $filters;
  328.                 break;
  329.         }
  330.         $data['currency'] = $domain->getCurrency();
  331.         if ($step == self::MAX_STEP_NUMBER) {
  332.             // return rendered view
  333.             return $this->render('AtechNetCoreBundle/Wizard/lastStep.html.twig', $data);
  334.         }
  335.         // return rendered view
  336.         return $this->render('AtechNetCoreBundle/Wizard/step.html.twig', $data);
  337.     }
  338.     /**
  339.      * @Route("/wizard/result", name="wizard_result", defaults={"_locale":""})
  340.      * @Route("/{_locale<%supported_locales%>?}/wizard/result", name="wizard_result_locale", defaults={"_locale":""})
  341.      * @param Request $request
  342.      * @return Response
  343.      * @throws \Psr\SimpleCache\InvalidArgumentException
  344.      */
  345.     public function productResultAction(Request $request): Response
  346.     {
  347.         $multiStore = $this->getMultiStoreHelper($request);
  348.         $domain = $multiStore->getDomain();
  349.         $mainCategory = $request->query->all()['mc'] ?? 0;
  350.         if ($mainCategory === 0) {
  351.             return $this->redirectToRoute('wizard_home');
  352.         }
  353.         $subCategory = $request->get('sc');
  354.         $checkedFilters = (array) $request->get('filtersAttrs');
  355.         $priceMin = $request->get('setterPriceMin');
  356.         $pricemax = $request->get('setterPricemax');
  357.         $sort = $request->get('sort') ?? 'price';
  358.         $order = $request->get('order') ?? 'asc';
  359.         $filters = [];
  360.         if (!empty($checkedFilters)) {
  361.             foreach ($checkedFilters as $key => $value) {
  362.                 $v = json_decode(str_replace("'", '"', $value), true);
  363.                 $this->updateFilters($v, $key, $filters);
  364.             }
  365.         }
  366.         $data = [];
  367.         $filters['limit'] = $data['limit'] = $request->get('limit') ?? 24;
  368.         // Sort By
  369.         $data['sortOrders'] = [];
  370.         $translator = $this->getTranslator();
  371.         $data['sortOrders'] = [
  372.             [
  373.                 'text' => $translator->trans('low to high'),
  374.                 'value' => 'price-asc',
  375.                 'sort' => 'price',
  376.                 'order' => 'asc',
  377.             ], [
  378.                 'text' => $translator->trans('high to low'),
  379.                 'value' => 'price-desc',
  380.                 'sort' => 'price',
  381.                 'order' => 'desc',
  382.             ], [
  383.                 'text' => $translator->trans('Last added products'),
  384.                 'value' => 'created-desc',
  385.                 'sort' => 'created',
  386.                 'order' => 'desc',
  387.             ], [
  388.                 'text' => $translator->trans('By name'),
  389.                 'value' => 'name-asc',
  390.                 'sort' => 'name',
  391.                 'order' => 'asc',
  392.             ],
  393.         ];
  394.         $limits = [12, 24, 48, 96];
  395.         foreach ($limits as $value) {
  396.             $data['limits'][] = [
  397.                 'text' => $value,
  398.                 'value' => $value,
  399.             ];
  400.         }
  401.         $filters['filterPriceMin'] = str_replace(' ', '', $priceMin);
  402.         $filters['filterPriceMax'] = str_replace(' ', '', $pricemax);
  403.         $filters['sort'] = $data['sort'] = $sort;
  404.         $filters['order'] = $data['order'] = $order;
  405.         $this->addFiltersFromSelectedWizardElement($mainCategory, $subCategory, $filters);
  406.         $filters['subCategory'] = $subCategory;
  407.         $filters['mainCategory'] = $mainCategory;
  408.         $data['filterData'] = $filters;
  409.         $data['seoTitle'] = 'Wizard result';
  410.         $data['header'] = $this->getHeader($request);
  411.         $data['footer'] = $this->getFooter($request);
  412.         $data['currency'] = $domain->getCurrency();
  413.         $data['precision'] = $domain->getPricePrecision();
  414.         foreach ($checkedFilters as $key => $value) {
  415.             $checkedFilters[$key] = str_replace('"', "'", $value);
  416.         }
  417.         $data['filterData']['filtersAttrs'] = $checkedFilters;
  418.         $totalCount = $this->getProductList($request, $subCategory, $mainCategory, true, $domain->getId(), $filters);
  419.         $page = $request->get('page') ?? 1;
  420.         $data['filterData']['total'] = $totalCount;
  421.         $data['filterData']['page'] = $page;
  422.         $data['checkedFilters'] = $checkedFilters;
  423.         $data['pagination'] = [
  424.             'total' => $totalCount,
  425.             'page' => $page,
  426.             'limit' => $data['limit'],
  427.             'moreButton' => true,
  428.             'url' => $this->url($request)->link('category', $filters),
  429.         ];
  430.         $data['contentTitle'] = $totalCount > 0 ? $translator->trans('wizard_search_result_title') . ': ' . $totalCount : $translator->trans('No results');
  431.         return $this->render('AtechNetCoreBundle/Wizard/productResult.html.twig', $data);
  432.     }
  433.     /**
  434.      * @Route("/wizard/productCount", name="product_count", defaults={"_locale":""})
  435.      * @Route("/{_locale<%supported_locales%>?}/wizard/productCount", name="product_count_locale", defaults={"_locale":""})
  436.      * @param Request $request
  437.      * @return JsonResponse
  438.      */
  439.     public function productCountAction(Request $request): JsonResponse
  440.     {
  441.         $json = [];
  442.         $mainCategory = $request->request->get('mc');
  443.         $subCategory = $request->request->get('sc');
  444.         $checkedFilters = $request->request->get('filtersAttrs');
  445.         $filters = [];
  446.         if (!empty($checkedFilters) && $checkedFilters != 0) {
  447.             foreach ($checkedFilters as $key => $value) {
  448.                 $this->updateFilters($value, $key, $filters);
  449.             }
  450.         }
  451.         $this->addFiltersFromSelectedWizardElement($mainCategory, $subCategory, $filters);
  452.         $price = $request->request->get('price');
  453.         if (!empty($price)) {
  454.             $filters['filterPriceMin'] = str_replace(' ', '', $price['min']);
  455.             $filters['filterPriceMax'] = str_replace(' ', '', $price['max']);
  456.         }
  457.         if (!is_null($subCategory)) {
  458.             $multiStore = $this->getMultiStoreHelper($request);
  459.             $domain = $multiStore->getDomain();
  460.             $json = [
  461.                 'productCount' => $this->getProductList($request, $subCategory, $mainCategory, true, $domain->getId(), $filters),
  462.             ];
  463.         }
  464.         return new JsonResponse($json);
  465.     }
  466.     /**
  467.      * @param string $subCategory
  468.      * @param string $mainCategory
  469.      * @param array $filters
  470.      * @param bool $count
  471.      * @param Request $request
  472.      * @param int $domainId
  473.      * @return array|int|void
  474.      * @throws \Exception
  475.      */
  476.     private function getProductList(Request $request, string $subCategory, string $mainCategory, bool $count, int $domainId, array $filters = [])
  477.     {
  478.         $subCategory = self::WIZARD_ELEMENTS['nextSteps']['items'][$mainCategory]['nextSteps'][$subCategory];
  479.         $filters['categoryIds'] = $subCategory['cId'];
  480.         $filters['filterBadge'] = implode(',', $subCategory['badges']);
  481.         $productRepo = $this->getEntityManager()->getRepository(Product::class);
  482.         $filters['filterType'] = $productRepo->getProductTypeIds($filters['categoryIds']);
  483.         $productCount = $this->getProductHelper($request)->getCountForWizard($filters);
  484.         // returns with product-count if param $count is set TRUE
  485.         if ($count) {
  486.             return $productCount;
  487.         }
  488.         $placeholder = $this->getImage('placeholder.png', '')->resize(Image::PRODUCT_LIST_THUMB_WIDTH, Image::PRODUCT_LIST_THUMB_HEIGHT);
  489.         $filters['start'] = $filters['page'] > 1 ? ($filters['page'] - 1) * $filters['limit'] : 0;
  490.         $productList = $this->getProductHelper($request)->getStructuredDataByFilters($filters);
  491.         $i = 0;
  492.         $products = [];
  493.         foreach ($productList as $result) {
  494.             $image = $placeholder;
  495.             if (is_object($result['images'])) {
  496.                 $image = $this->getImage($result['images'], 'product/' . $result['code'])->resize(Image::PRODUCT_LIST_THUMB_WIDTH, Image::PRODUCT_LIST_THUMB_HEIGHT);
  497.             }
  498.             $products[] = [
  499.                 'id' => $result['id'],
  500.                 'code' => $result['code'],
  501.                 'title' => ProductHelper::getHeadingTitle($result, true),
  502.                 'vendor' => $result['vendor'],
  503.                 'vendorId' => $result['vendorId'],
  504.                 'labels' => $result['badges'],
  505.                 'rawPrice' => $result['rawPrice'],
  506.                 'price' => $result['vatPrice'],
  507.                 'rawSpecial' => ($result['isDiscounted'] ? $result['rawDiscountPrice'] : false),
  508.                 'special' => ($result['isDiscounted'] ? $result['discountVatPrice'] : false),
  509.                 'image' => $image,
  510.                 'attributes' => $result['attributes'],
  511.                 'attributesListTxt' => $result['attributesListTxt'],
  512.                 'available' => $result['availableQty'],
  513.                 'type' => $result['productType'],
  514.                 'typeId' => $result['productTypeId'],
  515.                 'href' => $this->url($request)->link('product', [
  516.                     'product_id' => $result['id'],
  517.                     'isBazaar'   => $result['isBazaar'] ?? 0
  518.                 ]),
  519.                 'active' => $result['active'],
  520.                 'timedSalesActive' => $result['timedSalesActive'],
  521.                 'reviewCount' => $result['reviewCount'],
  522.                 'reviewStars' => $result['reviewStars'],
  523.                 'reviews' => $result['reviews'],
  524.                 'freeShipping' => $result['freeShipping'],
  525.                 'warrantyYear' => $this->getWarrantyForProductType($domainId, $result['productTypeId'])['year'],
  526.                 'labelOS' => $this->getProductHelper($request)->getOSLabel($result),
  527.             ];
  528.             $i++;
  529.         }
  530.         return $products;
  531.     }
  532.     /**
  533.      * merge all filter attributes from items[], implodes array items to string and pushes the created filter-string under $filters[filterAttribute]
  534.      * @param array $items
  535.      * @param string $paramName
  536.      * @param array &$filters
  537.      * @return void
  538.      */
  539.     private function updateFilters(array $items, string $paramName, array &$filters): void
  540.     {
  541.         $attributes = [];
  542.         foreach ($items as $key => $value) {
  543.             array_push($attributes, self::LAST_PARAMS[$paramName]['items'][$value]['filters']);
  544.         }
  545.         $attributes = array_merge([], ...$attributes);
  546.         $filters['filterAttribute'][self::LAST_PARAMS[$paramName]['filterKey']] = implode(",", $attributes);
  547.     }
  548.     /**
  549.      * loops over in $subCategoryArray(selected subcategories) and uses defined filters[] items to create/update $filters[filterAttribute]
  550.      * @param string $mainCategory
  551.      * @param string $subCategory
  552.      * @param array &$filters
  553.      * @return void
  554.      */
  555.     private function addFiltersFromSelectedWizardElement(string $mainCategory, string $subCategory, array &$filters): void
  556.     {
  557.         $wizardElements = self::WIZARD_ELEMENTS;
  558.         $actualScFilters = $wizardElements['nextSteps']['items'][$mainCategory]['nextSteps'][$subCategory]['filters'];
  559.         if (!empty($actualScFilters)) {
  560.             $fa = $filters['filterAttribute'] ?? [];
  561.             foreach ($actualScFilters as $key => $fValues) {
  562.                 $fResultValues = $fValues;
  563.                 // if filter['filterAttribute'] has key => explode > merge > arrray_unique > implode
  564.                 if (isset($fa[$key])) {
  565.                     $actualFilterKey = explode(",", $fa[$key]);
  566.                     $fResultValues = array_unique(array_merge([], $actualFilterKey, $fValues));
  567.                 }
  568.                 $fa[$key] = implode(",", $fResultValues);
  569.             }
  570.             $filters['filterAttribute'] = $fa;
  571.         }
  572.     }
  573.     /**
  574.      * @Route("/wizard/get/product-list-ajax", name="wizard_product_list_ajax", defaults={"_locale":""})
  575.      * @Route("/{_locale<%supported_locales%>?}/wizard/get/product-list-ajax", name="wizard_product_list_ajax_locale", defaults={"_locale":""})
  576.      * @param Request $request
  577.      * @return JsonResponse
  578.      * @throws \Exception
  579.      */
  580.     public function productListAjaxAction(Request $request): JsonResponse
  581.     {
  582.         $domain = $this->getMultiStoreHelper($request)->getDomain();
  583.         $filterData = $request->query->all();
  584.         $checkedFilters = $request->get('checkedFilters') ?? null;
  585.         $data['products'] = [];
  586.         $productsCacheKey = 'wizard.products.' . $domain->getId() . '.' . $domain->getLanguage()->getCode() . '.' . md5(serialize($filterData));
  587.         $cacheItem = $this->cache->getItem($productsCacheKey);
  588.         if ($cacheItem->isHit()) {
  589.             $data['products'] = $cacheItem->get();
  590.         } else {
  591.             $data['products'] = $this->getProductList($request, $filterData['subCategory'], $filterData['mainCategory'], false, $domain->getId(), $filterData);
  592.             $cacheItem->set($data['products']);
  593.             $cacheItem->expiresAfter(self::CACHE_LIFETIME_30_MINUTES);
  594.             $this->cache->save($cacheItem);
  595.         }
  596.         $data['currency'] = $domain->getCurrency();
  597.         $data['precision'] = $domain->getPricePrecision();
  598.         $arguments = [
  599.             'mc' => $filterData['mainCategory'],
  600.             'sc' => $filterData['subCategory'],
  601.             'setterPriceMin' => $filterData['filterPriceMin'],
  602.             'setterPricemax' => $filterData['filterPriceMax'],
  603.             'sort' => $filterData['sort'],
  604.             'order' => $filterData['order'],
  605.             'filtersAttrs' => $checkedFilters,
  606.             'step' => 4,
  607.         ];
  608.         $arguments['page'] = '{page}';
  609.         $data['pagination'] = [
  610.             'total' => $filterData['total'],
  611.             'page' => $request->get('page') ?? 1,
  612.             'limit' => $filterData['limit'],
  613.             'moreButton' => true,
  614.             'url' => $this->url($request)->link('wizard_result', $arguments),
  615.         ];
  616.         $data['col'] = 3;
  617.         return new JsonResponse($data);
  618.     }
  619. }