src/Controller/EmbedController.php line 286

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Instance;
  4. use App\Entity\InstanceView;
  5. use App\Repository\InstanceRepository;
  6. use App\Repository\InstanceViewRepository;
  7. use App\Repository\MediaItemRepository;
  8. use App\Service\PropertyListService;
  9. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  10. use Symfony\Component\HttpFoundation\Request;
  11. use Symfony\Component\HttpFoundation\Response;
  12. use Symfony\Component\Routing\Annotation\Route;
  13. use Symfony\Contracts\HttpClient\HttpClientInterface;
  14. class EmbedController extends AbstractController
  15. {
  16.     /** @var HttpClientInterface */
  17.     private $httpClient;
  18.     public function __construct(HttpClientInterface $httpClient)
  19.     {
  20.         $this->httpClient $httpClient;
  21.     }
  22.     /**
  23.      * @Route("/embed/{slug}", name="instance_embed")
  24.      */
  25.     public function embed(string $slugInstanceRepository $instanceRepositoryRequest $requestInstanceViewRepository $viewRepositoryMediaItemRepository $mediaItemRepositoryPropertyListService $propertyListService): Response
  26.     {
  27.         $instance $instanceRepository->findOneBy(['slug' => $slug]);
  28.         if (!$instance) {
  29.             throw $this->createNotFoundException('Instance not found.');
  30.         }
  31.         $defaultView $this->getDefaultViewForInstance($instance$viewRepository);
  32.         // Handle missing data gracefully - return empty arrays instead of blocking
  33.         $propertyData = [];
  34.         try {
  35.             $propertyData $this->fetch3DPropertyDetails($instance$defaultView);
  36.         } catch (\Exception $e) {
  37.             // If fetch fails, continue with empty data
  38.             $propertyData = [];
  39.         }
  40.         // Basic environment / device information
  41.         $userAgent $request->headers->get('User-Agent''');
  42.         $isMobile = (bool) preg_match('/Mobile|Android|iP(hone|od|ad)|IEMobile|BlackBerry|Opera Mini/i'$userAgent);
  43.         // Data derived from the 3D property payload
  44.         $properties $propertyData['properties'] ?? [];
  45.         $rooms array_values($propertyData['chList'] ?? []);
  46.         sort($rooms);
  47.         $biensList $propertyListService->build($properties$request->query->all());
  48.         $surfaceMin $propertyData['surfaceMin'] ?? null;
  49.         $surfaceMax $propertyData['surfaceMax'] ?? null;
  50.         $priceMin $propertyData['priceMin'] ?? null;
  51.         $priceMax $propertyData['priceMax'] ?? null;
  52.         // Details are JSON-encoded in the Twig templates for use in JS
  53.         $details = [
  54.             'properties' => $properties,
  55.             'mappedProperties' => $propertyData['mappedProperties'] ?? null,
  56.         ];
  57.         $viewMode $instance->getViewMode();
  58.         // Determine the default view for this instance and build the 3D image dataset
  59.         $data3d $this->buildData3DFromMedia($instance$defaultView$mediaItemRepository$request);
  60.         $presentationItems $mediaItemRepository->findBy(
  61.             ['instance' => $instance'type' => 'presentation'],
  62.             ['row' => 'ASC''id' => 'ASC']
  63.         );
  64.         // If the default view has an uploaded GLB model, expose its relative path for the front-end
  65.         // Normalize any Windows-style backslashes to forward slashes so it can be safely used as a web URL.
  66.         $modelPath null;
  67.         if ($defaultView && $defaultView->getGlbPath()) {
  68.             $storedGlbPath $defaultView->getGlbPath();
  69.             $modelPath str_replace('\\''/'$storedGlbPath);
  70.         }
  71.         // Generic configuration placeholders for the front-end scripts.
  72.         $baseUrl rtrim((string) $instance->getBaseUrl(), '/');
  73.         $view $defaultView ? (string) $defaultView->getId() : null;
  74.         $viewDataUrl $this->generateUrl('instance_embed_view_data', ['slug' => $slug]);
  75.         $baseWeb '';
  76.         $baseWebLanguage '';
  77.         $ajaxDetailUrl $this->generateUrl('instance_embed_property_detail', ['slug' => $slug]);
  78.         $socketUrl '';
  79.         $version '';
  80.         $favorisUrl '';
  81.         $comparateurUrl '';
  82.         $groupeName $instance->getParentName();
  83.         if ($defaultView && $defaultView->getMeshGroupName()) {
  84.             $groupeName $defaultView->getMeshGroupName();
  85.         }
  86.         if ($viewMode === 'floor') {
  87.             // Floor-specific helpers/placeholders used by the Twig/JS layer
  88.             $etageMin null;
  89.             $etageMax null;
  90.             $floorsRooms = [];
  91.             $categories = [];
  92.             $showPoints '';
  93.             $model $modelPath ?? null;
  94.             $icon360 '';
  95.             $views $viewRepository->findBy(['instance' => $instance], ['sortOrder' => 'ASC''id' => 'ASC']);
  96.             return $this->render('embed/floor.html.twig', [
  97.                 'instance' => $instance,
  98.                 'views' => $views,
  99.                 'propertyData' => $propertyData,
  100.                 'properties' => $properties,
  101.                 'biensList' => $biensList,
  102.                 'rooms' => $rooms,
  103.                 'presentationItems' => $presentationItems,
  104.                 'surfaceMin' => $surfaceMin,
  105.                 'surfaceMax' => $surfaceMax,
  106.                 'priceMin' => $priceMin,
  107.                 'priceMax' => $priceMax,
  108.                 'etageMin' => $etageMin,
  109.                 'etageMax' => $etageMax,
  110.                 'categories' => $categories,
  111.                 'floorsRooms' => $floorsRooms,
  112.                 'is_mobile' => $isMobile,
  113.                 'data3d' => $data3d,
  114.                 'details' => $details,
  115.                 'view' => $view,
  116.                 'viewDataUrl' => $viewDataUrl,
  117.                 'baseWeb' => $baseWeb,
  118.                 'baseUrl' => $baseUrl,
  119.                 'showPoints' => $showPoints,
  120.                 'baseWebLanguage' => $baseWebLanguage,
  121.                 'ajaxDetailUrl' => $ajaxDetailUrl,
  122.                 'socketUrl' => $socketUrl,
  123.                 'model' => $model,
  124.                 'icon360' => $icon360,
  125.                 'groupeName' => $groupeName,
  126.             ]);
  127.         }
  128.         return $this->render('embed/normal.html.twig', [
  129.             'instance' => $instance,
  130.             'propertyData' => $propertyData,
  131.             'properties' => $properties,
  132.             'biensList' => $biensList,
  133.             'rooms' => $rooms,
  134.             'presentationItems' => $presentationItems,
  135.             'is_mobile' => $isMobile,
  136.             'data3d' => $data3d,
  137.             'details' => $details,
  138.             'view' => $view,
  139.             'viewDataUrl' => $viewDataUrl,
  140.             'baseWeb' => $baseWeb,
  141.             'baseUrl' => $baseUrl,
  142.             'baseWebLanguage' => $baseWebLanguage,
  143.             'ajaxDetailUrl' => $ajaxDetailUrl,
  144.             'socketUrl' => $socketUrl,
  145.             'version' => $version,
  146.             'favorisUrl' => $favorisUrl,
  147.             'comparateurUrl' => $comparateurUrl,
  148.             'modelPath' => $modelPath,
  149.             'groupeName' => $groupeName,
  150.         ]);
  151.     }
  152.     /**
  153.      * Returns detailed information for a single property and renders the modal HTML (AJAX).
  154.      *
  155.      * This proxies the remote /api2/property-details endpoint and wraps the result
  156.      * into a small Twig-rendered HTML fragment, to be injected into #property-detail-modal.
  157.      *
  158.      * @Route("/embed/{slug}/property-detail", name="instance_embed_property_detail", methods={"GET"})
  159.      */
  160.     public function propertyDetail(
  161.         string $slug,
  162.         Request $request,
  163.         InstanceRepository $instanceRepository
  164.     ): Response {
  165.         $instance $instanceRepository->findOneBy(['slug' => $slug]);
  166.         if (!$instance) {
  167.             throw $this->createNotFoundException('Instance not found.');
  168.         }
  169.         $id = (int) $request->query->get('id'0);
  170.         if ($id <= 0) {
  171.             return $this->json([
  172.                 'status' => false,
  173.                 'error' => 'Id not found',
  174.             ]);
  175.         }
  176.         $property $this->fetch3DPropertyDetail($instance$id);
  177.         if (!$property) {
  178.             return $this->json([
  179.                 'status' => false,
  180.                 'error' => 'Detail not found',
  181.             ]);
  182.         }
  183.         // Build a small, safe view model for Twig (avoid repeating array checks there)
  184.         $propertyTypeLabel null;
  185.         $propertyType $property['property_type']['name'] ?? null;
  186.         $propertyTypeId $property['property_type']['id'] ?? null;
  187.         if ($propertyTypeId === 12) {
  188.             $propertyTypeLabel 'Bureau';
  189.         } else {
  190.             if (!empty($property['property_sub_type'][0]['name'])) {
  191.                 $propertyTypeLabel $property['property_sub_type'][0]['name'];
  192.             } else {
  193.                 $propertyTypeLabel $propertyType;
  194.             }
  195.         }
  196.         $view = [
  197.             'name' => $property['name'] ?? '',
  198.             'propertyTypeLabel' => $propertyTypeLabel,
  199.             'statusLabel' => $property['statut']['name'] ?? null,
  200.             'surface' => $property['surface_terrain'] ?? null,
  201.             'bedrooms' => $property['chambres'] ?? null,
  202.             'terraceSurface' => $property['surface_terrasse'] ?? null,
  203.         ];
  204.         $html $this->renderView('embed/_property_detail_modal.html.twig', [
  205.             'instance' => $instance,
  206.             'property' => $property,
  207.             'view' => $view,
  208.         ]);
  209.         return $this->json([
  210.             'status' => true,
  211.             'html' => $html,
  212.             'plan3d' => null,
  213.             'floorsData' => null,
  214.         ]);
  215.     }
  216.     /**
  217.      * @Route("/embed/{slug}/biens", name="instance_embed_biens_partial", methods={"GET"})
  218.      */
  219.     public function biensPartial(string $slugRequest $requestInstanceRepository $instanceRepositoryInstanceViewRepository $viewRepositoryPropertyListService $propertyListService): Response
  220.     {
  221.         $instance $instanceRepository->findOneBy(['slug' => $slug]);
  222.         if (!$instance) {
  223.             throw $this->createNotFoundException('Instance not found.');
  224.         }
  225.         $defaultView $this->getDefaultViewForInstance($instance$viewRepository);
  226.         $propertyData = [];
  227.         try {
  228.             $propertyData $this->fetch3DPropertyDetails($instance$defaultView);
  229.         } catch (\Exception $e) {
  230.             $propertyData = [];
  231.         }
  232.         $properties $propertyData['properties'] ?? [];
  233.         $biensList $propertyListService->build($properties$request->query->all());
  234.         return $this->render('shared/_biens_list.html.twig', [
  235.             'filters' => $biensList['filters'] ?? [],
  236.             'statsByPieces' => $biensList['statsByPieces'] ?? [],
  237.             'groups' => $biensList['groups'] ?? [],
  238.             'pagination' => $biensList['pagination'] ?? [],
  239.             'basePath' => $this->generateUrl('instance_embed', ['slug' => $instance->getSlug()]),
  240.             'ajaxUrl' => $this->generateUrl('instance_embed_biens_partial', ['slug' => $instance->getSlug()]),
  241.             'containerId' => 'biens-list-container-embed-' $instance->getSlug(),
  242.             'wrapperClass' => 'p-0',
  243.             'theme' => 'light',
  244.             'detailUrl' => $this->generateUrl('instance_embed_property_detail', ['slug' => $instance->getSlug()]),
  245.         ]);
  246.     }
  247.     /**
  248.      * Returns 3D data for a specific view (used by AJAX in the embeds).
  249.      *
  250.      * @Route("/embed/{slug}/view-data", name="instance_embed_view_data", methods={"GET"})
  251.      */
  252.     public function viewData(
  253.         string $slug,
  254.         Request $request,
  255.         InstanceRepository $instanceRepository,
  256.         InstanceViewRepository $viewRepository,
  257.         MediaItemRepository $mediaItemRepository
  258.     ): Response {
  259.         $instance $instanceRepository->findOneBy(['slug' => $slug]);
  260.         if (!$instance) {
  261.             throw $this->createNotFoundException('Instance not found.');
  262.         }
  263.         $viewId $request->query->get('view');
  264.         $view null;
  265.         if ($viewId !== null && $viewId !== '') {
  266.             $view $viewRepository->findOneBy([
  267.                 'id' => (int) $viewId,
  268.                 'instance' => $instance,
  269.             ]);
  270.         }
  271.         if (!$view) {
  272.             $view $this->getDefaultViewForInstance($instance$viewRepository);
  273.         }
  274.         if (!$view) {
  275.             return $this->json([
  276.                 'model' => null,
  277.                 'data3d' => [],
  278.                 'firstImageOriginalWidth' => null,
  279.                 'firstImageOriginalHeight' => null,
  280.                 'firstRowImagesCount' => 0,
  281.             ]);
  282.         }
  283.         $data3d $this->buildData3DFromMedia($instance$view$mediaItemRepository$request);
  284.         // Build model URL for this view
  285.         $modelRel null;
  286.         if ($view->getGlbPath()) {
  287.             $storedGlbPath $view->getGlbPath();
  288.             $modelRel str_replace('\\''/'$storedGlbPath);
  289.         }
  290.         $basePath $request->getBasePath();
  291.         $publicPrefix $basePath === '' '/' rtrim($basePath'/') . '/';
  292.         $model null;
  293.         if ($modelRel) {
  294.             $model $publicPrefix ltrim($modelRel'/\\');
  295.         }
  296.         // Derive first image metadata from the returned data3d
  297.         $firstImageOriginalWidth null;
  298.         $firstImageOriginalHeight null;
  299.         $firstRowImagesCount 0;
  300.         if (\is_array($data3d) && isset($data3d[0]) && \is_array($data3d[0]) && isset($data3d[0][0]) && \is_array($data3d[0][0])) {
  301.             $firstRow $data3d[0];
  302.             $firstRowImagesCount = \count($firstRow);
  303.             $first $firstRow[0];
  304.             if (\array_key_exists('width'$first)) {
  305.                 $firstImageOriginalWidth $first['width'];
  306.             }
  307.             if (\array_key_exists('height'$first)) {
  308.                 $firstImageOriginalHeight $first['height'];
  309.             }
  310.         }
  311.         $propertyDetails = [];
  312.         try {
  313.             $details $this->fetch3DPropertyDetails($instance$view);
  314.             $propertyDetails $details['mappedProperties'] ?? [];
  315.         } catch (\Exception $e) {
  316.             $propertyDetails = [];
  317.         }
  318.         $debugPayload null;
  319.         if ($request->query->getBoolean('debug')) {
  320.             $meshMap $view->getMeshPropertyMap();
  321.             $debugPayload = [
  322.                 'viewId' => $view->getId(),
  323.                 'hasMeshMap' => \is_array($meshMap) && $meshMap !== [],
  324.                 'meshMapCount' => \is_array($meshMap) ? \count($meshMap) : 0,
  325.                 'meshMapSample' => \is_array($meshMap) ? array_slice($meshMap010true) : null,
  326.                 'resolvedPropertyDetailsCount' => \is_array($propertyDetails) ? \count($propertyDetails) : 0,
  327.                 'resolvedPropertyDetailsSampleKeys' => \is_array($propertyDetails) ? array_slice(array_keys($propertyDetails), 010) : null,
  328.             ];
  329.         }
  330.         return $this->json([
  331.             'model' => $model,
  332.             'data3d' => $data3d,
  333.             'firstImageOriginalWidth' => $firstImageOriginalWidth,
  334.             'firstImageOriginalHeight' => $firstImageOriginalHeight,
  335.             'firstRowImagesCount' => $firstRowImagesCount,
  336.             'groupeName' => $view->getMeshGroupName() ?: $instance->getParentName(),
  337.             'propertyDetails' => $propertyDetails,
  338.             'debug' => $debugPayload,
  339.         ]);
  340.     }
  341.     private function getDefaultViewForInstance(Instance $instanceInstanceViewRepository $viewRepository): ?InstanceView
  342.     {
  343.         $views $viewRepository->findBy(['instance' => $instance], ['id' => 'ASC']);
  344.         if (!$views) {
  345.             return null;
  346.         }
  347.         foreach ($views as $view) {
  348.             if ($view->isDefault()) {
  349.                 return $view;
  350.             }
  351.         }
  352.         return $views[0];
  353.     }
  354.     private function buildData3DFromMedia(Instance $instance, ?InstanceView $viewMediaItemRepository $mediaItemRepositoryRequest $request): array
  355.     {
  356.         if (!$view) {
  357.             return [];
  358.         }
  359.         $items $mediaItemRepository->findBy(
  360.             ['instance' => $instance'view' => $view'type' => 'image'],
  361.             ['row' => 'ASC''id' => 'ASC']
  362.         );
  363.         if (!$items) {
  364.             return [];
  365.         }
  366.         $grouped = [];
  367.         $basePath $request->getBasePath();
  368.         $publicPrefix $basePath === '' '/' rtrim($basePath'/') . '/';
  369.         $toUrl = static function (?string $relative) use ($publicPrefix): ?string {
  370.             if (!$relative) {
  371.                 return null;
  372.             }
  373.             $relative ltrim($relative'/\\');
  374.             return $publicPrefix str_replace('\\''/'$relative);
  375.         };
  376.         foreach ($items as $item) {
  377.             $variants $item->getVariants();
  378.             if (!\is_array($variants) || $variants === []) {
  379.                 continue;
  380.             }
  381.             $low915 $variants[915] ?? null;
  382.             $low1366 $variants[1366] ?? null;
  383.             $good4000 $variants[4000] ?? null;
  384.             $webp915 = \is_array($low915) ? ($low915['webp'] ?? null) : null;
  385.             $jpg915 = \is_array($low915) ? ($low915['jpg'] ?? null) : null;
  386.             $webp1366 = \is_array($low1366) ? ($low1366['webp'] ?? null) : null;
  387.             $jpg1366 = \is_array($low1366) ? ($low1366['jpg'] ?? null) : null;
  388.             $webp4000 = \is_array($good4000) ? ($good4000['webp'] ?? null) : null;
  389.             $jpg4000 = \is_array($good4000) ? ($good4000['jpg'] ?? null) : null;
  390.             $imageWebp $webp915 ?? $webp1366 ?? $webp4000;
  391.             $imageJpg $jpg915 ?? $jpg1366 ?? $jpg4000;
  392.             if (!$imageWebp && !$imageJpg) {
  393.                 continue;
  394.             }
  395.             $row $item->getRow();
  396.             if ($row <= 0) {
  397.                 $row 1;
  398.             }
  399.             $rowIndex $row 1;
  400.             $width null;
  401.             $height null;
  402.             $sizeSourceRel $webp4000 ?? $jpg4000 ?? $webp1366 ?? $jpg1366 ?? $imageWebp ?? $imageJpg;
  403.             if ($sizeSourceRel) {
  404.                 $absolute $this->getParameter('kernel.project_dir')
  405.                     . DIRECTORY_SEPARATOR 'public' DIRECTORY_SEPARATOR
  406.                     ltrim($sizeSourceRel'/\\');
  407.                 if (is_file($absolute)) {
  408.                     $sizeInfo = @getimagesize($absolute);
  409.                     if (\is_array($sizeInfo)) {
  410.                         $width $sizeInfo[0] ?? null;
  411.                         $height $sizeInfo[1] ?? null;
  412.                     }
  413.                 }
  414.             }
  415.             $entry = [
  416.                 'image' => $toUrl($imageWebp),
  417.                 'image1366' => $toUrl($webp1366),
  418.                 'image915' => $toUrl($webp915),
  419.                 'bigImage' => $toUrl($webp4000),
  420.                 'imageJpeg' => $toUrl($imageJpg),
  421.                 'imageJpeg1366' => $toUrl($jpg1366),
  422.                 'imageJpeg915' => $toUrl($jpg915),
  423.                 'bigImageJpeg' => $toUrl($jpg4000),
  424.                 'floor' => (string) ($view->getName() ?? ''),
  425.                 'width' => $width,
  426.                 'height' => $height,
  427.                 'type' => 18,
  428.                 'attr' => ($width && $height) ? sprintf('width="%d" height="%d"'$width$height) : '',
  429.                 'default' => false,
  430.             ];
  431.             $grouped[$rowIndex][] = $entry;
  432.         }
  433.         if ($grouped === []) {
  434.             return [];
  435.         }
  436.         ksort($grouped);
  437.         $data3d = [];
  438.         foreach ($grouped as $rowEntries) {
  439.             $data3d[] = array_values($rowEntries);
  440.         }
  441.         if (isset($data3d[0][0])) {
  442.             $data3d[0][0]['default'] = true;
  443.         }
  444.         return $data3d;
  445.     }
  446.     private function fetch3DPropertyDetail(Instance $instanceint $id): ?array
  447.     {
  448.         $baseUrl rtrim((string) $instance->getBaseUrl(), '/');
  449.         $token $instance->getToken();
  450.         // Return null if essential data is missing
  451.         if ($baseUrl === '' || !$token) {
  452.             return null;
  453.         }
  454.         $url sprintf('%s/api2/property-details'$baseUrl);
  455.         $response $this->httpClient->request('GET'$url, [
  456.             'query' => ['id' => $id],
  457.             'headers' => [
  458.                 'cache-control' => 'no-cache',
  459.                 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
  460.                 'x-auth-token' => $token,
  461.             ],
  462.             'max_redirects' => 10,
  463.             'timeout' => 30,
  464.         ]);
  465.         if ($response->getStatusCode() !== 200) {
  466.             return null;
  467.         }
  468.         $raw $response->getContent(false);
  469.         $decoded json_decode(utf8_decode($raw), true);
  470.         if (!\is_array($decoded) || $decoded === [] || isset($decoded['msg'])) {
  471.             return null;
  472.         }
  473.         return $decoded;
  474.     }
  475.     private function fetch3DPropertyDetails(Instance $instance, ?InstanceView $view null): array
  476.     {
  477.         $baseUrl rtrim((string) $instance->getBaseUrl(), '/');
  478.         $projectId $instance->getProjectId();
  479.         $token $instance->getToken();
  480.         // Return empty array if essential data is missing
  481.         if ($baseUrl === '' || !$projectId || !$token) {
  482.             return [
  483.                 'properties' => [],
  484.                 'surfaceMin' => null,
  485.                 'surfaceMax' => null,
  486.                 'priceMin' => null,
  487.                 'priceMax' => null,
  488.                 'chList' => [],
  489.                 'statusList' => [],
  490.                 'orientations' => [],
  491.             ];
  492.         }
  493.         $url sprintf('%s/api2/%s/draw-property-list-by-project'$baseUrl$projectId);
  494.         $response $this->httpClient->request('GET'$url, [
  495.             'query' => ['hasCoords' => false],
  496.             'headers' => [
  497.                 'cache-control' => 'no-cache',
  498.                 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
  499.                 'x-auth-token' => $token,
  500.             ],
  501.             'max_redirects' => 10,
  502.             'timeout' => 30,
  503.         ]);
  504.         if ($response->getStatusCode() !== 200) {
  505.             return [
  506.                 'properties' => [],
  507.                 'surfaceMin' => null,
  508.                 'surfaceMax' => null,
  509.                 'priceMin' => null,
  510.                 'priceMax' => null,
  511.                 'chList' => [],
  512.                 'statusList' => [],
  513.                 'orientations' => [],
  514.             ];
  515.         }
  516.         $raw $response->getContent(false);
  517.         $decoded json_decode(utf8_decode($raw), true);
  518.         if (!\is_array($decoded)) {
  519.             return [
  520.                 'properties' => [],
  521.                 'surfaceMin' => null,
  522.                 'surfaceMax' => null,
  523.                 'priceMin' => null,
  524.                 'priceMax' => null,
  525.                 'chList' => [],
  526.                 'statusList' => [],
  527.                 'orientations' => [],
  528.             ];
  529.         }
  530.         $surfaceMin null;
  531.         $surfaceMax null;
  532.         $priceMin null;
  533.         $priceMax null;
  534.         $statusList = [];
  535.         $chList = [];
  536.         $newProperties = [];
  537.         $orientations = [];
  538.         foreach ($decoded as $property) {
  539.             if (!isset($property['info']['title'])) {
  540.                 continue;
  541.             }
  542.             $info $property['info'];
  543.             $name $info['title'];
  544.             $status $info['statut'] ?? null;
  545.             $price = isset($info['price']) ? (float) $info['price'] : 0.0;
  546.             $surface = isset($info['surface_terrain']) ? (float) $info['surface_terrain'] : 0.0;
  547.             $pieces $info['pieces'] ?? null;
  548.             $orientation $info['orientation'] ?? null;
  549.             if ($surfaceMin === null || $surface $surfaceMin) {
  550.                 $surfaceMin $surface;
  551.             }
  552.             if ($surfaceMax === null || $surface $surfaceMax) {
  553.                 $surfaceMax $surface;
  554.             }
  555.             if ($priceMin === null || $price $priceMin) {
  556.                 $priceMin $price;
  557.             }
  558.             if ($priceMax === null || $price $priceMax) {
  559.                 $priceMax $price;
  560.             }
  561.             if ($pieces !== null) {
  562.                 $chList[$pieces] = $pieces;
  563.             }
  564.             $statusTag $status !== null ? (string) $status '';
  565.             $property['info']['statutTag'] = $statusTag;
  566.             if ($status !== null) {
  567.                 $statusList[$status] = $statusTag;
  568.             }
  569.             $orientationLabel null;
  570.             if ($orientation === 1) {
  571.                 $orientationLabel 'Nord';
  572.             } elseif ($orientation === 2) {
  573.                 $orientationLabel 'Ouest';
  574.             } elseif ($orientation === 3) {
  575.                 $orientationLabel 'Est';
  576.             } elseif ($orientation === 4) {
  577.                 $orientationLabel 'Sud';
  578.             }
  579.             if ($orientationLabel !== null && $orientation !== null) {
  580.                 $orientations[$orientation] = [
  581.                     'id' => $orientation,
  582.                     'label' => $orientationLabel,
  583.                 ];
  584.             }
  585.             $slugTitle $name;
  586.             $slugTitle str_replace('°'''$slugTitle);
  587.             $slugTitle strtolower($slugTitle);
  588.             $slugTitle str_replace('boutique -''boutique-'$slugTitle);
  589.             if (preg_match('/^p\.b /'$slugTitle)) {
  590.                 $slugTitle str_replace('p.b ''pb-'$slugTitle);
  591.             }
  592.             if ($status == 2) {
  593.                 $color '#99BEB2';
  594.                 if($pieces == 2){
  595.                     $color '#397B65';
  596.                 } elseif($pieces == 3){
  597.                     $color '#395D7B';
  598.                 } elseif($pieces == 4){
  599.                     $color '#1b7a0c';
  600.                 } elseif($pieces == 5){
  601.                     $color '#4C2D77';
  602.                 }
  603.                 if (isset($property['info']['property_type_id']) && intval($property['info']['property_type_id']) == 12) {
  604.                     if(in_array('25'$property['info']['property_sub_type_idx'])){
  605.                         $color '#107252';
  606.                     } else {
  607.                         $color '#99BEB2';
  608.                     }
  609.                 }
  610.             } else {
  611.                 $color '#B71818';
  612.             }
  613.             $newProperties[$slugTitle] = [
  614.                 'color'=>$color,
  615.                 'infos'=>$property
  616.             ];
  617.         }
  618.         $allProperties $newProperties;
  619.         $normalizeSlugTitle = static function (?string $value): string {
  620.             $value trim((string) ($value ?? ''));
  621.             if ($value === '') {
  622.                 return '';
  623.             }
  624.             $value str_replace('°'''$value);
  625.             $value strtolower($value);
  626.             $value str_replace('boutique -''boutique-'$value);
  627.             if (preg_match('/^p\.b /'$value)) {
  628.                 $value str_replace('p.b ''pb-'$value);
  629.             }
  630.             return $value;
  631.         };
  632.         $mappedProperties null;
  633.         if ($view && $view->getMeshPropertyMap()) {
  634.             $map $view->getMeshPropertyMap();
  635.             if (\is_array($map) && $map !== []) {
  636.                 $mapped = [];
  637.                 foreach ($map as $meshName => $slugTitle) {
  638.                     $meshName trim((string) $meshName);
  639.                     $rawKey trim((string) ($slugTitle ?? ''));
  640.                     $normalizedKey $normalizeSlugTitle($rawKey);
  641.                     if ($meshName === '' || ($rawKey === '' && $normalizedKey === '')) {
  642.                         continue;
  643.                     }
  644.                     $entry null;
  645.                     if ($rawKey !== '' && isset($allProperties[$rawKey])) {
  646.                         $entry $allProperties[$rawKey];
  647.                     } elseif ($normalizedKey !== '' && isset($allProperties[$normalizedKey])) {
  648.                         $entry $allProperties[$normalizedKey];
  649.                     }
  650.                     if ($entry === null) {
  651.                         continue;
  652.                     }
  653.                     $mapped[$meshName] = $entry;
  654.                 }
  655.                 $mappedProperties $mapped;
  656.             }
  657.         }
  658.         ksort($chList);
  659.         ksort($statusList);
  660.         return [
  661.             'properties' => $allProperties,
  662.             'mappedProperties' => $mappedProperties,
  663.             'surfaceMin' => $surfaceMin !== null ? (int) floor($surfaceMin) : null,
  664.             'surfaceMax' => $surfaceMax !== null ? (int) floor($surfaceMax) : null,
  665.             'priceMin' => $priceMin !== null ? (int) floor($priceMin) : null,
  666.             'priceMax' => $priceMax !== null ? (int) floor($priceMax) : null,
  667.             'chList' => $chList,
  668.             'statusList' => $statusList,
  669.             'orientations' => $orientations,
  670.         ];
  671.     }
  672. }