src/Controller/HomepageController.php line 968

  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Blog;
  4. use App\Entity\Partner;
  5. use App\Entity\Vehicle;
  6. use App\Repository\BlogKindRepository;
  7. use App\Repository\BlogRepository;
  8. use App\Repository\DealerOpenHourRepository;
  9. use App\Repository\DealerRepository;
  10. use App\Repository\DistrictRepository;
  11. use App\Repository\PageRepository;
  12. use App\Repository\PartnerRepository;
  13. use App\Repository\RegionRepository;
  14. use App\Repository\VehicleBodyRepository;
  15. use App\Repository\VehicleCategoryRepository;
  16. use App\Repository\VehicleFuelRepository;
  17. use App\Repository\VehicleKindRepository;
  18. use App\Repository\VehicleManufacturerRepository;
  19. use App\Repository\VehicleModelRepository;
  20. use App\Repository\VehiclePartKindRepository;
  21. use App\Repository\VehicleRepository;
  22. use App\Repository\VehicleTipRepository;
  23. use App\Service\VehicleSeoService;
  24. use App\Service\VehicleUrlGenerator;
  25. use DateTime;
  26. use DateTimeImmutable;
  27. use DateTimeZone;
  28. use Knp\Component\Pager\PaginatorInterface;
  29. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  30. use Symfony\Component\HttpFoundation\Request;
  31. use Symfony\Component\HttpFoundation\Response;
  32. use Symfony\Component\Routing\Annotation\Route;
  33. use Symfony\Contracts\Cache\CacheInterface;
  34. use Symfony\Contracts\Cache\ItemInterface;
  35. /**
  36.  * @phpstan-type PlaceValue array{name: string, url: string}
  37.  */
  38. class HomepageController extends AbstractController
  39. {
  40.     private const RECOMMENDED_VEHICLES_COUNT 12;
  41.     private const HOMEPAGE_KIND_URLS = [
  42.         'osobni-vozidla',
  43.         'uzitkova-vozidla',
  44.         'nakladni-vozidla',
  45.         'obytna-vozidla',
  46.         'motorky-skutry',
  47.     ];
  48.     #[Route('/{idAutanet}/auto-{idTipCars}/{urlName}.html'name'page_vehicle_detail_redirect'methods: ['GET'])]
  49.     public function vehicleDetailRedirect(int $idAutanetint $idTipCarsstring $urlNameVehicleRepository $vehicleRepositoryVehicleUrlGenerator $vehicleUrlGenerator): Response
  50.     {
  51.         $vehicle $vehicleRepository->findOneBy(['idImport' => $idTipCars]);
  52.         if ($vehicle) {
  53.             return $this->redirect($vehicleUrlGenerator->path($vehicle), 301);
  54.         } else {
  55.             throw $this->createNotFoundException('Page old url vehicle no exist');
  56.         }
  57.     }
  58.     #[Route('/models'name'app_models'methods: ['GET'])]
  59.     public function getModels(Request $requestVehicleModelRepository $vehicleModelRepositoryVehicleManufacturerRepository $manufacturerRepository): Response
  60.     {
  61.         $models = [];
  62.         $manufacturerId $request->get('idManufacturer');
  63.         if ($manufacturerId) {
  64.             $manufacturer $manufacturerRepository->find($manufacturerId);
  65.             if ($manufacturer) {
  66.                 foreach ($vehicleModelRepository->findActive($manufacturer) as $model) {
  67.                     $models[] = [
  68.                         'id' => $model->getId(),
  69.                         'name' => $model->getName(),
  70.                         'url' => $model->getUrl(),
  71.                     ];
  72.                 }
  73.             }
  74.         }
  75.         return $this->json(compact('models'));
  76.     }
  77.     #[Route('/autobazary/{urlPlace}'name'page_dealer'defaults: ['urlPlace' => false], methods: ['GET'])]
  78.     public function dealers(string             $urlPlaceRequest $requestPaginatorInterface $paginator,
  79.                             DealerRepository   $dealerRepository,
  80.                             VehicleRepository  $vehicleRepository,
  81.                             DistrictRepository $districtRepository,
  82.                             RegionRepository   $regionRepository): Response
  83.     {
  84.         $parameters = [
  85.             'visible' => '1',
  86.             'excludeBasic' => true,
  87.         ];
  88.         $activePlace false;
  89.         if ($urlPlace) {
  90.             $dealer $dealerRepository->findOneBy(['url' => $urlPlace]);
  91.             if ($dealer) {
  92.                 return $this->redirectToRoute('page_dealer_detail', ['url' => $urlPlace], Response::HTTP_MOVED_PERMANENTLY);
  93.             }
  94.             if ('praha' === $urlPlace) {
  95.                 $urlPlace 'hlavni-mesto-praha';
  96.             }
  97.             if ('ostrava' === $urlPlace) {
  98.                 $urlPlace 'ostrava-mesto';
  99.             }
  100.             if ('brno' === $urlPlace) {
  101.                 $urlPlace = ['brno-mesto''brno-venkov'];
  102.             }
  103.             if ('plzen' === $urlPlace) {
  104.                 $urlPlace = ['plzen-mesto''plzen-jih''plzen-sever'];
  105.             }
  106.             if (!is_array($urlPlace) && $regionRepository->findOneBy(['url' => $urlPlace])) {
  107.                 $parameters['region'] = $urlPlace;
  108.                 $activePlace $regionRepository->findOneBy(['url' => $urlPlace]);
  109.             } else {
  110.                 $parameters['district'] = $urlPlace;
  111.                 if (is_array($urlPlace)) {
  112.                     $urlPlace $urlPlace[0];
  113.                 }
  114.                 $activePlace $districtRepository->findOneBy(['url' => $urlPlace]);
  115.             }
  116.             if (!$activePlace) {
  117.                 throw $this->createNotFoundException('The place does not exist');
  118.             }
  119.         }
  120.         if ($request->query->all()) {
  121.             $parameters += $request->query->all();
  122.         }
  123.         $pagination $paginator->paginate(
  124.             $dealerRepository->findQuery($parameters), /* query NOT result */
  125.             $request->query->getInt('page'1), /*page number*/
  126.             10 /*limit per page*/
  127.         );
  128.         $dealerMap = [];
  129.         foreach ($dealerRepository->findBy(['visible' => '1'], ['name' => 'ASC']) as $dealer) {
  130.             if ($dealer->isBasicAdvertising()) {
  131.                 continue;
  132.             }
  133.             $dealerMap[] = [
  134.                 'id' => $dealer->getId(),
  135.                 'lat' => $dealer->getLat(),
  136.                 'lng' => $dealer->getLng(),
  137.                 'street' => $dealer->getStreet(),
  138.                 'city' => $dealer->getCity(),
  139.                 'link' => $this->generateUrl('page_dealer_detail', ['url' => $dealer->getUrl()]),
  140.                 'name' => $dealer->getName(),
  141.                 'region' => $dealer->getDistrict()?->getRegion()?->getName() ?? '',
  142.                 'countVehicle' => $vehicleRepository->count(['dealer' => $dealer'visible' => '1']),
  143.             ];
  144.         }
  145.         return $this->render('homepage/dealer.html.twig', [
  146.             'dealers' => $pagination,
  147.             'urlPlace' => $urlPlace,
  148.             'dealerMap' => $dealerMap,
  149.             'selectPlaceValues' => $this->getSelectPlaceValues($regionRepository$districtRepository),
  150.             'activePlace' => $activePlace,
  151.         ]);
  152.     }
  153.     /**
  154.      * @param RegionRepository $regionRepository
  155.      * @param DistrictRepository $districtRepository
  156.      * @return array<int, PlaceValue>
  157.      */
  158.     private function getSelectPlaceValues(RegionRepository $regionRepositoryDistrictRepository $districtRepository): array
  159.     {
  160.         $selectPlaceValues = [];
  161.         foreach ($regionRepository->findBy([], ['name' => 'ASC']) as $region) {
  162.             if (strpos($region->getName(), 'Praha') !== false) {
  163.                 $selectPlaceValues[] = [
  164.                     'name' => 'Praha',
  165.                     'url' => 'praha'
  166.                 ];
  167.                 continue;
  168.             }
  169.             $selectPlaceValues[] = [
  170.                 'name' => $region->getName(),
  171.                 'url' => $region->getUrl()
  172.             ];
  173.         }
  174.         foreach ($districtRepository->findBy([], ['name' => 'ASC']) as $district) {
  175.             //if in name praha
  176.             if (strpos($district->getName(), 'Praha') !== false) {
  177.                 continue;
  178.             }
  179.             if (strpos($district->getName(), 'Ostrava') !== false) {
  180.                 $selectPlaceValues[] = [
  181.                     'name' => 'Ostrava',
  182.                     'url' => 'ostrava'
  183.                 ];
  184.                 continue;
  185.             }
  186.             if (strpos($district->getName(), 'Brno-venkov') !== false) {
  187.                 continue;
  188.             }
  189.             if (strpos($district->getName(), 'území Hlavního města Prahy') !== false) {
  190.                 continue;
  191.             }
  192.             if (strpos($district->getName(), 'Brno-město') !== false) {
  193.                 $selectPlaceValues[] = [
  194.                     'name' => 'Brno',
  195.                     'url' => 'brno'
  196.                 ];
  197.                 continue;
  198.             }
  199.             if (strpos($district->getName(), 'Plzeň-jih') !== false
  200.                 || strpos($district->getName(), 'Plzeň-sever') !== false) {
  201.                 continue;
  202.             }
  203.             if (strpos($district->getName(), 'Plzeň-město') !== false) {
  204.                 $selectPlaceValues[] = [
  205.                     'name' => 'Plzeň',
  206.                     'url' => 'plzen'
  207.                 ];
  208.                 continue;
  209.             }
  210.             $selectPlaceValues[] = [
  211.                 'name' => $district->getName(),
  212.                 'url' => $district->getUrl()
  213.             ];
  214.         }
  215.         return $selectPlaceValues;
  216.     }
  217.     #[Route(
  218.         path: [
  219.             '/autobazary/{url}/kontakt',
  220.             '/autobazary/{url}/kontakty',
  221.         ], name'page_dealer_detail_redirect_contact'methods: ['GET'])]
  222.     public function dealerDetailRedirectContact(string $url): Response
  223.     {
  224.         return $this->redirectToRoute('page_dealer_detail', ['url' => $url], Response::HTTP_MOVED_PERMANENTLY);
  225.     }
  226.     #[Route('/autobazary/{url}/vozidla'name'redirect_autobazary_vozidla'methods: ['GET'], priority10)]
  227.     public function redirectAutobazaryVozidla(string $url): Response
  228.     {
  229.         return $this->redirect(sprintf("/autobazar/%s/vozidla"$url), Response::HTTP_MOVED_PERMANENTLY);
  230.     }
  231.     #[Route(
  232.         path: [
  233.             '/autobazary/{url}/{urlManufacturer}',
  234.         ], name'page_dealer_detail_redirect'methods: ['GET'])]
  235.     public function dealerDetailRedirect(string $urlstring $urlManufacturer): Response
  236.     {
  237.         //http://127.0.0.1:8000/autobazary/ald-automotive/audi
  238.         $url '/autobazar/' $url '/vozidla/' $urlManufacturer;
  239.         return $this->redirect($urlResponse::HTTP_MOVED_PERMANENTLY);
  240.     }
  241.     #[Route('/autobazar/{url}'name'page_dealer_detail'methods: ['GET'])]
  242.     public function dealerDetail(string                   $url,
  243.                                  VehicleRepository        $vehicleRepository,
  244.                                  DealerOpenHourRepository $dealerOpenHourRepository,
  245.                                  DealerRepository         $dealerRepository,
  246.                                  VehicleTipRepository     $vehicleTipRepository
  247.     ): Response
  248.     {
  249.         $dealer $dealerRepository->findOneBy(['url' => $url]);
  250.         if (!$dealer) {
  251.             throw $this->createNotFoundException('The dealer does not exist');
  252.         }
  253.         $date = new DateTime('now', new DateTimeZone('Europe/Prague'));
  254.         $openTime $dealerOpenHourRepository->findBy(['dealer' => $dealer'summer' => $date->format('I')]);
  255.         $hasVehicleImport $dealer->getNameImportVehicle() !== null && !empty($dealer->getIdImportVehicle());
  256.         if (!$hasVehicleImport) {
  257.             $vehicles = [];
  258.             $vehicleTips $vehicleTipRepository->findRandVisible(6);
  259.         } else {
  260.             $vehicles $vehicleRepository->findBy(['dealer' => $dealer'visible' => true], ['createAt' => 'DESC'], 5);
  261.             $vehicleTips = [];
  262.         }
  263.         $dealerType $dealer->isAuthSeller() ? 'autosalon' 'autobazar';
  264.         $seoTitle sprintf('%s %s %s | Autanet.cz'$dealer->getName(), $dealerType$dealer->getCity());
  265.         if (!$hasVehicleImport) {
  266.             $seoDescription sprintf('%s %s %s – profil prodejce vozidel s kontaktními údaji, mapou, odkazem na webové stránky a provozní dobou.',
  267.                 $dealer->getName(), $dealerType$dealer->getCity());
  268.         } else {
  269.             $seoDescription sprintf('%s %s %s - novinky z nabídky inzerce, kontakty, mapa, provozní doba.',
  270.                 $dealer->getName(), $dealerType$dealer->getCity());
  271.         }
  272.         return $this->render('homepage/detailDealer.html.twig', [
  273.             'dealer' => $dealer,
  274.             'openTime' => $openTime,
  275.             'vehicles' => $vehicles,
  276.             'vehicleTips' => $vehicleTips,
  277.             'hasVehicleImport' => $hasVehicleImport,
  278.             'isBasicDealer' => $dealer->isBasicAdvertising(),
  279.             'seoTitle' => $seoTitle,
  280.             'seoDescription' => $seoDescription,
  281.         ]);
  282.     }
  283.     #[Route('clanky/{urlKind}/{urlManufacturer}'name'page_blog_universal'requirements: ['urlManufacturer' => '[a-z0-9\-]+'], methods: ['GET'], priority1)]
  284.     public function blogUniversal(string                        $urlKind,
  285.                                   string                        $urlManufacturer,
  286.                                   BlogKindRepository            $blogKindRepository,
  287.                                   BlogRepository                $blogRepository,
  288.                                   VehicleManufacturerRepository $manufacturerRepository,
  289.                                   VehicleModelRepository        $modelRepository,
  290.                                   Request                       $request,
  291.                                   PaginatorInterface            $paginator
  292.     ): Response
  293.     {
  294.         $blogKind $blogKindRepository->findOneBy(['url' => $urlKind]);
  295.         if (!$blogKind) {
  296.             throw $this->createNotFoundException('The blog kind does not exist');
  297.         }
  298.         if (str_contains($urlManufacturer'-')) {
  299.             [$manufacturerUrl$modelUrl] = $this->parseManufacturerAndModel($urlManufacturer$manufacturerRepository$blogKindRepository);
  300.         } else {
  301.             $manufacturerUrl $urlManufacturer;
  302.             $modelUrl null;
  303.         }
  304.         $manufacturer $manufacturerUrl $manufacturerRepository->findOneBy(['url' => $manufacturerUrl]) : null;
  305.         $model = ($modelUrl && $manufacturer) ? $modelRepository->findOneBy(['url' => $modelUrl'vehicleManufacturer' => $manufacturer]) : null;
  306.         $isTestyAut $blogKind->getUrl() === 'testy-aut';
  307.         $baseParameters = ['visible' => '1''urlKind' => $urlKind];
  308.         $notFoundMessage null;
  309.         $activeManufacturer null;
  310.         $activeModel null;
  311.         if (!$isTestyAut || !$manufacturer) {
  312.             $pagination $paginator->paginate(
  313.                 $blogRepository->findQuery($baseParameters),
  314.                 $request->query->getInt('page'1), 20
  315.             );
  316.             if (!$manufacturer) {
  317.                 $notFoundMessage "Testy pro požadovanou značku nebyly nalezeny. Zobrazujeme všechny testy aut.";
  318.             }
  319.         } else {
  320.             $manufacturerParameters array_merge($baseParameters, ['manufacturer' => $manufacturer->getId()]);
  321.             $manufacturerResults $blogRepository->findQueryWithManufacturer($manufacturerParameters);
  322.             if (empty($manufacturerResults)) {
  323.                 $pagination $paginator->paginate(
  324.                     $blogRepository->findQuery($baseParameters),
  325.                     $request->query->getInt('page'1), 20
  326.                 );
  327.                 $notFoundMessage "Testy pro požadovanou značku nebyly nalezeny. Zobrazujeme všechny testy aut.";
  328.             } else {
  329.                 $activeManufacturer $manufacturer;
  330.                 $paginateResults $manufacturerResults;
  331.                 $notFoundMessage $modelUrl "Testy pro hledaný model nebyly nalezeny. Zobrazujeme všechny testy této značky." null;
  332.                 if ($model) {
  333.                     $modelParameters array_merge($manufacturerParameters, ['model' => $model->getId()]);
  334.                     $modelResults $blogRepository->findQueryWithManufacturerAndModel($modelParameters);
  335.                     if (!empty($modelResults)) {
  336.                         $activeModel $model;
  337.                         $paginateResults $modelResults;
  338.                         $notFoundMessage null;
  339.                     }
  340.                 }
  341.                 $pagination $paginator->paginate($paginateResults$request->query->getInt('page'1), 20);
  342.             }
  343.         }
  344.         $manufacturers $manufacturerRepository->findByBlogKind($blogKind);
  345.         $models $manufacturer $modelRepository->findByManufacturerAndBlogKind($manufacturer$blogKind) : [];
  346.         $data = [
  347.             'blogs' => $pagination,
  348.             'blogKind' => $blogKind,
  349.             'manufacturers' => $manufacturers,
  350.             'models' => $models,
  351.             'activeManufacturer' => $activeManufacturer,
  352.             'activeModel' => $activeModel,
  353.             'notFoundMessage' => $notFoundMessage,
  354.         ];
  355.         return $this->render('homepage/blog.html.twig'$data);
  356.     }
  357.     #[Route('clanky/{urlKind}'name'page_blog'methods: ['GET'], priority0)]
  358.     public function blog(string         $urlKindBlogKindRepository $blogKindRepository,
  359.                          BlogRepository $blogRepositoryVehicleManufacturerRepository $manufacturerRepository,
  360.                          Request        $requestPaginatorInterface $paginator): Response
  361.     {
  362.         if ($urlKind == 'vsechny-clanky') {
  363.             $parameters = [
  364.                 'visible' => '1',
  365.                 'urlKind' => '',
  366.             ];
  367.             $blogKind false;
  368.         } else {
  369.             $parameters = [
  370.                 'visible' => '1',
  371.                 'urlKind' => $urlKind,
  372.             ];
  373.             $blogKind $blogKindRepository->findOneBy(['url' => $urlKind]);
  374.             if (!$blogKind) {
  375.                 throw $this->createNotFoundException('The blog kind does not exist');
  376.             }
  377.         }
  378.         $pagination $paginator->paginate(
  379.             $blogRepository->findQuery($parameters), /* query NOT result */
  380.             $request->query->getInt('page'1), /*page number*/
  381.             20 /*limit per page*/
  382.         );
  383.         $manufacturers = [];
  384.         $models = [];
  385.         if ($blogKind && $blogKind->getUrl() === 'testy-aut') {
  386.             $manufacturers $manufacturerRepository->findByBlogKind($blogKind);
  387.         }
  388.         return $this->render('homepage/blog.html.twig', [
  389.             'blogs' => $pagination,
  390.             'blogKind' => $blogKind,
  391.             'manufacturers' => $manufacturers,
  392.             'models' => $models,
  393.             'activeManufacturer' => null,
  394.             'activeModel' => null,
  395.             'notFoundMessage' => null,
  396.         ]);
  397.     }
  398.     #[Route(
  399.         path: [
  400.             '/autobazar/{url}/vozidla',
  401.             '/autobazar/{url}/vozidla/{urlManufacturer}',
  402.             '/autobazar/{url}/vozidla/{urlManufacturer}/{urlModel}'
  403.         ]
  404.         , name'page_dealer_vehicle'defaults: ['urlManufacturer' => null'urlModel' => null], methods: ['GET'])]
  405.     public function dealerVehicles(string                        $url,
  406.                                    string|null                   $urlManufacturer,
  407.                                    string|null                   $urlModel,
  408.                                    Request                       $request,
  409.                                    PaginatorInterface            $paginator,
  410.                                    VehicleRepository             $vehicleRepository,
  411.                                    VehicleManufacturerRepository $manufacturerRepository,
  412.                                    VehicleModelRepository        $vehicleModelRepository,
  413.                                    DealerRepository              $dealerRepository
  414.     ): Response
  415.     {
  416.         $dealer $dealerRepository->findOneBy(['url' => $url]);
  417.         if (!$dealer) {
  418.             throw $this->createNotFoundException('The dealer does not exist');
  419.         }
  420.         $parameters = [
  421.             'visible' => '1',
  422.             'dealer' => (string)$dealer->getId(),
  423.         ];
  424.         $manufacturer null;
  425.         if ($urlManufacturer) {
  426.             $manufacturer $manufacturerRepository->findOneBy(['url' => $urlManufacturer]);
  427.             if (!$manufacturer) {
  428.                 throw $this->createNotFoundException('The manufacturer does not exist');
  429.             }
  430.             $manufacturerId $parameters['manufacturer'] = $manufacturer->getId();
  431.         }
  432.         $model null;
  433.         if ($urlModel) {
  434.             $model $vehicleModelRepository->findOneBy(['url' => $urlModel]);
  435.             if (!$model) {
  436.                 throw $this->createNotFoundException('The model does not exist');
  437.             }
  438.             $parameters['model'] = $model->getId();
  439.         }
  440.         $pagination $paginator->paginate(
  441.             $vehicleRepository->findQuery($parameters), /* query NOT result */
  442.             $request->query->getInt('page'1), /*page number*/
  443.             10 /*limit per page*/
  444.         );
  445.         $dealerType $dealer->isAuthSeller() ? 'autosalon' 'autobazar';
  446.         $vehicleCount $pagination->getTotalItemCount();
  447.         if ($manufacturer && $model) {
  448.             $seoTitle sprintf('%s %s - nabídka vozidel %s %s | Autanet.cz',
  449.                 $dealer->getName(), $dealer->getCity(), $manufacturer->getName(), $model->getName());
  450.             $seoDescription sprintf('Nabídka %d vozidel %s %s z %su %s %s. Vybírejte z nabídky vozidel značky %s %s a dalších modelů.',
  451.                 $vehicleCount$manufacturer->getName(), $model->getName(), $dealerType$dealer->getName(), $dealer->getCity(),
  452.                 $manufacturer->getName(), $model->getName());
  453.         } elseif ($manufacturer) {
  454.             $seoTitle sprintf('%s %s - nabídka vozidel %s | Autanet.cz',
  455.                 $dealer->getName(), $dealer->getCity(), $manufacturer->getName());
  456.             $seoDescription sprintf('Nabídka %d vozidel %s z %su %s %s. Vybírejte z nabídky vozidel značky %s a dalších.',
  457.                 $vehicleCount$manufacturer->getName(), $dealerType$dealer->getName(), $dealer->getCity(), $manufacturer->getName());
  458.         } else {
  459.             $seoTitle sprintf('%s %s - nabídka vozidel | Autanet.cz'$dealer->getName(), $dealer->getCity());
  460.             $topManufacturers $manufacturerRepository->findByDealerActive($dealer);
  461.             $manufacturerNames array_slice(array_map(fn($m) => $m->getName(), $topManufacturers), 05);
  462.             $manufacturerList implode(', '$manufacturerNames);
  463.             $seoDescription sprintf('Nabídka %d vozidel z %su %s %s. Vybírejte z vozidel značek %s a dalších.',
  464.                 $vehicleCount$dealerType$dealer->getName(), $dealer->getCity(), $manufacturerList);
  465.         }
  466.         if ($manufacturer && $model) {
  467.             $seoH1 sprintf('Nabídka vozidel %s %s %s'$manufacturer->getName(), $model->getName(), $dealer->getName());
  468.         } elseif ($manufacturer) {
  469.             $seoH1 sprintf('Nabídka vozidel %s %s'$manufacturer->getName(), $dealer->getName());
  470.         } else {
  471.             $seoH1 sprintf('Nabídka vozidel %s'$dealer->getName());
  472.         }
  473.         return $this->render('homepage/vehicleDealer.html.twig', [
  474.             'dealer' => $dealer,
  475.             'activeManufacturer' => $manufacturer,
  476.             'activeModel' => $model,
  477.             'manufacturer' => $manufacturerRepository->findByDealerActive($dealer),
  478.             'model' => $vehicleModelRepository->findByDealerActive($dealer$manufacturer?->getId()),
  479.             'vehicles' => $pagination,
  480.             'seoTitle' => $seoTitle,
  481.             'seoDescription' => $seoDescription,
  482.             'seoH1' => $seoH1,
  483.         ]);
  484.     }
  485.     #[Route(path: [
  486.         '/nova/{urlKind}',
  487.         '/nova/{urlKind}/{urlManufacturer}',
  488.         '/nova/{urlKind}/{urlManufacturer}/{urlModel}',
  489.     ], name'app_search_redirect'defaults: ['urlManufacturer' => null'urlModel' => null], methods: ['GET'])]
  490.     public function redirectSearch(string $urlKindstring|null $urlManufacturerstring|null $urlModel): Response
  491.     {
  492.         if ($urlManufacturer and $urlModel) {
  493.             return $this->redirectToRoute('page_vehicle_search'compact('urlKind''urlManufacturer''urlModel'), Response::HTTP_MOVED_PERMANENTLY);
  494.         } elseif ($urlManufacturer) {
  495.             return $this->redirectToRoute('page_vehicle_search'compact('urlKind''urlManufacturer'), Response::HTTP_MOVED_PERMANENTLY);
  496.         } else {
  497.             return $this->redirectToRoute('page_vehicle_search'compact('urlKind'), Response::HTTP_MOVED_PERMANENTLY);
  498.         }
  499.     }
  500.     #[Route(path: [
  501.         '/{urlKind}',
  502.         '/{urlKind}/{urlManufacturer}',
  503.         '/{urlKind}/{urlManufacturer}/{urlModel}',
  504.     ], name'page_vehicle_search'defaults: ['urlManufacturer' => null'urlModel' => null], methods: ['GET'])]
  505.     public function vehicleSearch(string                        $urlKindstring|null $urlManufacturerstring|null $urlModelRequest $requestPaginatorInterface $paginator,
  506.                                   VehicleRepository             $vehicleRepository,
  507.                                   VehicleKindRepository         $vehicleKindRepository,
  508.                                   VehicleCategoryRepository     $vehicleCategoryRepository,
  509.                                   VehicleManufacturerRepository $manufacturerRepository,
  510.                                   VehicleModelRepository        $vehicleModelRepository,
  511.                                   VehicleBodyRepository         $vehicleBodyRepository,
  512.                                   VehicleFuelRepository         $vehicleFuelRepository,
  513.                                   RegionRepository              $regionRepository,
  514.                                   DistrictRepository            $districtRepository,
  515.                                   VehiclePartKindRepository     $vehiclePartKindRepository): Response
  516.     {
  517.         $parameters = [
  518.             'visible' => '1',
  519.         ];
  520.         $vehicleKind null;
  521.         $models = [];
  522.         $vehicleCategory null;
  523.         $manufacturer null;
  524.         $mileageValues = [
  525.             '0-10000' => 'do 10000 km',
  526.             '10000-30000' => '10 000 - 30 000 km',
  527.             '30000-50000' => '30 000 - 50 000 km',
  528.             '50000-100000' => '50 000 - 100 000 km',
  529.             '100000-150000' => '100 000 - 150 000 km',
  530.             '150000-200000' => '150 000 - 200 000 km',
  531.             '200000-300000' => '200 000 - 300 000 km',
  532.             '300000-10000000' => '300 000 a více km',
  533.         ];
  534.         $powerValues = [
  535.             '0-40' => 'do 40 kW',
  536.             '40-60' => '40 - 60 kW',
  537.             '60-80' => '60 - 80 kW',
  538.             '80-100' => '80 - 100 kW',
  539.             '100-125' => '100 - 125 kW',
  540.             '125-150' => '120 - 150 kW',
  541.             '150-200' => '150 - 200 kW',
  542.             '200-300' => '200 - 300 kW',
  543.             '300-2000' => 'nad 300 kW',
  544.         ];
  545.         $vehiclePartKindValues = [];
  546.         if ($urlKind) {
  547.             if ($urlKind == 'obytna-vozidla') {
  548.                 $powerValues = [
  549.                     '0-60' => 'do 60 kW',
  550.                     '60-80' => '60 - 80 kW',
  551.                     '80-100' => '80 - 100 kW',
  552.                     '100-125' => '100 - 125 kW',
  553.                     '125-150' => '125 - 150 kW',
  554.                     '150-200' => '150 - 200 kW',
  555.                     '200-300' => '200 - 300 kW',
  556.                     '300-2000' => 'nad 300 kW',
  557.                 ];
  558.             }
  559.             if ($urlKind == 'nakladni-vozidla') {
  560.                 $powerValues = [
  561.                     '0-100' => 'do 100 kW',
  562.                     '100-150' => '100 - 150 kW',
  563.                     '150-200' => '150 - 200 kW',
  564.                     '200-300' => '200 - 300 kW',
  565.                     '300-400' => '300 - 400 kW',
  566.                     '400-2000' => 'nad 400 kW',
  567.                 ];
  568.             }
  569.             if ($urlKind == 'motorky-skutry') {
  570.                 $mileageValues = [
  571.                     '0-10000' => 'do 10000 km',
  572.                     '10000-30000' => '10 000 - 30 000 km',
  573.                     '30000-50000' => '30 000 - 50 000 km',
  574.                     '50000-100000' => '50 000 - 100 000 km',
  575.                     '100000-150000' => '100 000 - 150 000 km',
  576.                     '150000-1000000' => '150 000 a více km',
  577.                 ];
  578.                 $powerValues = [
  579.                     '0-25' => 'do 25 kW',
  580.                     '25-50' => '25 - 50 kW',
  581.                     '50-75' => '50 - 75 kW',
  582.                     '75-100' => '75 - 100 kW',
  583.                     '100-125' => '100 - 125 kW',
  584.                     '125-150' => '125 - 150 kW',
  585.                     '150-2000' => 'nad 150 kW',
  586.                 ];
  587.             }
  588.             if ($urlKind == 'nahradni-dily' || $urlKind == 'prislusenstvi') {
  589.                 $vehicleCategory $vehicleCategoryRepository->findOneBy(['url' => $urlKind]);
  590.                 if ($vehicleCategory) {
  591.                     $parameters['category'] = $vehicleCategory->getId();
  592.                 } else {
  593.                     return $this->forward('App\Controller\HomepageController::blogDetail', ['url' => $urlKind,]);
  594.                 }
  595.                 $vehiclePartKindValues $vehiclePartKindRepository->findActiveCategory($vehicleCategory);
  596.             } else {
  597.                 $vehicleKind $vehicleKindRepository->findOneBy(['url' => $urlKind]);
  598.                 if ($vehicleKind) {
  599.                     $parameters['kind'] = $vehicleKind->getId();
  600.                 } else {
  601.                     return $this->forward('App\Controller\HomepageController::blogDetail', ['url' => $urlKind,]);
  602.                 }
  603.             }
  604.         }
  605.         if ($urlManufacturer) {
  606.             $manufacturer $manufacturerRepository->findOneBy(['url' => $urlManufacturer]);
  607.             if ($manufacturer) {
  608.                 $parameters['manufacturer'] = $manufacturer->getId();
  609.                 $models $vehicleModelRepository->findActive($manufacturer$vehicleCategory$vehicleKind);
  610.             } else {
  611.                 return $this->forward('App\Controller\HomepageController::page', ['url' => $urlKind,]);
  612.             }
  613.         }
  614.         $activeModel null;
  615.         if ($urlModel) {
  616.             $activeModel $vehicleModelRepository->findOneBy(['url' => $urlModel]);
  617.             if ($activeModel) {
  618.                 $parameters['model'] = $activeModel->getId();
  619.             } else {
  620.                 return $this->forward('App\Controller\HomepageController::page', ['url' => $urlKind,]);
  621.             }
  622.         }
  623.         $urlPlace $request->get('place');
  624.         if ($urlPlace) {
  625.             if ('praha' === $urlPlace) {
  626.                 $urlPlace 'hlavni-mesto-praha';
  627.             }
  628.             if ('ostrava' === $urlPlace) {
  629.                 $urlPlace 'ostrava-mesto';
  630.             }
  631.             if ('brno' === $urlPlace) {
  632.                 $urlPlace = ['brno-mesto''brno-venkov'];
  633.             }
  634.             if ('plzen' === $urlPlace) {
  635.                 $urlPlace = ['plzen-mesto''plzen-jih''plzen-sever'];
  636.             }
  637.             if (!is_array($urlPlace) && $regionRepository->findOneBy(['url' => $urlPlace])) {
  638.                 $parameters['region'] = $urlPlace;
  639.             } else {
  640.                 $parameters['district'] = $urlPlace;
  641.             }
  642.         }
  643.         if ($request->query->all()) {
  644.             $parameters += $request->query->all();
  645.         }
  646.         $pagination $paginator->paginate(
  647.             $vehicleRepository->findQuery($parameters), /* query NOT result */
  648.             $request->query->getInt('page'1), /*page number*/
  649.             20 /*limit per page*/
  650.         );
  651.         return $this->render('homepage/search.html.twig', [
  652.             'vehicles' => $pagination,
  653.             'activeKind' => $vehicleKind,
  654.             'baseUrl' => $vehicleCategory $vehicleCategory->getUrl() : $vehicleKind->getUrl(),
  655.             'activeCategory' => $vehicleCategory,
  656.             'activeManufacturer' => isset($parameters['manufacturer']) ? $manufacturerRepository->find($parameters['manufacturer']) : null,
  657.             'activeModel' => $activeModel,
  658.             'categories' => $vehicleCategoryRepository->findActive(),
  659.             'manufacturers' => $manufacturerRepository->findActive($vehicleKind$vehicleCategory),
  660.             'models' => $models,
  661.             'bodies' => $vehicleBodyRepository->findActive($vehicleKind$manufacturer$activeModel),
  662.             'fuels' => $vehicleFuelRepository->findActive(),
  663.             'gearbox' => $vehicleRepository->findActiveGearBox(),
  664.             'selectPlaceValues' => $this->getSelectPlaceValues($regionRepository$districtRepository),
  665.             'vehicleKind' => $vehicleKind,
  666.             'mileageValues' => $mileageValues,
  667.             'powerValues' => $powerValues,
  668.             'vehiclePartKindValues' => $vehiclePartKindValues
  669.         ]);
  670.     }
  671.     #[Route('/detail/{seoSlug}-{id}'name'page_vehicle_detail_seo'methods: ['GET'], requirements: ['seoSlug' => '.+''id' => '[0-9]+'], priority10)]
  672.     public function vehicleDetailSeo(string            $seoSlugint $idVehicleRepository $vehicleRepository,
  673.                                      VehicleSeoService $vehicleSeoServiceDealerOpenHourRepository $dealerOpenHourRepository): Response
  674.     {
  675.         $vehicle $vehicleRepository->find($id);
  676.         if (!$vehicle) {
  677.             throw $this->createNotFoundException('Vehicle not found');
  678.         }
  679.         if (!$vehicle->getSeoSlug()) {
  680.             $vehicleSeoService->generateSeoData($vehicle);
  681.         }
  682.         if ($vehicle->getSeoSlug() && $seoSlug !== $vehicle->getSeoSlug()) {
  683.             return $this->redirectToRoute('page_vehicle_detail_seo', [
  684.                 'seoSlug' => $vehicle->getSeoSlug(),
  685.                 'id' => $id
  686.             ], 301);
  687.         }
  688.         if (!$vehicle->isVisible()) {
  689.             $response $this->render('homepage/detailVehicleDisable.html.twig'compact('vehicle'));
  690.             $response->setStatusCode(404);
  691.             return $response;
  692.         }
  693.         $date = new DateTime('now', new DateTimeZone('Europe/Prague'));
  694.         $openTime $dealerOpenHourRepository->findBy(['dealer' => $vehicle->getDealer(), 'summer' => $date->format('I')]);
  695.         $vehicleOthers $vehicleRepository->findOtherDealerBody($vehicle4);
  696.         return $this->render('homepage/detailVehicle.html.twig'compact('vehicle''openTime''vehicleOthers'));
  697.     }
  698.     #[Route(
  699.         [
  700.             '/detail/{urlManufacturer}/{urlModel}/{id}',
  701.         ], name'page_vehicle_detail'defaults: ['urlModel' => null], methods: ['GET'])]
  702.     public function vehicleDetail(string                        $urlManufacturerstring $urlModelint $idVehicleRepository $vehicleRepository,
  703.                                   VehicleManufacturerRepository $vehicleManufacturerRepository,
  704.                                   VehicleModelRepository        $vehicleModelRepositoryDealerOpenHourRepository $dealerOpenHourRepository,
  705.                                   VehicleSeoService             $vehicleSeoService): Response
  706.     {
  707.         if ($urlModel) {
  708.             $vehicleModel $vehicleModelRepository->findOneBy(['url' => $urlModel]);
  709.         }
  710.         $manufacturer $vehicleManufacturerRepository->findOneBy(['url' => $urlManufacturer]);
  711.         if ($urlModel) {
  712.             $vehicle $vehicleRepository->findOneBy(['id' => $id'visible' => true'manufacturer' => $manufacturer'model' => $vehicleModel]);
  713.         } else {
  714.             $vehicle $vehicleRepository->findOneBy(['id' => $id'visible' => true'manufacturer' => $manufacturer]);
  715.         }
  716.         if ($urlModel) {
  717.             $disableVehicle $vehicleRepository->findOneBy(['id' => $id'visible' => false'manufacturer' => $manufacturer'model' => $vehicleModel]);
  718.         } else {
  719.             $disableVehicle $vehicleRepository->findOneBy(['id' => $id'visible' => false'manufacturer' => $manufacturer]);
  720.         }
  721.         if ($disableVehicle) {
  722.             $response $this->render('homepage/detailVehicleDisable.html.twig', [
  723.                 'vehicle' => $disableVehicle,
  724.             ]);
  725.             $response->setStatusCode(404);
  726.             return $response;
  727.         }
  728.         if (!$vehicle) {
  729.             throw $this->createNotFoundException('The vehicle does not exist');
  730.         }
  731.         if (!$vehicle->getSeoSlug()) {
  732.             $vehicleSeoService->generateSeoData($vehicle);
  733.         }
  734.         if ($vehicleSeoService->shouldRedirectToSeo($vehicle) && $vehicle->getSeoSlug()) {
  735.             return $this->redirectToRoute('page_vehicle_detail_seo', [
  736.                 'seoSlug' => $vehicle->getSeoSlug(),
  737.                 'id' => $id
  738.             ], 301);
  739.         }
  740.         $date = new DateTime('now', new DateTimeZone('Europe/Prague'));
  741.         $openTime $dealerOpenHourRepository->findBy(['dealer' => $vehicle->getDealer(), 'summer' => $date->format('I')]);
  742.         $vehicleOthers $vehicleRepository->findOtherDealerBody($vehicle4);
  743.         return $this->render('homepage/detailVehicle.html.twig', [
  744.             'vehicle' => $vehicle,
  745.             'openTime' => $openTime,
  746.             'vehicleOthers' => $vehicleOthers,
  747.         ]);
  748.     }
  749.     #[Route('/{url}'name'page'methods: ['GET'], priority: -2)]
  750.     public function page(string $urlPageRepository $pageRepository): Response
  751.     {
  752.         $page $pageRepository->findPage($url);
  753.         if (!$page) {
  754.             throw $this->createNotFoundException('Page no exist');
  755.         }
  756.         return $this->render('homepage/page.html.twig', [
  757.             'page' => $page,
  758.         ]);
  759.     }
  760.     #[Route('/{url}'name'page_blog_detail'methods: ['GET'], priority: -1)]
  761.     public function blogDetail(string $urlBlogRepository $blogRepositoryVehicleRepository $vehicleRepository): Response
  762.     {
  763.         $blog $blogRepository->findOneVisibleUrl($url);
  764.         if (!$blog) {
  765.             return $this->forward('App\Controller\HomepageController::page', [
  766.                 'url' => $url,
  767.             ]);
  768.         }
  769.         $vehicles $vehicleRepository->findRandActive($blog->getVehicleManufacturer(), $blog->getVehicleModel(), 3);
  770.         if (!$vehicles) {
  771.             $vehicles $vehicleRepository->findRandActive($blog->getVehicleManufacturer(), null3);
  772.             $isVehicleModel false;
  773.         } else {
  774.             $isVehicleModel true;
  775.         }
  776.         $blogOtherManufacturer $blogRepository->findRandActive($blog->getVehicleManufacturer(), $blog->getVehicleModel(), $blog->getId(), 3);
  777.         return $this->render('homepage/blogDetail.html.twig', [
  778.             'blog' => $blog,
  779.             'vehicles' => $vehicles,
  780.             'isVehicleModel' => $isVehicleModel,
  781.             'blogOtherManufacturer' => $blogOtherManufacturer,
  782.             'blogNew' => $blogRepository->findNewActive(3)
  783.         ]);
  784.     }
  785.     #[Route('/'name'app_homepage'methods: ['GET'])]
  786.     public function index(BlogRepository       $blogRepository,
  787.                           VehicleTipRepository $vehicleTipRepository,
  788.                           PartnerRepository    $partnerRepository,
  789.                           VehicleRepository    $vehicleRepository,
  790.                           DealerRepository     $dealerRepository,
  791.                           CacheInterface       $cache): Response
  792.     {
  793.         $blogsTests $blogRepository->findLatestByKindUrl('testy-aut'3);
  794.         $excludeIds array_map(static fn(Blog $blog): int => (int)$blog->getId(), $blogsTests);
  795.         [$recommendedVehicles$tipVehicleIds] = $this->getRecommendedVehicles($vehicleTipRepository$vehicleRepository);
  796.         return $this->render('homepage/index.html.twig', [
  797.             'partners' => $this->getPartners($partnerRepository),
  798.             'blogsTests' => $blogsTests,
  799.             'blogsLatest' => $blogRepository->findLatestExcluding(3$excludeIds),
  800.             'recommendedVehicles' => $recommendedVehicles,
  801.             'tipVehicleIds' => $tipVehicleIds,
  802.             'stats' => $this->getHomepageStats($vehicleRepository$dealerRepository$cache),
  803.         ]);
  804.     }
  805.     /**
  806.      * @return array{0: Vehicle[], 1: int[]}
  807.      */
  808.     private function getRecommendedVehicles(VehicleTipRepository $vehicleTipRepositoryVehicleRepository $vehicleRepository): array
  809.     {
  810.         $vehicles = [];
  811.         $tipVehicleIds = [];
  812.         foreach ($vehicleTipRepository->findRandVisible(self::RECOMMENDED_VEHICLES_COUNT) as $tip) {
  813.             $vehicle $tip->getVehicle();
  814.             if ($vehicle === null) {
  815.                 continue;
  816.             }
  817.             $vehicles[] = $vehicle;
  818.             $tipVehicleIds[] = (int)$vehicle->getId();
  819.         }
  820.         $missing self::RECOMMENDED_VEHICLES_COUNT count($vehicles);
  821.         if ($missing 0) {
  822.             $vehicles array_merge($vehicles$vehicleRepository->findRandomVisibleWithImages($missing$tipVehicleIds));
  823.         }
  824.         return [$vehicles$tipVehicleIds];
  825.     }
  826.     /**
  827.      * @return Partner[]
  828.      */
  829.     private function getPartners(PartnerRepository $partnerRepository): array
  830.     {
  831.         $partners $partnerRepository->findAll();
  832.         shuffle($partners);
  833.         return $partners;
  834.     }
  835.     /**
  836.      * @return array{total: int, today: int, week: int, dealers: int}
  837.      */
  838.     private function getHomepageStats(VehicleRepository $vehicleRepository,
  839.                                       DealerRepository  $dealerRepository,
  840.                                       CacheInterface    $cache): array
  841.     {
  842.         return $cache->get('homepage_stats', function (ItemInterface $item) use ($vehicleRepository$dealerRepository): array {
  843.             $item->expiresAfter(300);
  844.             $storageTimezone = new DateTimeZone(date_default_timezone_get());
  845.             $todayStart = new DateTimeImmutable('today', new DateTimeZone('Europe/Prague'));
  846.             return [
  847.                 'total' => $vehicleRepository->countVisibleByKindUrls(self::HOMEPAGE_KIND_URLS),
  848.                 'today' => $vehicleRepository->countFirstSeenSince($todayStart->setTimezone($storageTimezone), self::HOMEPAGE_KIND_URLS),
  849.                 'week' => $vehicleRepository->countFirstSeenSince($todayStart->modify('-6 days')->setTimezone($storageTimezone), self::HOMEPAGE_KIND_URLS),
  850.                 'dealers' => $dealerRepository->countActiveProfiles(),
  851.             ];
  852.         });
  853.     }
  854.     /**
  855.      * Rozdělí URL typu "bmw-rada-3" na výrobce "bmw" a model "rada-3"
  856.      * Použije přístup: najít všechny možné kandidáty na výrobce a vybrat nejdelší shodu
  857.      */
  858.     private function parseManufacturerAndModel(string $manufacturerAndModelVehicleManufacturerRepository $manufacturerRepositoryBlogKindRepository $blogKindRepository): array
  859.     {
  860.         $testyAutKind $blogKindRepository->findOneBy(['url' => 'testy-aut']);
  861.         if (!$testyAutKind) {
  862.             return [nullnull];
  863.         }
  864.         $manufacturers $manufacturerRepository->findByBlogKind($testyAutKind);
  865.         $manufacturerUrls array_map(fn($m) => $m->getUrl(), $manufacturers);
  866.         $candidates = [];
  867.         $urlParts explode('-'$manufacturerAndModel);
  868.         $countParts count($urlParts);
  869.         for ($i 1$i <= $countParts$i++) {
  870.             $candidateManufacturer implode('-'array_slice($urlParts0$i));
  871.             if (in_array($candidateManufacturer$manufacturerUrls)) {
  872.                 $candidates[] = $candidateManufacturer;
  873.             }
  874.         }
  875.         if (empty($candidates)) {
  876.             return [nullnull];
  877.         }
  878.         usort($candidates, fn($a$b) => strlen($b) <=> strlen($a));
  879.         $manufacturerUrl $candidates[0];
  880.         if ($manufacturerUrl === $manufacturerAndModel) {
  881.             return [$manufacturerUrlnull];
  882.         }
  883.         $modelUrl substr($manufacturerAndModelstrlen($manufacturerUrl) + 1);
  884.         return [$manufacturerUrl$modelUrl];
  885.     }
  886. }