src/DcSiteBundle/Controller/BaseDcController.php line 251

Open in your IDE?
  1. <?php
  2. namespace DcSiteBundle\Controller;
  3. use CoreBundle\Component\CoreFormFactory;
  4. use CoreBundle\Component\FormManager;
  5. use CoreBundle\Controller\ViDiController;
  6. use CoreBundle\Entity\Dealer;
  7. use CoreBundle\Entity\Model;
  8. use CoreBundle\Factory\Vehicle as VehicleFactory;
  9. use CoreBundle\Model\Api\OnlineService\ApiServer1C;
  10. use CoreBundle\Model\Vehicles\AbstractVehicle;
  11. use CoreBundle\Model\Vehicles\InStockVehicle;
  12. use CoreBundle\Model\Vehicles\Repository;
  13. use CoreBundle\Model\Vehicles\Vehicle;
  14. use CoreBundle\Services\MediaExtensionVidi;
  15. use DateTime;
  16. use Doctrine\ORM\EntityManagerInterface;
  17. use Exception;
  18. use MyBundle\Entity\Basket;
  19. use PortalBundle\Model\SeoMetaTag;
  20. use Symfony\Component\Filesystem\Filesystem;
  21. use Symfony\Component\HttpFoundation\JsonResponse;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\RequestStack;
  24. use Symfony\Component\HttpFoundation\Response;
  25. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  26. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  27. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  28. use Symfony\Component\Routing\RouterInterface;
  29. use Symfony\Component\String\UnicodeString;
  30. use Twig\Environment;
  31. class BaseDcController extends ViDiController
  32. {
  33.     protected SeoMetaTag $seoMetaTag;
  34.     protected RequestStack $requestStack;
  35.     protected RouterInterface $router;
  36.     protected FormManager $formManager;
  37.     protected EntityManagerInterface $em;
  38.     protected ApiServer1C $apiServer1C;
  39.     protected SessionInterface $session;
  40.     protected Filesystem $filesystem;
  41.     protected MediaExtensionVidi $mediaExtensionVidi;
  42.     protected Repository $vehicleRepository;
  43.     protected VehicleFactory $vehicleFactory;
  44.     protected Environment $twig;
  45.     public function __construct(CoreFormFactory    $coreFormFactorySeoMetaTag $seoMetaTagRequestStack $requestStack,
  46.                                 RouterInterface    $routerFormManager $formManagerEntityManagerInterface $em,
  47.                                 ApiServer1C        $apiServer1CSessionInterface $sessionFilesystem $filesystem,
  48.                                 MediaExtensionVidi $mediaExtensionVidiRepository $vehicleRepository,
  49.                                 VehicleFactory     $vehicleFactoryEnvironment $twig)
  50.     {
  51.         parent::__construct($coreFormFactory);
  52.         $this->seoMetaTag $seoMetaTag;
  53.         $this->router $router;
  54.         $this->formManager $formManager;
  55.         $this->em $em;
  56.         $this->apiServer1C $apiServer1C;
  57.         $this->session $session;
  58.         $this->filesystem $filesystem;
  59.         $this->mediaExtensionVidi $mediaExtensionVidi;
  60.         $this->vehicleRepository $vehicleRepository;
  61.         $this->vehicleFactory $vehicleFactory;
  62.         $this->twig $twig;
  63.         $this->requestStack $requestStack;
  64.     }
  65.     const DEALER_CITY_ID 3;
  66.     /**
  67.      * @var array
  68.      */
  69.     private static $dealers = [];
  70.     private $js = [];
  71.     private $baseJs = [];
  72.     private $jsParams = [];
  73.     protected function sendSmsLead(Request $request): JsonResponse
  74.     {
  75.         $formName '';
  76.         $formPhone '';
  77.         $formData = [
  78.             'vin' => '''visit_date' => '''model' => '''brand' => '''year' => '''data' => []
  79.         ];
  80.         try {
  81.             if (empty($request->get('vin'))) {
  82.                 throw new Exception("Не вказано 'vin'");
  83.             }
  84.             if (empty($request->get('visitDate'))) {
  85.                 throw new Exception("Не вказано 'visitDate'");
  86.             }
  87.             $formData['vin'] = $request->get('vin');
  88.             $formData['visit_date'] = str_replace('%''-'$request->get('visitDate'));
  89.             $arr $this->apiServer1C->getHistory($formData['vin']);
  90.             if (empty($arr)) {
  91.                 throw new Exception("Не знайдено '{$formData['vin']}'");
  92.             }
  93.             if (!empty($arr['ModelID'])) {
  94.                 /**
  95.                  * @var $model Model
  96.                  */
  97.                 $model $this->em
  98.                     ->getRepository(Model::class)
  99.                     ->findOneBy(['uid_1c' => $arr['ModelID']]);
  100.                 if (!empty($model)) {
  101.                     $formData['model'] = $model->getTitle();
  102.                     $formData['brand'] = $model->getBrand()->getName();
  103.                 }
  104.             }
  105.             if (!empty($arr['LastOwner'])) {
  106.                 $formName $arr['LastOwner'];
  107.             }
  108.             if (!empty($arr['Phone'])) {
  109.                 $formPhone $arr['Phone'];
  110.             }
  111.             if (!empty($arr['Year'])) {
  112.                 $formData['year'] = $arr['Year'];
  113.             }
  114.             if (!empty($arr['data'])) {
  115.                 $formData['data'] = $arr['data'];
  116.             }
  117.             $this->formManager->saveSmsLeadForm(
  118.                 $request,
  119.                 $this->getDealer(),
  120.                 $formName,
  121.                 $formPhone,
  122.                 $formData
  123.             );
  124.             return $this->success();
  125.         } catch (Exception $e) {
  126.             return $this->error($e);
  127.         }
  128.     }
  129.     protected function checkSend(Request $request): bool
  130.     {
  131.         $vin $request->get('vin');
  132.         $visitDate str_replace('%''-'$request->get('visitDate'));
  133.         $toCheck md5($vin $visitDate);
  134.         $inSes $this->session->get('nselectcheck');
  135.         $inSes is_array($inSes) ? $inSes : [];
  136.         if (!in_array($toCheck$inSes)) {
  137.             $inSes[] = $toCheck;
  138.             $this->session->set('nselectcheck'$inSes);
  139.             return false;
  140.         } else {
  141.             return true;
  142.         }
  143.     }
  144.     public function getDealer(): Dealer
  145.     {
  146.         $request $this->requestStack->getCurrentRequest();
  147.         $host $this->modifyHost($request->getHost());
  148.         if (!isset(self::$dealers[md5($host)])) {
  149.             $dealerId $request->get('dealerId');
  150.             if ($host == 'vidi.ua' && !is_null($dealerId)) {
  151.                 $findDealer = ['id' => $request->get('dealerId')];
  152.             } else {
  153.                 $host $this->isDealerShop($host);
  154.                 $findDealer = ['domain' => $host];
  155.             }
  156.             $Dealer $this->em->getRepository(Dealer::class)->findOneBy($findDealer);
  157.             if (!$Dealer) {
  158.                 throw new NotFoundHttpException();
  159.             }
  160.             self::$dealers[md5($host)] = $Dealer;
  161.         }
  162.         return self::$dealers[md5($host)];
  163.     }
  164.     private function isDealerShop($host)
  165.     {
  166.         return str_replace("shop."""$host);
  167.     }
  168.     protected function webpUpdate(Request $request$url$manager)
  169.     {
  170.         $userAgent $request->server->get('HTTP_USER_AGENT');
  171.         preg_match("/(MSIE|Opera|Firefox|Chrome|Version)(?:\/| )([0-9.]+)/"$userAgent$browser_info);
  172.         $browser $browser_info[1] ?? false;
  173.         if (!$browser) {
  174.             return $url;
  175.         }
  176.         if ($browser == 'Version' || $browser == 'MSIE') {
  177.             return $url;
  178.         }
  179.         $basePath $this->getParameter('kernel.root_dir') . '/../public';
  180.         $path $basePath $url '.webp';
  181.         if (file_exists($path)) {
  182.             return $manager->getUrl($url '.webp');
  183.         }
  184.         $im = @imagecreatefrompng($basePath $url);
  185.         if (!$im) {
  186.             $im = @imagecreatefromjpeg($basePath $url);
  187.         }
  188.         if ($im) {
  189.             $newUrl $url '.webp';
  190.             if (imagewebp($im$path)) {
  191.                 imagedestroy($im);
  192.                 return $manager->getUrl($newUrl);
  193.             } else {
  194.                 imagedestroy($im);
  195.                 return $manager->getUrl($url);
  196.             }
  197.         }
  198.         return $url;
  199.     }
  200.     private function modifyHost($host): mixed
  201.     {
  202.         if ($_ENV['APP_ENV'] == 'dev') {
  203.             $host str_replace($this->getParameter('host'), 'vidi.ua'$host);
  204.         }
  205.         return $host;
  206.     }
  207.     protected function baseDcRender($view, array $parameters = [], Response $response null): ?Response
  208.     {
  209.         $request $this->requestStack->getCurrentRequest();
  210.         $dealer $this->getDealer();
  211.         $seoMeta $this->seoMetaTag->getSeoMeta($request);
  212.         $subdomain explode('.'$request->server->get('HTTP_HOST'))[0];
  213.         $basketCount = [];
  214.         if ($this->getUser()) {
  215.             $basketCount $this->em->getRepository(Basket::class)->getBasketCountByDealerUser($this->getDealer(), $this->getUser());
  216.         } else {
  217.             $guestBasket $this->session->get('guest_basket', []);
  218.             if ($guestBasket && isset($guestBasket[$this->getDealer()->getId()])) {
  219.                 $basketCount['count'] = count($guestBasket[$this->getDealer()->getId()]);
  220.             }
  221.         }
  222.         $basketCount = ($basketCount) ? $basketCount['count'] : 0;
  223.         $isSubdomainShop $subdomain === "shop";
  224.         $utmData $this->getUtmData();
  225.         $parameters array_merge($parameters, [
  226.             'dealer' => $this->getDealer(),
  227.             'basketCount' => $basketCount,
  228.             'dealerCityNihgtBooking' => self::DEALER_CITY_ID,
  229.             'backLoginUrl' => $request->getUri(),
  230.             'seoMeta' => $seoMeta,
  231.             'isSubdomainShop' => $isSubdomainShop,
  232.             'currentYear' => (new DateTime('now'))->format('Y'),
  233.             'utmData' => $utmData,
  234.         ]);
  235.         $helpCrunchParams $this->getParameter('help_crunch_params')[$dealer->getUniqueId()] ?? null;
  236.         if ($helpCrunchParams) {
  237.             $parameters array_merge($parameters, ['helpCrunch' => $helpCrunchParams]);
  238.         }
  239.         $content $this->twig->render($view$parameters);
  240.         if (null === $response) {
  241.             $response = new Response();
  242.         }
  243.         return $response->setContent($content);
  244.     }
  245.     protected function getMpdfTempDir(): string
  246.     {
  247.         $newTempDir $this->getParameter('file_directory_mpdf');
  248.         if (!$this->filesystem->exists($newTempDir)) {
  249.             $this->filesystem-- > mkdir($newTempDir0777);
  250.         }
  251.         return $newTempDir;
  252.     }
  253.     public function getCarStructuredDataCarInStock(Request $requestInStockVehicle $item): bool|string
  254.     {
  255.         $twig $this->mediaExtensionVidi;
  256.         $router $this->router;
  257.         $routerCurrent $router->getMatcher()->match($router->getContext()->getPathInfo())['_route'];
  258.         $routerCurrentParams = [
  259.             'car' => $item->getUrl(),
  260.             'url' => $item->getUrl(),
  261.             'category' => $item->getCategory()->getUrl()
  262.         ];
  263.         if ($routerCurrent == 'suzuki_card_moto') {
  264.             $routerCurrentParams = [
  265.                 'moto' => $item->getUrl()
  266.             ];
  267.         }
  268.         $images = [];
  269.         $gallery $item->getGallery();
  270.         if (!empty($gallery)) {
  271.             $gallery $gallery->getGalleryItems();
  272.             if (!empty($gallery)) {
  273.                 $images $gallery->toArray();
  274.             }
  275.         }
  276.         $images array_reduce($images, function ($rows$row) use ($request$twig) {
  277.             $rows[] = $request->getSchemeAndHttpHost() . $twig->getPath($row->getMedia(), 'reference');
  278.             return $rows;
  279.         }, [$request->getSchemeAndHttpHost() . $twig->getPath($item->getPreview(), 'reference')]);
  280.         $characteristics $item->getCharacterisaticsByGroup($request->getLocale());
  281.         $characteristicsSize $characteristics[1] ?? [];
  282.         $characteristicsSizeNumberOfDoors array_filter($characteristicsSize['characteristics'], fn($row) => $row['id'] === 15);
  283.         $characteristicsSizeNumberOfDoors array_values($characteristicsSizeNumberOfDoors);
  284.         $characteristicsSizeVehicleSeatingCapacity array_filter($characteristicsSize['characteristics'], fn($row) => $row['id'] === 20);
  285.         $characteristicsSizeVehicleSeatingCapacity array_values($characteristicsSizeVehicleSeatingCapacity);
  286.         switch ($item->getDriveUnitType()->getId()) {
  287.             case 13:
  288.                 $driveWheelConfiguration 'AllWheelDriveConfiguration'// Повний
  289.                 break;
  290.             case 3:
  291.                 $driveWheelConfiguration 'FrontWheelDriveConfiguration'// Передній
  292.                 break;
  293.             case 11:
  294.                 $driveWheelConfiguration 'RearWheelDriveConfiguration'// Задній
  295.                 break;
  296.             case 27:
  297.                 $driveWheelConfiguration 'FourWheelDriveConfiguration'// AWD
  298.                 break;
  299.             default:
  300.                 $driveWheelConfiguration 'undefined';
  301.                 break;
  302.         }
  303.         return json_encode([
  304.             "@context" => "http://schema.org",
  305.             "@type" => "Car",
  306.             "name" => $item->getFullName(),
  307.             "vehicleIdentificationNumber" => $item->getVin() ?? '00000000000000000',
  308.             "image" => $images,
  309.             "url" => $router->generate($routerCurrent$routerCurrentParamsUrlGeneratorInterface::ABSOLUTE_URL),
  310.             "offers" => [
  311.                 "@type" => "Offer",
  312.                 "availability" => "http://schema.org/InStock",
  313.                 "price" => $item->price(),
  314.                 "priceCurrency" => "UAH"
  315.             ],
  316.             "itemCondition" => "https://schema.org/NewCondition",
  317.             "brand" => [
  318.                 "@type" => "Brand",
  319.                 "name" => $item->getBrand()->getName()
  320.             ],
  321.             "model" => $item->getModelName(),
  322.             "vehicleConfiguration" => $item->getEquipment()->getTitle(),
  323.             "vehicleModelDate" => $item->getYear(),
  324.             "mileageFromOdometer" => [
  325.                 "@type" => "QuantitativeValue",
  326.                 "value" => 0,
  327.                 "unitCode" => 'KMT'
  328.             ],
  329.             "color" => $item->getColor(),
  330.             "vehicleInteriorColor" => $item->getColor(),
  331.             "vehicleInteriorType" => 'Standard',
  332.             "bodyType" => $item->getBodyTypeName($request->getLocale()),
  333.             "driveWheelConfiguration" => $driveWheelConfiguration,
  334.             "vehicleEngine" => [
  335.                 "@type" => "EngineSpecification",
  336.                 "engineType" => $item->getFuelTypeName($request->getLocale()),
  337.             ],
  338.             "vehicleTransmission" => $item->getTransmissionTypeName($request->getLocale()),
  339.             "numberOfDoors" => $characteristicsSizeNumberOfDoors[0]['value'] ?? null,
  340.             "vehicleSeatingCapacity" => $characteristicsSizeVehicleSeatingCapacity[0]['value'] ?? null
  341.         ], JSON_UNESCAPED_SLASHES);
  342.     }
  343.     public function getCarStructuredDataCarUsed(Request $requestAbstractVehicle $item): bool|string
  344.     {
  345.         $twig $this->mediaExtensionVidi;
  346.         $router $this->router;
  347.         $routerCurrent $router->getMatcher()->match($router->getContext()->getPathInfo())['_route'];
  348.         $routerCurrentParams = [
  349.             'id' => $item->getVehicleId(),
  350.             'url' => $item->getUrl()
  351.         ];
  352.         if ($routerCurrent == 'suzuki_card_moto') {
  353.             $routerCurrentParams = [
  354.                 'moto' => $item->getUrl()
  355.             ];
  356.         }
  357.         $images array_reduce(($item->getGallery()) ? $item->getGallery()->getGalleryItems()->toArray() : [], function ($rows$row) use ($request$twig) {
  358.             $rows[] = $request->getSchemeAndHttpHost() . $twig->getPath($row->getMedia(), 'reference');
  359.             return $rows;
  360.         }, [$request->getSchemeAndHttpHost() . $twig->getPath($item->getPreview(), 'reference')]);
  361.         $characteristics $item->getCharacterisaticsWithGroup($request->getLocale());
  362.         $characteristicsSize $characteristics[1] ?? [];
  363.         $characteristicsSizeNumberOfDoors array_filter($characteristicsSize['characteristics'], fn($row) => $row['id'] === 15);
  364.         $characteristicsSizeNumberOfDoors array_values($characteristicsSizeNumberOfDoors);
  365.         $characteristicsSizeVehicleSeatingCapacity array_filter($characteristicsSize['characteristics'], fn($row) => $row['id'] === 20);
  366.         $characteristicsSizeVehicleSeatingCapacity array_values($characteristicsSizeVehicleSeatingCapacity);
  367.         return json_encode([
  368.             "@context" => "http://schema.org",
  369.             "@type" => "Car",
  370.             "name" => $item->getFullName(),
  371.             "vehicleIdentificationNumber" => $item->getVIN() ?? '00000000000000000',
  372.             "image" => $images,
  373.             "url" => $router->generate($routerCurrent$routerCurrentParamsUrlGeneratorInterface::ABSOLUTE_URL),
  374.             "offers" => [
  375.                 "@type" => "Offer",
  376.                 "availability" => "http://schema.org/InStock",
  377.                 "price" => $item->price(),
  378.                 "priceCurrency" => "UAH"
  379.             ],
  380.             "itemCondition" => "https://schema.org/UsedCondition",
  381.             "brand" => [
  382.                 "@type" => "Brand",
  383.                 "name" => $item->getBrand()->getName()
  384.             ],
  385.             "model" => $item->getModelName(),
  386.             "vehicleConfiguration" => $item->getEquipment()->getTitle(),
  387.             "vehicleModelDate" => $item->getYear(),
  388.             "mileageFromOdometer" => [
  389.                 "@type" => "QuantitativeValue",
  390.                 "value" => $item->getMileage(),
  391.                 "unitCode" => 'KMT'
  392.             ],
  393.             "color" => $item->getBodyColor($request->getLocale()),
  394.             "vehicleInteriorColor" => $item->getBodyColor($request->getLocale()),
  395.             "vehicleInteriorType" => 'Standard',
  396.             "bodyType" => $item->getBodyTypeName($request->getLocale()),
  397.             "driveWheelConfiguration" => $item->getDriveUnitTypeName($request->getLocale()),
  398.             "vehicleEngine" => [
  399.                 "@type" => "EngineSpecification",
  400.                 "engineType" => $item->getFuelTypeName($request->getLocale()),
  401.             ],
  402.             "vehicleTransmission" => $item->getTransmissionTypeName($request->getLocale()),
  403.             "numberOfDoors" => $characteristicsSizeNumberOfDoors[0]['value'] ?? null,
  404.             "vehicleSeatingCapacity" => $characteristicsSizeVehicleSeatingCapacity[0]['value'] ?? null
  405.         ], JSON_UNESCAPED_SLASHES);
  406.     }
  407.     public function getCarPageStructuredData(Request $requestVehicle $vehicle): string
  408.     {
  409.         $twig $this->mediaExtensionVidi;
  410.         $router $this->router;
  411.         $currentRoute $router->getMatcher()->match($router->getContext()->getPathInfo())['_route'];
  412.         if ($vehicle->getDealer()->getUrl() !== 'land-rover') {
  413.             $homepageRoute $router->generate(
  414.                 preg_replace('([^a-zA-Z])''_'$vehicle->getDealer()->getUrl()) . '_homepage', [], UrlGeneratorInterface::ABSOLUTE_URL
  415.             );
  416.         } else {
  417.             $homepageRoute $router->generate(
  418.                 preg_replace('([^a-zA-Z])'''$vehicle->getDealer()->getUrl()) . '_homepage', [], UrlGeneratorInterface::ABSOLUTE_URL
  419.             );
  420.         }
  421.         $params = [
  422.             'car' => $vehicle->getUrl(),
  423.             'url' => $vehicle->getUrl(),
  424.             'category' => $vehicle->getCategory() ? $vehicle->getCategory()->getUrl() : '',
  425.         ];
  426.         if ($currentRoute == 'suzuki_card_moto') {
  427.             $params = ['moto' => $vehicle->getUrl()];
  428.         }
  429.         $images = [
  430.             $request->getSchemeAndHttpHost() . $twig->getPath($vehicle->getPreview(), 'reference'),
  431.         ];
  432.         $gallery $vehicle->getGallery();
  433.         if ($gallery) {
  434.             foreach ($gallery->getGalleryItems() as $galleryItem) {
  435.                 $images[] = $request->getSchemeAndHttpHost() . $twig->getPath($galleryItem->getMedia(), 'reference');
  436.             }
  437.         }
  438.         return json_encode([
  439.             "@context" => "http://schema.org",
  440.             "@type" => "Product",
  441.             "image" => $images,
  442.             "brand" => [
  443.                 "@type" => "Brand",
  444.                 "name" => $vehicle->getBrand()->getName(),
  445.             ],
  446.             "manufacturer" => [
  447.                 "@type" => "Corporation",
  448.                 "name" => $vehicle->getBrand()->getName()
  449.             ],
  450.             "description" => $vehicle->seoDescription($request->getLocale()),
  451.             "sku" => $vehicle->getVehicleId(),
  452.             "name" => $vehicle->getFullName(),
  453.             "offers" => [
  454.                 "@type" => "AggregateOffer",
  455.                 "availability" => "http://schema.org/InStock",
  456.                 "priceCurrency" => "UAH",
  457.                 "price" => $vehicle->price(),
  458.                 "lowPrice" => $vehicle->minPrice(),
  459.                 "highPrice" => $vehicle->maxPrice(),
  460.             ],
  461.             "itemCondition" => "http://schema.org/NewCondition",
  462.             "url" => $router->generate($currentRoute$paramsUrlGeneratorInterface::ABSOLUTE_URL)
  463.         ], JSON_UNESCAPED_SLASHES);
  464.     }
  465.     public function getModelMenuItems($locale$format null$categId false): array
  466.     {
  467.         $cars $this->vehicleRepository->getNewByDealer($this->getDealer());
  468.         $categories = [];
  469.         /** @var \CoreBundle\Entity\Vehicles\Vehicle $car */
  470.         foreach ($cars as $car) {
  471.             $vehicleModel $this->vehicleFactory->createByEntity($car);
  472.             if (!$vehicleModel) {
  473.                 continue;
  474.             }
  475.             $categoryId $car->getCategory() ? $car->getCategory()->getId() : null;
  476.             if ($categId && $categoryId != $categId) continue;
  477.             $key array_search($categoryIdarray_column($categories'id'));
  478.             $format $format ?: 'menu';
  479.             $oneCar = [
  480.                 'id' => $vehicleModel->getVehicleId(),
  481.                 'title' => $vehicleModel->getModelName(),
  482.                 'customTitle' => $vehicleModel->getCustomName($locale),
  483.                 'isNew' => $vehicleModel->isNew(),
  484.                 'isPreOrder' => $vehicleModel->isPreOrder(),
  485.                 'onTestDrive' => $vehicleModel->getTestDrive(),
  486.                 'position' => $vehicleModel->getPosition(),
  487.                 'url' => $vehicleModel->getUrl(),
  488.                 'hasHybrid' => $vehicleModel->hasHybrid(),
  489.                 'price' => round($vehicleModel->price()),
  490.                 'image' => $vehicleModel->getPreviewPath($format),
  491.                 'image_webp' => $vehicleModel->getPreviewPathWebp($format),
  492.                 'modelId' => $vehicleModel->getModel()->getId(),
  493.                 'preorderPrice' => $vehicleModel->getPreorderPrice(),
  494.             ];
  495.             if ($key === false && $car->getCategory()) {
  496.                 $category = [
  497.                     'id' => $categoryId,
  498.                     'title' => $vehicleModel->getCategory()->getTitle($locale),
  499.                     'position' => $vehicleModel->getCategory()->getPosition(),
  500.                     'url' => $vehicleModel->getCategory()->getUrl(),
  501.                     'cars' => []
  502.                 ];
  503.                 $category['cars'][] = $oneCar;
  504.                 $categories[] = $category;
  505.             } else {
  506.                 $categories[$key]['cars'][] = $oneCar;
  507.             }
  508.         }
  509.         $positionC = [];
  510.         foreach ($categories as $k => &$row) {
  511.             $position = [];
  512.             foreach ($row['cars'] as $key => $car) {
  513.                 $position[$key] = $car['position'];
  514.             }
  515.             $positionC[$k] = $row['position'];
  516.             array_multisort($positionSORT_ASC$row['cars']);
  517.         }
  518.         array_multisort($positionCSORT_ASC$categories);
  519.         return $categories;
  520.     }
  521.     protected function removeBOM($str ""): bool|string
  522.     {
  523.         if (substr($str03) == pack('CCC'0xef0xbb0xbf)) {
  524.             $str substr($str3);
  525.         }
  526.         return $str;
  527.     }
  528.     protected function getUtmData(): string
  529.     {
  530.         $request $this->requestStack->getCurrentRequest();
  531.         if (!$request) {
  532.             return json_encode([], JSON_UNESCAPED_SLASHES JSON_PRETTY_PRINT);
  533.         }
  534.         $utmData = [];
  535.         $utmTags = ['utm_source''utm_medium''utm_campaign''utm_term''utm_content'];
  536.         foreach ($utmTags as $tag) {
  537.             $pascalCaseTag = (new UnicodeString($tag))->camel()->title()->toString();
  538.             $utmData[$pascalCaseTag] = $request->query->get($tag'');
  539.         }
  540.         $utmData['BpmRef'] = $request->headers->get('referer''');
  541.         $utmData['BpmHref'] = $request->getUri();
  542.         return json_encode($utmData,  JSON_UNESCAPED_SLASHES JSON_PRETTY_PRINT);
  543.     }
  544. }