src/Controller/Front/Product/HotelController.php line 606

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Front\Product;
  3. use App\Config\FaqRouteEnum;
  4. use App\Config\ModuleEnum;
  5. use App\Entity\CustomerMoral;
  6. use App\Repository\CityRepository;
  7. use App\Repository\FrontThemeRepository;
  8. use App\Repository\HotelXmlPriceRepository;
  9. use App\Repository\HotelXmlRepository;
  10. use App\Repository\OrderLineRepository;
  11. use App\Repository\OrderRepository;
  12. use App\Service\Api3TAuthenticationService;
  13. use App\Service\CartService;
  14. use App\Service\CurrencyService;
  15. use App\Service\FrontRecentSearchService;
  16. use App\Service\FrontService;
  17. use App\Service\Helpers;
  18. use App\Service\HotelApiService;
  19. use App\Service\ParameterService;
  20. use DateTime;
  21. use Exception;
  22. use Gedmo\Translatable\TranslatableListener;
  23. use Psr\Log\LoggerInterface;
  24. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  25. use Symfony\Component\HttpClient\HttpClient;
  26. use Symfony\Component\HttpFoundation\JsonResponse;
  27. use Symfony\Component\HttpFoundation\RedirectResponse;
  28. use Symfony\Component\HttpFoundation\Request;
  29. use Symfony\Component\HttpFoundation\Response;
  30. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  31. use Symfony\Component\HttpFoundation\StreamedResponse;
  32. use Symfony\Component\Routing\Annotation\Route;
  33. #[Route('/hotel')]
  34. class HotelController extends AbstractController
  35. {
  36.     public function __construct(
  37.         private readonly Api3TAuthenticationService $apiAuthenticationService,
  38.         private readonly HotelApiService            $hotelApiService,
  39.         private readonly CurrencyService            $currencyService,
  40.         private readonly FrontService               $frontService,
  41.         private readonly ParameterService           $parameterService,
  42.         private readonly Helpers                    $helpers,
  43.         private readonly LoggerInterface            $logger,
  44.     )
  45.     {
  46.     }
  47.     #[Route('/hotelcity/{name}'name'app_front_hotel_city')]
  48.     public function hotelByCity(
  49.         string                  $name,
  50.         CityRepository          $cityRepository,
  51.         HotelXmlPriceRepository $hotelXmlPriceRepository,
  52.         FrontService            $frontService
  53.     ): Response
  54.     {
  55.         $hotels = [];
  56.         $city $cityRepository->findOneBy(['name' => $name]);
  57.         $description "";
  58.         $country "";
  59.         if ($city) {
  60.             $description $city->getDescription();
  61.             $country $city->getCountry()->getName();
  62.         }
  63.         $hotelXmlPrices $hotelXmlPriceRepository->getHotelXmlPriceByCriterias(['cities' => [$name]]);
  64.         foreach ($hotelXmlPrices as $HotelXmlPrice) {
  65.             $hotels[] = $frontService->getItemHotel($HotelXmlPrice);
  66.         }
  67.         return $this->render('front/hotel/hotel_by_city.html.twig', [
  68.             'hotels' => $hotels,
  69.             'city' => $city,
  70.             'name' => $name,
  71.             'country' => $country,
  72.             'society' => $this->parameterService->getSocietyParameters(),
  73.             'social_networks' => $this->frontService->getSocialNetworks(),
  74.             'agencies' => $this->frontService->getAgencies(),
  75.             'currencies' => $this->frontService->getCurrencies(),
  76.             'currencySwitcher' => true,
  77.             'tracking_tools' => $this->frontService->getTrackingTools(),
  78.             'faqs' => $this->frontService->getFaqs(FaqRouteEnum::APP_FRONT_HOTEL_CITYstrtolower($name)),
  79.         ]);
  80.     }
  81.     #[Route('/hoteltheme/{name}'name'app_front_hotel_theme')]
  82.     public function hotelByTheme(
  83.         string                  $name,
  84.         FrontThemeRepository    $frontThemeRepository,
  85.         HotelXmlPriceRepository $hotelXmlPriceRepository,
  86.         FrontService            $frontService
  87.     ): Response
  88.     {
  89.         $hotels = [];
  90.         $theme $frontThemeRepository->findOneBy(['name' => $name]);
  91.         $description "";
  92.         if ($theme) {
  93.             $description $theme->getDescription();
  94.         }
  95.         $hotelXmlPrices $hotelXmlPriceRepository->getHotelXmlPriceByCriterias(['themes' => $name]);
  96.         foreach ($hotelXmlPrices as $HotelXmlPrice) {
  97.             $hotels[] = $frontService->getItemHotel($HotelXmlPrice);
  98.         }
  99.         return $this->render('front/hotel/hotel_by_theme.html.twig', [
  100.             'hotels' => $hotels,
  101.             'description' => $description,
  102.             'name' => $name,
  103.             'society' => $this->parameterService->getSocietyParameters(),
  104.             'social_networks' => $this->frontService->getSocialNetworks(),
  105.             'agencies' => $this->frontService->getAgencies(),
  106.             'currencies' => $this->frontService->getCurrencies(),
  107.             'tracking_tools' => $this->frontService->getTrackingTools()
  108.         ]);
  109.     }
  110.     #[Route('/search'name'hotel_search'methods: ['GET''POST'])]
  111.     public function hotelSearch(Request $requestFrontRecentSearchService $frontRecentSearchService): Response
  112.     {
  113.         $data $request->get("data"); // data : JSON request
  114.         $requestData json_decode($datatrue);
  115.         $date = new DateTime();
  116.         // Output the date in default format
  117.         $currentDate $date->format('Y-m-d'); // Example: 2025-01-02 15:30:45
  118.         if ($requestData['checkIn'] < $currentDate || $requestData['checkOut'] < $currentDate) {
  119.             return $this->redirectToRoute('app_main');
  120.         }
  121.         $customer $this->apiAuthenticationService->getCustomer($this->getUser());
  122.         if (is_null($customer)) {
  123.             $this->addFlash('error''UNKNOWN_CUSTOMER');
  124.             return $this->redirectToRoute('front_info_message');
  125.         }
  126.         $hotel_search_history $frontRecentSearchService->updateHotelSearchHistory($requestData$data);
  127.         $frontMenus $this->frontService->getFrontMenu();
  128.         return $this->render('front/hotel/search.html.twig', [
  129.             'requestData' => $requestData,
  130.             'customer' => $customer,
  131.             'society' => $this->parameterService->getSocietyParameters(),
  132.             'social_networks' => $this->frontService->getSocialNetworks(),
  133.             'agencies' => $this->frontService->getAgencies(),
  134.             'currencies' => $this->frontService->getCurrencies(),
  135.             'hotel_search_history' => $hotel_search_history,
  136.             'sections' => $this->frontService->getSections(),
  137.             'currencySwitcher' => true,
  138.             'tracking_tools' => $this->frontService->getTrackingTools()
  139.         ]);
  140.     }
  141.     #[Route('/formsearch/{city_label}/{hotel_label}'name'hotel_form_search'methods: ['GET''POST'])]
  142.     public function hotelFormSearch(Request $requeststring $city_labelstring $hotel_label): Response
  143.     {
  144.         $requestData json_decode($request->get("data"), true);
  145.         return $this->render('front/hotel/form_search.html.twig', [
  146.             'requestData' => $requestData,
  147.             'hotel_label' => $hotel_label,
  148.             'city_label' => $city_label
  149.             /* 'society' => $this->parameterService->getSocietyParameters(),
  150.              'social_networks' => $this->frontService->getSocialNetworks(),
  151.              'agencies' => $this->frontService->getAgencies(),
  152.              'currencies' => $this->frontService->getCurrencies(),*/
  153.         ]);
  154.     }
  155.     #[Route('/search_json'name'hotel_search_json'methods: ['GET''POST'])]
  156.     public function hotelSearchJson(Request $requestHotelApiService $hotelApiService): JsonResponse
  157.     {
  158.         // ----- PREPARE the HttpRequestCall ----- //
  159.         // Get auth_data : user, timestamp, signature
  160.         $user $this->getUser();
  161.         $auth_data $this->apiAuthenticationService->getAuthenticationArray($user);
  162.         $customer $this->apiAuthenticationService->getCustomer($user);
  163.         $showResellingRates false;
  164.         if ($customer instanceof CustomerMoral) {
  165.             if ($customer->getResellerMargin() > && (in_array('ROLE_B2B_ADMIN'$user->getRoles()) || in_array('ROLE_B2B_AGENT'$user->getRoles()))) {
  166.                 $showResellingRates true;
  167.             }
  168.         }
  169.         $requestJson json_decode($request->getContent(), true);
  170.         $xmlSourceId $request->headers->get('xmlSourceId') ?? 0;
  171.         $base_uri $request->getSchemeAndHttpHost();
  172.         // ----- PERFORM the HttpRequestCall ----- //
  173.         $data $hotelApiService->query_ApiHotel_Availability($auth_data$requestJson$xmlSourceId$base_uritrue);
  174.         $today DateTime::createFromFormat("Y-m-d"date("Y-m-d"));
  175.         if (count($data['content']) > 0) { // Re-format data to fit the FRONT-B2C :::: VIEW_BY_BOARD ::::
  176.             $nbRooms count($requestJson['occupancies']);
  177.             $content_front = []; // prepare data to fit the front design
  178.             foreach ($data['content'] as $hotel) {
  179.                 $hotel_key strtoupper($hotel['hotel']['name']); // HOTEL_ITEM_IDENTIFIER TO GROUP MULTIPLE HOTELS WITH SAME NAME
  180.                 $hotel_offer_count 0;
  181.                 if (isset($content_front[$hotel_key]['offersCount'])) {
  182.                     $hotel_offer_count $content_front[$hotel_key]['offersCount'];
  183.                 }
  184.                 $content_front[$hotel_key]['hotel'] = $hotel['hotel'];
  185.                 $content_front[$hotel_key]['hotel']['customRating'] = $this->hotelApiService->getHotelCustomRating($hotel['hotel']['name']);
  186.                 // TODO Add function getReviewRating(hotelName, reviewsSource);
  187.                 $content_front[$hotel_key]['hotel']['reviewRating'] = null;//rand(4, 10); // TODO GET RATING FROM External src like Tripadvisor
  188.                 if (count($hotel['sources']) > 0) {
  189.                     $content_front[$hotel_key]['hotel']['facilities'] = $hotel['sources'][0]['facilities'];
  190.                 }
  191.                 $content_front[$hotel_key]['hotel']['promos'] = [];
  192.                 $content_front[$hotel_key]['hotel']['discounts'] = [];
  193.                 $content_front[$hotel_key]['hotel']['infos'] = [];
  194.                 foreach ($hotel['sources'] as $hotel_source) {
  195.                     $sourceKey $hotel['hotel']['id']; // considering the full-hotel-id as the sourceKey (string containing the end-source-id)
  196.                     $content_front[$hotel_key]['sources'][$sourceKey]['sourceKey'] = $sourceKey;
  197.                     $hotelSourcePrice 0;
  198.                     $hotelSourceAvailable false;
  199.                     $hotelSourceFreeCancellation false;
  200.                     $tab_boards = [];
  201.                     foreach ($hotel_source['rooms'] as $index => $index_hrts) { // room_index
  202.                         // SORT hrts for current hotel-source by salePromoRate
  203.                         usort($index_hrts, function ($a$b) {
  204.                             return $a['salePromoRate'] <=> $b['salePromoRate']; // Ascending
  205.                         });
  206.                         foreach ($index_hrts as $hrt) {
  207.                             // freeCancellation FLAG to use in React Filter (i.e. is there a possibility to cancel without fees )
  208.                             if ($hrt['NRF']) {
  209.                                 $hrt['freeCancellation'] = false;
  210.                             } else {
  211.                                 if (is_null($hrt['deadline'])) {
  212.                                     $hrt['freeCancellation'] = true;
  213.                                 } else {
  214.                                     $deadline = new DateTime($hrt['deadline']);
  215.                                     $difference date_diff($today$deadline); // Interval before deadline.
  216.                                     if ($difference->invert) { // deadline is exceeded
  217.                                         $hrt['freeCancellation'] = false;
  218.                                     } else {
  219.                                         $hrt['freeCancellation'] = true;
  220.                                     }
  221.                                 }
  222.                             }
  223.                             $hotelSourceFreeCancellation $hotelSourceFreeCancellation || $hrt['freeCancellation'];
  224.                             // END freeCancellation FLAG
  225.                             // SHOW SaleResellingRates rather thant saleRate applied to the customer
  226.                             if ($showResellingRates) {
  227.                                 $hrt['salePromoRate'] = $hrt['salePromoRate'] * (100.0 $customer->getResellerMargin()) / 100.0;
  228.                                 $hrt['saleRate'] = $hrt['saleRate'] * (100.0 $customer->getResellerMargin()) / 100.0;
  229.                             }
  230.                             $content_front[$hotel_key]['sources'][$sourceKey]['hrts'][$hrt['boardName']][$index][] = $hrt;
  231.                             $tab_boards[$hrt['boardName']] = $hrt['boardXmlName'];
  232.                             // Increment the number of available-offers
  233.                             if ($hrt['available']) {
  234.                                 $hotel_offer_count++;
  235.                                 $hotelSourceAvailable true;
  236.                             }
  237.                         }
  238.                     } // END Room Indexes
  239.                     // HOTEL BADGES (Promos, Discounts, Infos)
  240.                     foreach ($hotel_source['promos'] as $item) {
  241.                         $content_front[$hotel_key]['hotel']['promos'][] = $item;
  242.                     }
  243.                     foreach ($hotel_source['discounts'] as $item) {
  244.                         $content_front[$hotel_key]['hotel']['discounts'][] = $item;
  245.                     }
  246.                     foreach ($hotel_source['infos'] as $item) {
  247.                         $content_front[$hotel_key]['hotel']['infos'][] = $item;
  248.                     }
  249.                     $bestOffers $this->hotelApiService->getBestPriceBoard($content_front[$hotel_key], $sourceKey);
  250.                     $content_front[$hotel_key]['sources'][$sourceKey]['bestPrice'] = $bestOffers['bestPrice_currentSourceKey'];
  251.                     $content_front[$hotel_key]['bestPrice'] = $bestOffers['bestPrice'];
  252.                     $content_front[$hotel_key]['bestBoard'] = $bestOffers['bestBoard'];
  253.                     $content_front[$hotel_key]['bestRooms'] = $this->helpers->formatArrayCounts($bestOffers['bestRooms']);
  254.                     $content_front[$hotel_key]['offersCount'] = $hotel_offer_count;
  255.                     foreach ($content_front[$hotel_key]['sources'][$sourceKey]['hrts'] as $board_key => $board_hrts) {
  256.                         if (count($board_hrts) < $nbRooms) {
  257.                             unset($content_front[$hotel_key]['sources'][$sourceKey]['hrts'][$board_key]);
  258.                         }
  259.                     }
  260.                     foreach ($tab_boards as $board_key => $board_value) {
  261.                         $content_front[$hotel_key]['sources'][$sourceKey]['boards'][] =
  262.                             [
  263.                                 'key' => $board_value,
  264.                                 'value' => $board_value,
  265.                             ];
  266.                     }
  267.                     // set general stock
  268.                     $content_front[$hotel_key]['sources'][$sourceKey]['generalStock'] = $hotel_source['generalStock'];
  269.                     // set search code
  270.                     $content_front[$hotel_key]['sources'][$sourceKey]['searchCode'] = $hotel_source['searchCode'];
  271.                     // set source id
  272.                     $content_front[$hotel_key]['sources'][$sourceKey]['xmlSourceId'] = $hotel_source['xmlSourceId'];
  273.                     // set xml source name
  274.                     $content_front[$hotel_key]['sources'][$sourceKey]['xmlSourceName'] = $hotel_source['xmlSourceName'];
  275.                     // set hotel Source available
  276.                     $content_front[$hotel_key]['sources'][$sourceKey]['available'] = $hotelSourceAvailable;
  277.                     // set hotel Source available
  278.                     $content_front[$hotel_key]['sources'][$sourceKey]['freeCancellation'] = $hotelSourceFreeCancellation;
  279.                     // set hotel Source available
  280.                     $content_front[$hotel_key]['sources'][$sourceKey]['associationRequired'] = $hotel_source['associationRequired'];
  281.                 }
  282.             }
  283.             // array values hotels
  284.             $data['content'] = array_values($content_front);
  285.             // array values sources
  286.             foreach ($data['content'] as &$hotel) {
  287.                 usort($hotel['sources'], function ($a$b) {
  288.                     return $a['bestPrice'] <=> $b['bestPrice']; // Ascending
  289.                 });
  290.                 $hotel['sources'] = array_values($hotel['sources']);
  291.                 foreach ($hotel['sources'] as &$source) {
  292.                     $source['hrts'] = array_values($source['hrts']);
  293.                 }
  294.             }
  295.         }
  296.         return $this->json($data);
  297.     }
  298.     #[Route('/autocomplete'name'hotel_autocomplete'methods: ['GET'])]
  299.     public function autoComplete(Request $request): JsonResponse
  300.     {
  301.         $query $request->query->get('query');
  302.         if ($query == null) {
  303.             return $this->json([]);
  304.         }
  305.         $query_trim trim($query);
  306.         $headers = [
  307.             'Accept' => 'application/json',
  308.             'Content-Type' => 'application/json',
  309.         ];
  310.         try {
  311.             $httpClient HttpClient::create();
  312.             $response $httpClient->request('GET'$request->getSchemeAndHttpHost() . '/api/hotel/autocomplete', [
  313.                 'headers' => $headers,
  314.                 'query' => ['query' => $query_trim]
  315.             ]);
  316.             $data json_decode($response->getContent(), true);
  317.             return $this->json($data['response']);
  318.         } catch (Exception $e) {
  319.             return $this->json(['error' => $e->getMessage()]);
  320.         }
  321.     }
  322.     /**
  323.      * The detail of selected offer from the front : hotel search results page. --- Check-Rate ---
  324.      * Supposed to be called either from "hotel/search or "orderline_add_room"
  325.      */
  326.     #[Route('/offer-detail'name'hotel_offer_detail'methods: ['GET''POST'])]
  327.     public function hotelOfferDetail(
  328.         Request             $request,
  329.         OrderLineRepository $orderLineRepository,
  330.         OrderRepository     $orderRepository,
  331.     ): Response
  332.     {
  333.         $guestData = [];
  334.         if ($request->isMethod('POST')) {
  335.             $guestData = [
  336.                 'first_name' => $request->request->get('first_name'),
  337.                 'last_name' => $request->request->get('last_name'),
  338.                 'phone' => $request->request->get('phone'),
  339.                 'email' => $request->request->get('email'),
  340.             ];
  341.             $request->getSession()->set('guestData'$guestData);
  342.         }
  343.         $customer $this->apiAuthenticationService->getCustomer($this->getUser());
  344.         $auth_data $this->apiAuthenticationService->getAuthenticationArray($this->getUser());
  345.         $rooms_rate_keys = [];
  346.         for ($i 0$i $request->get('nbRooms'); $i++) {
  347.             $rooms_rate_keys[] = ["rateKey" => $request->get("room-" $i)];
  348.         }
  349.         $checkRateRequest = [
  350.             'rooms' => $rooms_rate_keys,
  351.             'searchCode' => $request->get('searchCode')
  352.         ];
  353.         $saleCurrency $this->currencyService->getSessionCurrency();
  354.         $product_fee $this->frontService->getProductFee(ModuleEnum::hotel->getValue(), $customer);
  355.         /** call api/hotel/checkrate */
  356.         $httpClient HttpClient::create();
  357.         $response $httpClient->request(
  358.             'POST'$request->getUriForPath('/') . 'api/hotel/checkrate',
  359.             [
  360.                 'headers' => [
  361.                     'Accept' => 'application/json',
  362.                     'Content-Type' => 'application/json',
  363.                     'currency' => $saleCurrency,
  364.                     'user' => $auth_data['user'],
  365.                     'timestamp' => $auth_data['timestamp'],
  366.                     'signature' => hash('sha256'$auth_data['signature'])
  367.                 ],
  368.                 'json' => $checkRateRequest
  369.             ]
  370.         );
  371.         $responseData json_decode($response->getContent(), true);
  372.         $searchCriteriaArray json_decode($request->get("searchCriteria"), true);
  373.         $twig_parameters = [
  374.             'searchCriteriaArray' => $searchCriteriaArray,
  375.             'searchCode' => null,
  376.             'checkRateData' => null,
  377.             "social_networks" => $this->frontService->getSocialNetworks(),
  378.             'menu_front' => $this->frontService->getFrontMenu(),
  379.             "currencies" => $this->frontService->getCurrencies(),
  380.             "agencies" => $this->frontService->getAgencies(),
  381.             "society" => $this->parameterService->getSocietyParameters(),
  382.             "referenceCurrency" => $this->parameterService->getReferenceCurrency(),
  383.             'guestData' => $guestData,
  384.             'action' => '',
  385.             'customer' => $customer
  386.         ];
  387.         $template "";
  388.         if (is_null($responseData) or $responseData['error']) {
  389.             // CASE 0 : Template for Error
  390.             return $this->render("front/hotel/offer_error.html.twig"$twig_parameters);
  391.         } else {
  392.             $checkRateData reset($responseData['content']);
  393.             $checkRateData['available'] = $this->hotelApiService->isAvailable($checkRateData) ? "true" "false";
  394.             $rooms = [];
  395.             foreach ($checkRateData['sources'][0]['rooms'] as $room_index) {
  396.                 $rooms[] = $room_index[0]; // the selected room for the index.
  397.             }
  398.             //$checkRateData['cancellation'] = $this->hotelApiService->getDeadlineFromRooms($rooms);
  399.             $twig_parameters['checkRateData'] = $checkRateData;
  400.             $twig_parameters['searchCode'] = $responseData['searchCode'];
  401.             $twig_parameters['product_fee'] = $product_fee;
  402.             $twig_parameters['META_PIXEL_ID'] = $this->parameterService->getTrackerMetaId();
  403.             // CASE 1 : Template to add something to an existing orderline
  404.             if ($request->get('orderlineId')) {
  405.                 $orderlineID $request->get('orderlineId');
  406.                 $twig_parameters['orderLine'] = $orderLineRepository->find($orderlineID);
  407.                 $twig_parameters['action'] = $this->generateUrl("orderline_add_room", ['id' => $orderlineID]);
  408.             }
  409.             // CASE 2 : new Order and new OrderLine
  410.             if (empty($request->get('orderlineId')) && empty($request->get('orderId'))) { //
  411.                 $twig_parameters['action'] = $this->generateUrl("app_admin_add_cart_line_hotel");
  412.             }
  413.             // CASE 3 : Add new OrderLine into an existing Order
  414.             if (empty($request->get('orderlineId')) && $request->get('orderId')) { //
  415.                 $orderID $request->get('orderId');
  416.                 $twig_parameters['order'] = $orderRepository->find($orderID);
  417.                 $twig_parameters['action'] = $this->generateUrl("order_add_orderline_hotel_book", ['id' => $orderID]);
  418.             }
  419.             return $this->render("front/hotel/offer_detail.html.twig"$twig_parameters);
  420.         }
  421.     }
  422.     /**
  423.      *  The detail of a given hotelId
  424.      */
  425.     #[Route('/{country_label}/{city_label}/{hotel_label}/{id}'name'hotel_detail'methods: ['GET'])]
  426.     public function hotelDetail(
  427.         Request            $request,
  428.                            $id,
  429.         string             $city_label,
  430.         string             $hotel_label,
  431.         ParameterService   $parameterService,
  432.         HotelXmlRepository $hotelXmlRepository
  433.     ): Response
  434.     {
  435.         $hotel_id_elements explode("|"$id);
  436.         $xmlSourceId $hotel_id_elements[0];
  437.         //        dd($xmlSourceId);
  438.         $codeHotel $hotel_id_elements[2];
  439.         if ($xmlSourceId == 0) {
  440.             $hotelDetails $this->hotelApiService->hotelDetails($codeHotel$request->getLocale());
  441.         } else {
  442.             $hotelDetails $this->hotelApiService->hotelDetailsHUB($id);
  443.         }
  444.         $recommendedHotels $hotelXmlRepository->getRecommendedHotels($city_label$id);
  445.         $currentDateTime = new DateTime();
  446.         $arrivalDate $currentDateTime->format('Y-m-d');//
  447.         $departureDate = new DateTime("tomorrow");
  448.         $departureDate $departureDate->format('Y-m-d');
  449.         return $this->render('front/hotel/hotel_detail.html.twig', [
  450.             'hotel_data' => $hotelDetails,
  451.             'society' => $this->parameterService->getSocietyParameters(),
  452.             'social_networks' => $this->frontService->getSocialNetworks(),
  453.             'currencies' => $this->frontService->getCurrencies(),
  454.             'agencies' => $this->frontService->getAgencies(),
  455.             'menu_front' => $this->frontService->getFrontMenu(),
  456.             'recommendedHotels' => $recommendedHotels,
  457.             "arrivalDate" => $arrivalDate,
  458.             "departureDate" => $departureDate,
  459.             "city_label" => $city_label,
  460.             "hotel_label" => $hotel_label,
  461.             "META_PIXEL_ID" => $this->parameterService->getTrackerMetaId(),
  462.             'faqs' => $this->frontService->getFaqs(FaqRouteEnum::HOTEL_DETAIL$id),
  463.         ]);
  464.     }
  465.     #[Route('/{id}/gallery'name'hotel_gallery_json'methods: ['GET'])]
  466.     public function hotelGallery(
  467.         Request $request,
  468.                 $id
  469.     ): JsonResponse
  470.     {
  471.         $auth_data $this->apiAuthenticationService->getAuthenticationArray($this->getUser());
  472.         // call hotel detail and get images
  473.         $hotelDetailsRequest = [
  474.             'hotelId' => $id
  475.         ];
  476.         $httpClient HttpClient::create();
  477.         $response $httpClient->request(
  478.             'POST'$request->getUriForPath('/') . 'api/hotel/details',
  479.             [
  480.                 'headers' => [
  481.                     'Accept' => 'application/json',
  482.                     'Content-Type' => 'application/json',
  483.                     'user' => $auth_data['user'],
  484.                     'timestamp' => $auth_data['timestamp'],
  485.                     'signature' => hash('sha256'$auth_data['signature'])
  486.                 ],
  487.                 'json' => $hotelDetailsRequest
  488.             ]
  489.         );
  490.         $responseData json_decode($response->getContent(), true);
  491.         if (is_null($responseData) || !key_exists('content'$responseData)) {
  492.             return $this->json([]);
  493.         }
  494.         $hotel_data_array $responseData['content']['images'];
  495.         return $this->json($hotel_data_array);
  496.     }
  497.     /**
  498.      * @throws Exception
  499.      */
  500.     #[Route('/promo'name'app_front_hotel_promo'methods: ['GET'])]
  501.     public function hotelPromo(HotelXmlPriceRepository $hotelXmlPriceRepository,
  502.                                FrontService            $frontService
  503.     ): Response
  504.     {
  505.         $hotels = [];
  506.         $hotelXmlPricesWithPromos $hotelXmlPriceRepository->getHotelXmlPriceWithPromos();
  507.         foreach ($hotelXmlPricesWithPromos as $hotelXmlPricesWithPromo) {
  508.             $hotels[] = $frontService->getItemHotel($hotelXmlPricesWithPromo);
  509.         }
  510.         return $this->render('front/hotel/hotel_promo.html.twig', [
  511.             'hotelsWithPromos' => $hotels,
  512.             'social_networks' => $this->frontService->getSocialNetworks(),
  513.             'currencies' => $this->frontService->getCurrencies(),
  514.             'agencies' => $this->frontService->getAgencies(),
  515.             'society' => $this->parameterService->getSocietyParameters(),
  516.             'currencySwitcher' => true,
  517.             'META_PIXEL_ID' => $this->parameterService->getTrackerMetaId()
  518.         ]);
  519.     }
  520.     #[Route('/cart/addProductHotel'name'app_admin_add_cart_line_hotel'methods: ['POST'])]
  521.     public function addProductHotel(
  522.         Request          $request,
  523.         SessionInterface $session,
  524.         CartService      $cartService,
  525.     ): RedirectResponse
  526.     {
  527.         $cart_array $cartService->initCart($this->getUser(), $request);
  528.         /* 2- add a line into cart_array['products'] */
  529.         $nbRooms $request->get("nbRooms"); // quantity : nbRooms
  530.         $cart_line_label $request->get('product_label');
  531.         $cart_line_date $request->get('product_date');
  532.         $beneficiaryName $request->get('beneficiary_name');
  533.         if (empty($beneficiaryName)) {
  534.             $beneficiaryFirstName trim((string) $request->get('beneficiary_first_name'''));
  535.             $beneficiaryLastName trim((string) $request->get('beneficiary_last_name'''));
  536.             $beneficiaryName trim($beneficiaryFirstName ' ' $beneficiaryLastName);
  537.         }
  538.         if (empty($beneficiaryName)) {
  539.             $beneficiaryName trim(
  540.                 (string) $request->get('room_0_pax_0_firstName''') . ' ' .
  541.                 (string) $request->get('room_0_pax_0_lastName''')
  542.             );
  543.         }
  544.         $beneficiaryEmail $request->get('beneficiary_email');
  545.         $product_price $request->get("totalAmount");
  546.         $productFee $request->get('product_fee') ?? 0;
  547.         $dates explode('-'$cart_line_date);
  548.         $date1 DateTime::createFromFormat('d/m/Y'$dates[0]);
  549.         $date2 DateTime::createFromFormat('d/m/Y'$dates[1]);
  550.         $interval $date1->diff($date2);
  551.         $nbNights $interval->days;
  552.         $hotel_with_party false;
  553.         $product_elements null;
  554.         for ($room_idx 0$room_idx $nbRooms$room_idx++) {
  555.             /* guests */
  556.             $room_guests = [];
  557.             $room_adults_count $request->get("room_" $room_idx "_adults_count");
  558.             $room_children_count $request->get("room_" $room_idx "_children_count");
  559.             $room_guest_count $room_adults_count $room_children_count;
  560.             for ($guest_index 0$guest_index $room_guest_count$guest_index++) {
  561.                 $paxCivility $request->get("room_" $room_idx "_pax_" $guest_index "_civility");
  562.                 $paxFirstName $request->get("room_" $room_idx "_pax_" $guest_index "_firstName");
  563.                 $paxLastName $request->get("room_" $room_idx "_pax_" $guest_index "_lastName");
  564.                 $paxAge $request->get("room_" $room_idx "_pax_" $guest_index "_age");
  565.                 if (!empty($paxFirstName) || !empty($paxLastName)) {
  566.                     // Use the detailed per-pax data
  567.                     $guest = [
  568.                         'civility' => $paxCivility ?? '',
  569.                         'firstName' => $paxFirstName ?? '',
  570.                         'lastName' => $paxLastName ?? '',
  571.                         'age' => $paxAge,
  572.                     ];
  573.                 } else {
  574.                     // Fallback: use the primary beneficiary name (backward compatibility)
  575.                     $guest = [
  576.                         'civility' => '',
  577.                         'firstName' => $beneficiaryName,
  578.                         'lastName' => '-',
  579.                     ];
  580.                 }
  581.                 $room_guests[] = $guest;
  582.             }
  583.             $room = [
  584.                 'rateKey' => $request->get("room_" $room_idx "_key"),
  585.                 'label' => $request->get("room_" $room_idx "_label"),
  586.                 'isAvailable' => $request->get("room_" $room_idx "_available"),
  587.                 'price' => $request->get("room_" $room_idx "_price"),
  588.                 'adultsCount' => $room_adults_count,
  589.                 'childrenCount' => $room_children_count,
  590.                 'guests' => $room_guests
  591.             ];
  592.             $room_nb_parties $request->get("room_" $room_idx "_nbParties");
  593.             $room_parties_zones = [];
  594.             if (!is_null($room_nb_parties)) { // if there is parties associated to the current room
  595.                 $hotel_with_party true;
  596.                 for ($party_index 0$party_index $room_nb_parties$party_index++) {
  597.                     $room_parties_zones[] = $request->get("room_" $room_idx "_party_" $party_index);
  598.                 }
  599.                 $room['parties'] = $room_parties_zones;
  600.                 //$product_price+= $request->get("partyPrice_".$party_index);
  601.             }
  602.             $product_elements[] = $room;
  603.         }
  604.         if ($request->isMethod('POST')) {
  605.             $guestData = [
  606.                 'first_name' => $request->request->get('first_name'),
  607.                 'last_name' => $request->request->get('last_name'),
  608.                 'phone' => $request->request->get('phone'),
  609.                 'email' => $request->request->get('email'),
  610.             ];
  611.             $request->getSession()->set('guestData'$guestData);
  612.         }
  613.         $cart_line_hotel = [
  614.             'module' => ModuleEnum::hotel->getValue(),
  615.             'elements' => $product_elements,
  616.             'label' => $cart_line_label,
  617.             'date' => $cart_line_date,
  618.             'checkIn' => $dates[0],
  619.             'checkOut' => $dates[1],
  620.             'nbNights' => $nbNights,
  621.             'quantity' => $nbRooms,
  622.             'options' => $request->get("options"),
  623.             'comment' => $request->get("comment"),
  624.             'price' => $product_price,
  625.             'fee' => $productFee,
  626.             'available' => $request->get('available'),
  627.             'searchCode' => $request->get('searchCode'),
  628.             'tokenForBook' => $request->get('tokenForBook'),
  629.             'beneficiary' => [
  630.                 'name' => $beneficiaryName,
  631.                 'email' => $beneficiaryEmail
  632.             ],
  633.             'image' => $request->get('hotel_image'),
  634.             'city' => $request->get('city'),
  635.             'country' => $request->get('country'),
  636.             'rating' => $request->get('rating'),
  637.             'guestData' => $guestData,
  638.         ];
  639.         $cart_array['products'][] = $cart_line_hotel// add a cart line ( a product )
  640.         $session->set("cart"$cart_array);
  641.         //return $this->redirectToRoute('app_shared_cart_index', [], Response::HTTP_SEE_OTHER);
  642.         return $this->redirectToRoute('app_shared_cart_hotel_product_index', [], Response::HTTP_SEE_OTHER);
  643.     }
  644.     #[Route('/search_stream'name'hotel_search_stream'methods: ['POST'])]
  645.     public function hotelSearchStream(
  646.         Request         $request,
  647.         HotelApiService $hotelApiService
  648.     ): StreamedResponse
  649.     {
  650.         $requestJson json_decode($request->getContent(), true);
  651.         $xmlSourceId $request->headers->get('xmlSourceId') ?? 0;
  652.         $user $this->getUser();
  653.         $auth_data $this->apiAuthenticationService->getAuthenticationArray($user);
  654.         $customer $this->apiAuthenticationService->getCustomer($user);
  655.         $showResellingRates $this->shouldShowResellingRates($customer$user);
  656.         $nbRooms count($requestJson['occupancies'] ?? []);
  657.         $baseUri $request->getSchemeAndHttpHost();
  658.         return new StreamedResponse(function () use (
  659.             $requestJson$xmlSourceId$auth_data$customer,
  660.             $showResellingRates$nbRooms$baseUri$hotelApiService$request
  661.         ) {
  662.             $startTime microtime(true);
  663.             // ════════════════════════════════════════════════
  664.             // SSE INIT
  665.             // ════════════════════════════════════════════════
  666.             $request->getSession()?->save();
  667.             @ini_set('output_buffering''0');
  668.             @ini_set('zlib.output_compression''0');
  669.             @ini_set('implicit_flush''1');
  670.             while (ob_get_level() > 0ob_end_clean();
  671.             ob_implicit_flush(true);
  672.             echo ":" str_repeat(" "2048) . "\n\n";
  673.             flush();
  674.             // ════════════════════════════════════════════════
  675.             // CONFIG
  676.             // ════════════════════════════════════════════════
  677.             $batchSize 150;
  678.             $totalLoaded 0;
  679.             $httpClient HttpClient::create([
  680.                 'timeout' => 120,
  681.                 'max_duration' => 180,
  682.             ]);
  683.             // ════════════════════════════════════════════════
  684.             // HELPER — flush hôtels en SSE
  685.             // ════════════════════════════════════════════════
  686.             $flushContent = function (array $content) use (
  687.                 &$totalLoaded$nbRooms$showResellingRates$customer$batchSize
  688.             ): void {
  689.                 if (empty($content)) return;
  690.                 $chunkData $this->buildContentFront(
  691.                     $content$nbRooms$showResellingRates$customer
  692.                 );
  693.                 $buffer = [];
  694.                 foreach ($chunkData as $hotel) {
  695.                     $buffer[] = $hotel;
  696.                     $totalLoaded++;
  697.                     if (count($buffer) >= $batchSize) {
  698.                         $this->emitSSE('hotels_batch'$buffer);
  699.                         $buffer = [];
  700.                     }
  701.                     if (connection_aborted()) return;
  702.                 }
  703.                 if (!empty($buffer)) {
  704.                     $this->emitSSE('hotels_batch'$buffer);
  705.                 }
  706.                 unset($buffer$chunkData);
  707.             };
  708.             // ════════════════════════════════════════════════
  709.             // PAGE 1 — bloquant pour récupérer pagesCount
  710.             // ════════════════════════════════════════════════
  711.             try {
  712.                 $req1 $requestJson;
  713.                 $req1['page'] = 1;
  714.                 $response1 $hotelApiService->query_ApiHotel_Availability_Async(
  715.                     $auth_data$req1$xmlSourceId$baseUri$httpClienttrue
  716.                 );
  717.                 $raw1 $response1->getContent(false);
  718.                 $data1 json_decode($raw1true);
  719.                 $content1 $data1['content'] ?? [];
  720.                 if (empty($content1)) {
  721.                     $this->logger->info('content-page-1',[$content1]);
  722.                     $this->emitSSE('done', [
  723.                         'total' => 0,
  724.                         'message' => 'empty result set',
  725.                         'time_seconds' => round(microtime(true) - $startTime2),
  726.                     ]);
  727.                     return;
  728.                 }
  729.                 $flushContent($content1);
  730.                 $pagesCount = (int)($data1['pagesCount'] ?? 0);
  731.                 $this->emitSSE('progress', [
  732.                     'page' => 1,
  733.                     'pagesCount' => $pagesCount,
  734.                     'loaded' => $totalLoaded,
  735.                 ]);
  736.                 unset($data1$content1$response1$req1);
  737.             } catch (\Throwable $e) {
  738.                 $this->emitSSE('done', [
  739.                     'total' => 0,
  740.                     'message' => 'error page 1: ' $e->getMessage(),
  741.                     'time_seconds' => round(microtime(true) - $startTime2),
  742.                 ]);
  743.                 return;
  744.             }
  745.             if ($pagesCount <= 1) {
  746.                 $this->emitSSE('done', [
  747.                     'total' => $totalLoaded,
  748.                     'message' => 'completed (single page)',
  749.                     'time_seconds' => round(microtime(true) - $startTime2),
  750.                 ]);
  751.                 return;
  752.             }
  753.             // ════════════════════════════════════════════════
  754.             // PAGES 2..N — toutes en parallèle d'un seul coup
  755.             // ════════════════════════════════════════════════
  756.             if ($pagesCount && !connection_aborted()) {
  757.                 // Lancer pages 2..pagesCount toutes simultanément
  758.                 $responses = [];
  759.                 for ($p 2$p <= $pagesCount$p++) {
  760.                     $req $requestJson;
  761.                     $req['page'] = $p;
  762.                     $responses[$p] = $hotelApiService->query_ApiHotel_Availability_Async(
  763.                         $auth_data$req$xmlSourceId$baseUri$httpClienttrue
  764.                     );
  765.                 }
  766.                 // Collecter et streamer dès qu'une page arrive
  767.                 foreach ($httpClient->stream($responses120) as $response => $chunk) {
  768.                     if (connection_aborted()) return;
  769.                     if ($chunk->isTimeout()){
  770.                         $this->logger->info('isTimeout');
  771.                         continue;
  772.                     }
  773.                     if (!$chunk->isLast()) {
  774.                         continue;
  775.                     }
  776.                     try {
  777.                         $raw $response->getContent(false);
  778.                         if (empty($raw)) {
  779.                             continue;
  780.                         }
  781.                         $data json_decode($rawtrue);
  782.                         if (!is_array($data)) {
  783.                             continue;
  784.                         }
  785.                         $content $data['content'] ?? [];
  786.                         if (empty($content)) {
  787.                             continue;
  788.                         } else {
  789.                             $flushContent($content);
  790.                         }
  791.                     } catch (\Exception $e) {
  792.                         $this->logger->error($e->getMessage());
  793.                         continue;
  794.                     }
  795.                     unset($data$content);
  796.                 }
  797.                 $this->emitSSE('progress', [
  798.                     'page' => $pagesCount,
  799.                     'pagesCount' => $pagesCount,
  800.                     'loaded' => $totalLoaded,
  801.                 ]);
  802.                 unset($responses);
  803.             }
  804.             $duration round(microtime(true) - $startTime2);
  805.             $this->logger->info('hotelSearchStream completed in ' $duration 's — ' $totalLoaded ' hotels');
  806.             $this->emitSSE('done', [
  807.                 'total' => $totalLoaded,
  808.                 'message' => 'stream completed',
  809.                 'time_seconds' => $duration,
  810.             ]);
  811.         }, 200, [
  812.             'Content-Type' => 'text/event-stream',
  813.             'Cache-Control' => 'no-cache, no-transform',
  814.             'X-Accel-Buffering' => 'no',
  815.             'Connection' => 'keep-alive',
  816.         ]);
  817.     }
  818.     private function emitSSE(string $eventmixed $data): void
  819.     {
  820.         $payload json_encode(
  821.             $data,
  822.             JSON_UNESCAPED_UNICODE JSON_PARTIAL_OUTPUT_ON_ERROR
  823.         );
  824.         if ($payload === false) {
  825.             $payload json_encode(['error' => 'json encode failed']);
  826.         }
  827.         echo "event: {$event}\n";
  828.         echo "data: {$payload}\n\n";
  829.         // flush sécurisé
  830.         if (ob_get_length()) {
  831.             @ob_flush();
  832.         }
  833.         @flush();
  834.     }
  835.     private function buildContentFront(
  836.         array $hotels,
  837.         int   $nbRooms,
  838.         bool  $showResellingRates,
  839.         mixed $customer
  840.     ): array
  841.     {
  842.         $today = new DateTime();
  843.         $content_front = []; // prepare data to fit the front design
  844.         foreach ($hotels as $hotel) {
  845.             $hotel_key strtoupper($hotel['hotel']['name']); // HOTEL_ITEM_IDENTIFIER TO GROUP MULTIPLE HOTELS WITH SAME NAME
  846.             $hotel_offer_count 0;
  847.             if (isset($content_front[$hotel_key]['offersCount'])) {
  848.                 $hotel_offer_count $content_front[$hotel_key]['offersCount'];
  849.             }
  850.             $content_front[$hotel_key]['hotel'] = $hotel['hotel'];
  851.             $content_front[$hotel_key]['hotel']['customRating'] = $this->hotelApiService->getHotelCustomRating($hotel['hotel']['name']);
  852.             // TODO Add function getReviewRating(hotelName, reviewsSource);
  853.             $content_front[$hotel_key]['hotel']['reviewRating'] = rand(410); // TODO GET RATING FROM External src like Tripadvisor
  854.             if (count($hotel['sources']) > 0) {
  855.                 $content_front[$hotel_key]['hotel']['facilities'] = $hotel['sources'][0]['facilities'];
  856.             }
  857.             $content_front[$hotel_key]['hotel']['promos'] = [];
  858.             $content_front[$hotel_key]['hotel']['discounts'] = [];
  859.             $content_front[$hotel_key]['hotel']['infos'] = [];
  860.             //$content_front[$hotel_key]['hotel']['minPrice'] = $hotel['minPrice'];
  861.             foreach ($hotel['sources'] as $hotel_source) {
  862.                 $sourceKey $hotel['hotel']['id']; // considering the full-hotel-id as the sourceKey (string containing the end-source-id)
  863.                 $content_front[$hotel_key]['sources'][$sourceKey]['sourceKey'] = $sourceKey;
  864.                 $hotelSourcePrice 0;
  865.                 $hotelSourceAvailable false;
  866.                 $hotelSourceFreeCancellation false;
  867.                 $tab_boards = [];
  868.                 foreach ($hotel_source['rooms'] as $index => $index_hrts) { // room_index
  869.                     // SORT hrts for current hotel-source by salePromoRate
  870.                     usort($index_hrts, function ($a$b) {
  871.                         return $a['salePromoRate'] <=> $b['salePromoRate']; // Ascending
  872.                     });
  873.                     foreach ($index_hrts as $hrt) {
  874.                         // freeCancellation FLAG to use in React Filter (i.e. is there a possibility to cancel without fees )
  875.                         if ($hrt['NRF']) {
  876.                             $hrt['freeCancellation'] = false;
  877.                         } else {
  878.                             if (is_null($hrt['deadline'])) {
  879.                                 $hrt['freeCancellation'] = true;
  880.                             } else {
  881.                                 $deadline = new DateTime($hrt['deadline']);
  882.                                 $difference date_diff($today$deadline); // Interval before deadline.
  883.                                 if ($difference->invert) { // deadline is exceeded
  884.                                     $hrt['freeCancellation'] = false;
  885.                                 } else {
  886.                                     $hrt['freeCancellation'] = true;
  887.                                 }
  888.                             }
  889.                         }
  890.                         $hotelSourceFreeCancellation $hotelSourceFreeCancellation || $hrt['freeCancellation'];
  891.                         // END freeCancellation FLAG
  892.                         // SHOW SaleResellingRates rather thant saleRate applied to the customer
  893.                         if ($showResellingRates) {
  894.                             $hrt['salePromoRate'] = $hrt['salePromoRate'] * (100.0 $customer->getResellerMargin()) / 100.0;
  895.                             $hrt['saleRate'] = $hrt['saleRate'] * (100.0 $customer->getResellerMargin()) / 100.0;
  896.                         }
  897.                         $content_front[$hotel_key]['sources'][$sourceKey]['hrts'][$hrt['boardName']][$index][] = $hrt;
  898.                         $tab_boards[$hrt['boardName']] = $hrt['boardXmlName'];
  899.                         // Increment the number of available-offers
  900.                         if ($hrt['available']) {
  901.                             $hotel_offer_count++;
  902.                             $hotelSourceAvailable true;
  903.                         }
  904.                     }
  905.                 } // END Room Indexes
  906.                 // HOTEL BADGES (Promos, Discounts, Infos)
  907.                 foreach ($hotel_source['promos'] as $item) {
  908.                     $content_front[$hotel_key]['hotel']['promos'][] = $item;
  909.                 }
  910.                 foreach ($hotel_source['discounts'] as $item) {
  911.                     $content_front[$hotel_key]['hotel']['discounts'][] = $item;
  912.                 }
  913.                 foreach ($hotel_source['infos'] as $item) {
  914.                     $content_front[$hotel_key]['hotel']['infos'][] = $item;
  915.                 }
  916.                 $bestOffers $this->hotelApiService->getBestPriceBoard($content_front[$hotel_key], $sourceKey);
  917.                 $content_front[$hotel_key]['sources'][$sourceKey]['bestPrice'] = $bestOffers['bestPrice_currentSourceKey'];
  918.                 $content_front[$hotel_key]['bestPrice'] = $bestOffers['bestPrice'];
  919.                 $content_front[$hotel_key]['bestBoard'] = $bestOffers['bestBoard'];
  920.                 $content_front[$hotel_key]['bestRooms'] = $this->helpers->formatArrayCounts($bestOffers['bestRooms']);
  921.                 $content_front[$hotel_key]['offersCount'] = $hotel_offer_count;
  922.                 foreach ($content_front[$hotel_key]['sources'][$sourceKey]['hrts'] as $board_key => $board_hrts) {
  923.                     if (count($board_hrts) < $nbRooms) {
  924.                         unset($content_front[$hotel_key]['sources'][$sourceKey]['hrts'][$board_key]);
  925.                     }
  926.                 }
  927.                 foreach ($tab_boards as $board_key => $board_value) {
  928.                     $content_front[$hotel_key]['sources'][$sourceKey]['boards'][] =
  929.                         [
  930.                             'key' => $board_value,
  931.                             'value' => $board_value,
  932.                         ];
  933.                 }
  934.                 // set general stock
  935.                 $content_front[$hotel_key]['sources'][$sourceKey]['generalStock'] = $hotel_source['generalStock'];
  936.                 // set search code
  937.                 $content_front[$hotel_key]['sources'][$sourceKey]['searchCode'] = $hotel_source['searchCode'];
  938.                 // set source id
  939.                 $content_front[$hotel_key]['sources'][$sourceKey]['xmlSourceId'] = $hotel_source['xmlSourceId'];
  940.                 // set xml source name
  941.                 $content_front[$hotel_key]['sources'][$sourceKey]['xmlSourceName'] = $hotel_source['xmlSourceName'];
  942.                 // set hotel Source available
  943.                 $content_front[$hotel_key]['sources'][$sourceKey]['available'] = $hotelSourceAvailable;
  944.                 // set hotel Source available
  945.                 $content_front[$hotel_key]['sources'][$sourceKey]['freeCancellation'] = $hotelSourceFreeCancellation;
  946.                 // set hotel Source available
  947.                 $content_front[$hotel_key]['sources'][$sourceKey]['associationRequired'] = $hotel_source['associationRequired'];
  948.             }
  949.         }
  950.         // array values hotels
  951.         $hotels array_values($content_front);
  952.         // array values sources
  953.         foreach ($hotels as &$hotel) {
  954.             usort($hotel['sources'], function ($a$b) {
  955.                 return $a['bestPrice'] <=> $b['bestPrice']; // Ascending
  956.             });
  957.             $hotel['sources'] = array_values($hotel['sources']);
  958.             foreach ($hotel['sources'] as &$source) {
  959.                 $source['hrts'] = array_values($source['hrts']);
  960.             }
  961.         }
  962.         return $hotels;
  963.     }
  964.     /**
  965.      * Déterminer si les tarifs revendeur doivent être affichés.
  966.      */
  967.     private function shouldShowResellingRates(mixed $customermixed $user): bool
  968.     {
  969.         if (!$customer instanceof CustomerMoral) {
  970.             return false;
  971.         }
  972.         if ($customer->getResellerMargin() <= 0) {
  973.             return false;
  974.         }
  975.         $roles $user->getRoles();
  976.         return in_array('ROLE_B2B_ADMIN'$rolestrue)
  977.             || in_array('ROLE_B2B_AGENT'$rolestrue);
  978.     }
  979. }