src/Controller/DeviceApiController.php line 98

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Pimcore\Controller\FrontendController;
  4. use Symfony\Component\HttpFoundation\JsonResponse;
  5. use Symfony\Component\Routing\Annotation\Route;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Contracts\Translation\TranslatorInterface;
  8. use DateTime;
  9. use App\Model\WeatherStationModel;
  10. use Symfony\Component\Security\Core\User\UserInterface;
  11. use App\Service\NCMWeatherAPIService;
  12. use App\Service\BassicAuthService;
  13. use App\Service\MeteomaticsWeatherService;
  14. use App\Service\UserPermission;
  15. use Symfony\Component\HttpFoundation\Response;
  16. use App\Model\EwsNotificationModel;
  17. use App\Model\EwsPortalModel;
  18. use App\Model\DeviceModel;
  19. use App\Model\ReportLogModel;
  20. use Pimcore\Log\ApplicationLogger;
  21. use Symfony\Component\Templating\EngineInterface;
  22. use App\Service\EmailService;
  23. use App\Service\MeteomaticApiService;
  24. use App\Service\PublicUserPermissionService;
  25. use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
  26. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  27. use Knp\Component\Pager\PaginatorInterface;
  28. use App\Model\WeatherForecastCityModel;
  29. use App\Service\CustomNotificationService;
  30. use App\Service\RedisCache;
  31. /**
  32.  * User controller is responsible for handling various actions related to user management.
  33.  *
  34.  * @Route("/api/ncm/device")
  35.  */
  36. class DeviceApiController extends FrontendController
  37. {
  38.     private $weatherStationModel;
  39.     private $ewsNotificationModel;
  40.     private $deviceModel;
  41.     private $reportLogModel;
  42.     var $emailService null;
  43.     public function __construct(
  44.         protected TranslatorInterface $translator,
  45.         private ApplicationLogger $logger,
  46.         private EngineInterface $templating,
  47.         private NCMWeatherAPIService $ncmWeatherApiService,
  48.         private MeteomaticApiService $meteomaticApiService,
  49.         private MeteomaticsWeatherService $meteomaticsWeatherService,
  50.         private BassicAuthService $bassicAuthService,
  51.         private UserPermission $userPermission,
  52.         private TokenStorageInterface $tokenStorageInterface,
  53.         private JWTTokenManagerInterface $jwtManager,
  54.         private PublicUserPermissionService $publicUserPermissionService,
  55.         private CustomNotificationService $customNotificationService,
  56.         private RedisCache $redisCache,
  57.     ) {
  58.         header('Content-Type: application/json; charset=UTF-8');
  59.         // header("Access-Control-Allow-Origin: *");
  60.         $this->ewsNotificationModel = new EwsNotificationModel();
  61.         $this->weatherStationModel = new WeatherStationModel();
  62.         $this->deviceModel = new DeviceModel();
  63.         $this->reportLogModel = new ReportLogModel();
  64.         $this->templating $templating;
  65.         $this->emailService = new EmailService();
  66.     }
  67.     /**
  68.      * @Route("/regions", methods={"POST"})
  69.      */
  70.     public function regions(Request $request): JsonResponse
  71.     {
  72.         try {
  73.             $params json_decode($request->getContent(), true);
  74.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  75.             // check user credentials and expiry
  76.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  77.             if ($response['success'] !== true) {
  78.                 return $this->json($response);
  79.             }
  80.             $search = (isset($params['search']) && !empty($params['search'])) ? $params['search'] : null;
  81.             $result $this->ncmWeatherApiService->getRegions($search, isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  82.             return $this->json($result);
  83.         } catch (\Exception $ex) {
  84.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  85.         }
  86.     }
  87.     /**
  88.      * @Route("/route-query", methods={"POST"})
  89.      */
  90.     public function routeQuery(Request $request): JsonResponse
  91.     {
  92.         $params json_decode($request->getContent(), true);
  93.         $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  94.         // check basic Authorization
  95.         $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  96.         if ($response['success'] !== true) {
  97.             return $this->json($response);
  98.         }
  99.         // validate required parms
  100.         if (
  101.             !isset($params['date']) || !isset($params['parameters']) || !isset($params['location'])
  102.         ) {
  103.             throw new \Exception('Missing required parameters');
  104.         }
  105.         $locations $params['location'];
  106.         $parameters $params['parameters'];
  107.         // Join the locations and parameters strings with ',' for the URL
  108.         $parameterString $parameters;
  109.         $locationString implode('+'$locations);
  110.         // Create a DateTime object
  111.         $date = new DateTime($params['date']);
  112.         $dateStrings = [];
  113.         // Format the DateTime object as a string and add it to the array
  114.         for ($i 0$i count($locations); $i++) {
  115.             $dateStrings[] = $date->format(DateTime::ISO8601);
  116.         }
  117.         // Join the date strings with ',' for the URL
  118.         $dateString implode(','$dateStrings);
  119.         $response $this->meteomaticApiService->getRouteQuery(
  120.             $dateString,
  121.             $parameterString,
  122.             $locationString
  123.         );
  124.         // For demonstration purposes, we will just return a JSON response
  125.         return $this->json($response);
  126.     }
  127.     /**
  128.      * @Route("/municipality", methods={"POST"})
  129.      */
  130.     public function getMunicipalityAction(Request $request): JsonResponse
  131.     {
  132.         try {
  133.             $params json_decode($request->getContent(), true);
  134.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  135.             // check user credentials and expiry
  136.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  137.             if ($response['success'] !== true) {
  138.                 return $this->json($response);
  139.             }
  140.             if (!isset($params['governorate_id'])) {
  141.                 throw new \Exception('Missing required governorate id');
  142.             }
  143.             $governorateId $params['governorate_id'];
  144.             $result $this->ewsNotificationModel->getMunicipality($governorateId$params['lang']);
  145.             return $this->json($result);
  146.         } catch (\Exception $ex) {
  147.             $this->logger->error($ex->getMessage());
  148.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  149.         }
  150.     }
  151.     /**
  152.      * @Route("/governorates", methods={"POST"})
  153.      */
  154.     public function getGovernorateByRegionAction(Request $request): JsonResponse
  155.     {
  156.         try {
  157.             $params json_decode($request->getContent(), true) ?: [];
  158.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  159.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  160.             if ($response['success'] !== true) {
  161.                 return $this->json($response);
  162.             }
  163.             $cacheKey self::deviceGovernoratesCacheKey($params);
  164.             $ttl defined('DEVICE_GOVERNORATES_CACHE_TTL') ? DEVICE_GOVERNORATES_CACHE_TTL 86400;
  165.             try {
  166.                 $cached $this->redisCache->get($cacheKey);
  167.                 if (is_array($cached) && isset($cached['success']) && $cached['success'] === true && array_key_exists('data'$cached)) {
  168.                     return $this->json($cached);
  169.                 }
  170.             } catch (\Throwable $e) {
  171.             }
  172.             $result $this->ewsNotificationModel->getGovernoratesByRegion($params);
  173.             try {
  174.                 $this->redisCache->set($cacheKey$result$ttl);
  175.             } catch (\Throwable $e) {
  176.             }
  177.             return $this->json($result);
  178.         } catch (\Exception $ex) {
  179.             $this->logger->error($ex->getMessage());
  180.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  181.         }
  182.     }
  183.     /**
  184.      * Redis key: region filter(s) + lang (sort order). No region / region_ids â†’ "__all__".
  185.      */
  186.     private static function deviceGovernoratesCacheKey(array $params): string
  187.     {
  188.         $lang = isset($params['lang']) ? strtolower((string) $params['lang']) : 'en';
  189.         $segments = [];
  190.         if (isset($params['region_id']) && $params['region_id'] !== '' && $params['region_id'] !== null) {
  191.             $segments[] = 'rid=' . (string) $params['region_id'];
  192.         }
  193.         if (isset($params['region_ids']) && !empty($params['region_ids'])) {
  194.             $ids is_array($params['region_ids']) ? $params['region_ids'] : [$params['region_ids']];
  195.             $ids array_map('strval'$ids);
  196.             sort($idsSORT_STRING);
  197.             $segments[] = 'rids=' implode(','$ids);
  198.         }
  199.         $regionKey $segments !== [] ? implode('&'$segments) : '__all__';
  200.         return 'device_governorates:v1:' md5($regionKey '|' $lang);
  201.     }
  202.     /**
  203.      * @Route("/tidal-amplitude", name="tidal-amplitude", methods={"POST"})
  204.      */
  205.     public function tidalAmplitudeData(Request $request): JsonResponse
  206.     {
  207.         try {
  208.             $params json_decode($request->getContent(), true);
  209.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  210.             // check user credentials and expiry
  211.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  212.             if ($response['success'] !== true) {
  213.                 return $this->json($response);
  214.             }
  215.             $requiredParameters = ['hour''parameter''date''coordinates''model''format'];
  216.             foreach ($requiredParameters as $param) {
  217.                 if (!isset($params[$param])) {
  218.                     $missingParams[] = $param;
  219.                 }
  220.             }
  221.             if (!empty($missingParams)) {
  222.                 // Throw an exception with a message that includes the missing parameters
  223.                 $parameterList implode(", "$missingParams);
  224.                 return $this->json(['success' => false'message' => sprintf($this->translator->trans("missing_required_parameters: %s"), $parameterList)]);
  225.             }
  226.             $hour $params['hour'];
  227.             $parameter $params['parameter'];
  228.             $date $params['date'];
  229.             $coordinates $params['coordinates'];
  230.             $model $params['model'];
  231.             $format $params['format'];
  232.             $response $this->meteomaticsWeatherService->getTidalAmplitudes(
  233.                 $hour,
  234.                 $parameter,
  235.                 $date,
  236.                 $coordinates,
  237.                 $model,
  238.                 $format
  239.             );
  240.             // You can return or process the $data as needed
  241.             // For demonstration purposes, we will just return a JSON response
  242.             return $this->json($response);
  243.         } catch (\Exception $ex) {
  244.             $this->logger->error($ex->getMessage());
  245.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  246.         }
  247.     }
  248.     /**
  249.      * @Route("/high-low-tide-times", name="high_low_tide_times", methods={"POST"})
  250.      */
  251.     public function highLowTideTimesData(Request $request): JsonResponse
  252.     {
  253.         try {
  254.             $params json_decode($request->getContent(), true);
  255.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  256.             // check user credentials and expiry
  257.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  258.             if ($response['success'] !== true) {
  259.                 return $this->json($response);
  260.             }
  261.             $requiredParameters = ['hour''date''coordinates''model''format'];
  262.             foreach ($requiredParameters as $param) {
  263.                 if (!isset($params[$param])) {
  264.                     $missingParams[] = $param;
  265.                 }
  266.             }
  267.             if (!empty($missingParams)) {
  268.                 // Throw an exception with a message that includes the missing parameters
  269.                 $parameterList implode(", "$missingParams);
  270.                 return $this->json(['success' => false'message' => sprintf($this->translator->trans("missing_required_parameters: %s"), $parameterList)]);
  271.             }
  272.             $hour $params['hour'];
  273.             $date $params['date'];
  274.             $coordinates $params['coordinates'];
  275.             $model $params['model'];
  276.             $format $params['format'];
  277.             $response $this->meteomaticsWeatherService->getHighLowTideTimes(
  278.                 $hour,
  279.                 $date,
  280.                 $coordinates,
  281.                 $model,
  282.                 $format
  283.             );
  284.             // You can return or process the $data as needed
  285.             // For demonstration purposes, we will just return a JSON response
  286.             return $this->json($response);
  287.         } catch (\Exception $ex) {
  288.             $this->logger->error($ex->getMessage());
  289.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  290.         }
  291.     }
  292.     /**
  293.      * @Route("/significant-wave-height", name="significant_wave_height", methods={"POST"})
  294.      */
  295.     public function significantWaveHeightData(Request $request): JsonResponse
  296.     {
  297.         try {
  298.             $params json_decode($request->getContent(), true);
  299.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  300.             // check user credentials and expiry
  301.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  302.             if ($response['success'] !== true) {
  303.                 return $this->json($response);
  304.             }
  305.             $requiredParameters = ['hour''date''coordinates''format'];
  306.             foreach ($requiredParameters as $param) {
  307.                 if (!isset($params[$param])) {
  308.                     $missingParams[] = $param;
  309.                 }
  310.             }
  311.             if (!empty($missingParams)) {
  312.                 // Throw an exception with a message that includes the missing parameters
  313.                 $parameterList implode(", "$missingParams);
  314.                 return $this->json(['success' => false'message' => sprintf($this->translator->trans("missing_required_parameters: %s"), $parameterList)]);
  315.             }
  316.             $hour $params['hour'];
  317.             $date $params['date'];
  318.             $coordinates $params['coordinates'];
  319.             $format $params['format'];
  320.             $response $this->meteomaticsWeatherService->getSignificantWaveHeight(
  321.                 $hour,
  322.                 $date,
  323.                 $coordinates,
  324.                 $format
  325.             );
  326.             // You can return or process the $data as needed
  327.             // For demonstration purposes, we will just return a JSON response
  328.             return $this->json($response);
  329.         } catch (\Exception $ex) {
  330.             $this->logger->error($ex->getMessage());
  331.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  332.         }
  333.     }
  334.     /**
  335.      * @Route("/surge-amplitude", name="surge_amplitude", methods={"POST"})
  336.      */
  337.     public function surgeAmplitudeData(Request $request): JsonResponse
  338.     {
  339.         try {
  340.             $params json_decode($request->getContent(), true);
  341.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  342.             // check user credentials and expiry
  343.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  344.             if ($response['success'] !== true) {
  345.                 return $this->json($response);
  346.             }
  347.             $requiredParameters = ['hour''date''coordinates''model''format'];
  348.             foreach ($requiredParameters as $param) {
  349.                 if (!isset($params[$param])) {
  350.                     $missingParams[] = $param;
  351.                 }
  352.             }
  353.             if (!empty($missingParams)) {
  354.                 // Throw an exception with a message that includes the missing parameters
  355.                 $parameterList implode(", "$missingParams);
  356.                 return $this->json(['success' => false'message' => sprintf($this->translator->trans("missing_required_parameters: %s"), $parameterList)]);
  357.             }
  358.             $hour $params['hour'];
  359.             $date $params['date'];
  360.             $coordinates $params['coordinates'];
  361.             $model $params['model'];
  362.             $format $params['format'];
  363.             $response $this->meteomaticsWeatherService->getSurgeAmplitude(
  364.                 $hour,
  365.                 $date,
  366.                 $coordinates,
  367.                 $model,
  368.                 $format
  369.             );
  370.             // You can return or process the $data as needed
  371.             // For demonstration purposes, we will just return a JSON response
  372.             return $this->json($response);
  373.         } catch (\Exception $ex) {
  374.             $this->logger->error($ex->getMessage());
  375.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  376.         }
  377.     }
  378.     /**
  379.      * @Route("/fog", name="fog", methods={"POST"})
  380.      */
  381.     public function fogData(Request $request): JsonResponse
  382.     {
  383.         try {
  384.             $params json_decode($request->getContent(), true);
  385.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  386.             // check user credentials and expiry
  387.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  388.             if ($response['success'] !== true) {
  389.                 return $this->json($response);
  390.             }
  391.             $requiredParameters = ['hour''date''coordinates''format'];
  392.             foreach ($requiredParameters as $param) {
  393.                 if (!isset($params[$param])) {
  394.                     $missingParams[] = $param;
  395.                 }
  396.             }
  397.             if (!empty($missingParams)) {
  398.                 // Throw an exception with a message that includes the missing parameters
  399.                 $parameterList implode(", "$missingParams);
  400.                 return $this->json(['success' => false'message' => sprintf($this->translator->trans("missing_required_parameters: %s"), $parameterList)]);
  401.             }
  402.             $hour $params['hour'];
  403.             $date $params['date'];
  404.             $coordinates $params['coordinates'];
  405.             $format $params['format'];
  406.             $response $this->meteomaticsWeatherService->getFog(
  407.                 $coordinates,
  408.                 $date
  409.             );
  410.             // You can return or process the $data as needed
  411.             // For demonstration purposes, we will just return a JSON response
  412.             return $this->json($response);
  413.         } catch (\Exception $ex) {
  414.             $this->logger->error($ex->getMessage());
  415.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  416.         }
  417.     }
  418.     /**
  419.      * @Route("/search-ews-notification",  name = "search-ews-notification", methods={"POST"})
  420.      */
  421.     public function searchEwsNotificationAction(Request $requestPaginatorInterface $paginator): JsonResponse
  422.     {
  423.         try {
  424.             // check user credentials and expiry
  425.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  426.             if ($response['success'] !== true) {
  427.                 return $this->json($response);
  428.             }
  429.             $params json_decode($request->getContent(), true);
  430.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  431.             $lang = (isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  432.             $result $this->ewsNotificationModel->searchEwsNotification($params$lang$paginator null$this->translator);
  433.             return $this->json($result);
  434.         } catch (\Exception $ex) {
  435.             $this->logger->error($ex->getMessage());
  436.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  437.         }
  438.     }
  439.     /**
  440.      * Proxies to NCM_EWS_API_URL so clients use this app, not ncmews.centricdxb.com directly.
  441.      * POST body is forwarded as JSON; server uses NCM_API_USERNAME / NCM_API_PASSWORD.
  442.      *
  443.      * @Route("/view-ews-notification", name="view-ews-notification", methods={"POST"})
  444.      */
  445.     public function viewEwsNotificationAction(Request $request): JsonResponse
  446.     {
  447.         try {
  448.             $authResponse $this->publicUserPermissionService->isAuthorized($request$this->translator);
  449.             if ($authResponse['success'] !== true) {
  450.                 return $this->json($authResponse);
  451.             }
  452.             $params json_decode($request->getContent(), true);
  453.             if (is_array($params)) {
  454.                 $this->translator->setlocale(isset($params['lang']) ? $params['lang'] : DEFAULT_LOCALE);
  455.             }
  456.             $payload is_array($params) ? $params : [];
  457.             $ewsPortalModel = new EwsPortalModel();
  458.             $result $ewsPortalModel->viewEwsNotificationRemote($payload);
  459.             if (empty($result['configured'])) {
  460.                 return $this->json(['success' => false'message' => 'EWS upstream is not configured'], 503);
  461.             }
  462.             $statusCode = (int) ($result['statusCode'] ?? 502);
  463.             if (is_array($result['body'] ?? null)) {
  464.                 return new JsonResponse($result['body'], $statusCode);
  465.             }
  466.             $body $result['body'] ?? null;
  467.             if ($body !== null) {
  468.                 return new JsonResponse($body$statusCode);
  469.             }
  470.             if (!empty($result['rawBody'])) {
  471.                 return new JsonResponse(
  472.                     ['success' => false'message' => 'Invalid response from EWS service'],
  473.                     $statusCode $statusCode 502
  474.                 );
  475.             }
  476.             return $this->json(
  477.                 ['success' => false'message' => $result['error'] ?? 'Upstream request failed'],
  478.                 $statusCode $statusCode 502
  479.             );
  480.         } catch (\Exception $ex) {
  481.             $this->logger->error($ex->getMessage());
  482.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  483.         }
  484.     }
  485.     /**
  486.      * @Route("/translations/{locale?}", name="translations", methods={"GET"})
  487.      */
  488.     public function frontendDashboardTranslation(Request $request$locale null)
  489.     {
  490.         try {
  491.             $response = [];
  492.             $db \Pimcore\Db::get();
  493.             if ($locale) {
  494.                 // Fetch translations for the provided locale
  495.                 $selectedLocalities $db->fetchAllAssociative("
  496.                 SELECT * FROM translations_messages 
  497.                 WHERE `key` LIKE 'publicapp_%' 
  498.                 AND `language` = :locale 
  499.                 ORDER BY `creationDate` ASC
  500.             ", ['locale' => $locale]);
  501.                 foreach ($selectedLocalities as $value) {
  502.                     $keyParts explode('_'$value['key'], 3);
  503.                     $category $keyParts[1];
  504.                     $key $keyParts[2];
  505.                     if (!isset($response[$category])) {
  506.                         $response[$category] = [];
  507.                     }
  508.                     $response[$category][$key] = $value['text'];
  509.                 }
  510.                 return $this->json($response);
  511.             } else {
  512.                 // Fetch translations for both 'ar' and 'en'
  513.                 $selectedLocalities $db->fetchAllAssociative("
  514.                 SELECT * FROM translations_messages 
  515.                 WHERE `key` LIKE 'publicapp_%' 
  516.                 AND `language` IN ('ar', 'en') 
  517.                 ORDER BY `creationDate` ASC
  518.             ");
  519.                 foreach ($selectedLocalities as $value) {
  520.                     $keyParts explode('_'$value['key'], 3);
  521.                     $category $keyParts[1];
  522.                     $key $keyParts[2];
  523.                     $language $value['language'];
  524.                     if (!isset($response[$language])) {
  525.                         $response[$language] = [];
  526.                     }
  527.                     if (!isset($response[$language][$category])) {
  528.                         $response[$language][$category] = [];
  529.                     }
  530.                     $response[$language][$category][$key] = $value['text'];
  531.                 }
  532.                 return $this->json($response);
  533.             }
  534.         } catch (\Exception $ex) {
  535.             $this->logger->error($ex->getMessage());
  536.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  537.         }
  538.     }
  539.     /**
  540.      * @Route("/list-ewsnotification", methods={"POST"})
  541.      */
  542.     public function listEwsNotificationAction(Request $requestPaginatorInterface $paginator): JsonResponse
  543.     {
  544.         try {
  545.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  546.             if ($response['success'] !== true) {
  547.                 return $this->json($response);
  548.             }
  549.             $params json_decode($request->getContent(), true);
  550.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  551.             $params['unpublished'] = false;
  552.             $params['status'] = 'active';
  553.             $result $this->ewsNotificationModel->notificationListing($params$paginator null$this->translator);
  554.             return $this->json($result);
  555.         } catch (\Exception $ex) {
  556.             $this->logger->error($ex->getMessage());
  557.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  558.         }
  559.     }
  560.     /**
  561.      * @Route("/query", methods={"POST"})
  562.      */
  563.     public function fetchMeteomaticData(Request $request): JsonResponse
  564.     {
  565.         $params json_decode($request->getContent(), true);
  566.         $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  567.         // check user credentials and expiry
  568.         $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  569.         if ($response['success'] !== true) {
  570.             return $this->json($response);
  571.         }
  572.         if (!isset($params['format'])) {
  573.             throw new \InvalidArgumentException('Missing "format" parameter');
  574.         }
  575.         if ($params['format'] == "json") {
  576.             if (
  577.                 !isset($params['startdate']) ||
  578.                 !isset($params['enddate']) ||
  579.                 !isset($params['resolution']) ||
  580.                 !isset($params['parameters']) ||
  581.                 !isset($params['lat']) ||
  582.                 !isset($params['lon']) ||
  583.                 !isset($params['format'])
  584.             ) {
  585.                 throw new \Exception('Missing required parameters');
  586.             }
  587.             // date_default_timezone_set('UTC');
  588.             //$hour = $params['hour'];
  589.             $format $params['format'];
  590.             $startDate $params['startdate'];
  591.             $endDate $params['enddate'];
  592.             $resolution $params['resolution'];
  593.             $parameters $params['parameters'];
  594.             $model $params['model'];
  595.             $lat $params['lat'];
  596.             $lon $params['lon'];
  597.             $response $this->meteomaticApiService->timeSeriesQuery(
  598.                 $startDate,
  599.                 $endDate,
  600.                 $resolution,
  601.                 $parameters,
  602.                 $model,
  603.                 $lat,
  604.                 $lon,
  605.                 $format,
  606.                 $this->translator
  607.             );
  608.         } else {
  609.             $mandatoryParams = ['version''request''layers''crs''bbox''format''width''height''tiled'];
  610.             foreach ($mandatoryParams as $param) {
  611.                 if (!isset($params[$param]) || empty($params[$param])) {
  612.                     $missingParams[] = $param;
  613.                 }
  614.             }
  615.             if (!empty($missingParams)) {
  616.                 // Throw an exception with a message that includes the missing parameters
  617.                 $parameterList implode(", "$missingParams);
  618.                 throw new \InvalidArgumentException(sprintf($this->translator->trans("missing_or_empty_mandatory_parameter: %s"), $parameterList));
  619.             }
  620.             header('Content-Type: image/png');
  621.             $result $this->meteomaticsWeatherService->getWeatherMap($params['version'], $params['request'], $params['layers'], $params['crs'], $params['bbox'], $params['format'], $params['width'], $params['height'], $params['tiled']);
  622.             echo $result;
  623.             exit;
  624.         }
  625.         // You can return or process the $data as needed
  626.         // For demonstration purposes, we will just return a JSON response
  627.         return $this->json($response);
  628.     }
  629.     /**
  630.      * @Route("/public-query", methods={"POST"})
  631.      */
  632.     public function publicMetarQuery(Request $request): JsonResponse
  633.     {
  634.         try {
  635.             $params json_decode($request->getContent(), true);
  636.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  637.             //check user credentials and expiry
  638.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  639.             if ($response['success'] !== true) {
  640.                 return $this->json($response);
  641.             }
  642.             if ($response['success'] !== true) {
  643.                 return $this->json($response);
  644.             }
  645.             if (
  646.                 !isset($params['startdate']) ||
  647.                 !isset($params['enddate']) ||
  648.                 !isset($params['resolution']) ||
  649.                 !isset($params['parameters']) ||
  650.                 !isset($params['coordinate']) ||
  651.                 !isset($params['format'])
  652.             ) {
  653.                 throw new \Exception('Missing required parameters');
  654.             }
  655.             // date_default_timezone_set('UTC');
  656.             //$hour = $params['hour'];
  657.             $format $params['format'];
  658.             $startDate $params['startdate'];
  659.             $endDate $params['enddate'];
  660.             $resolution $params['resolution'];
  661.             $parametersArray $params['parameters'];
  662.             $coordinate $params['coordinate'];
  663.             $parameters implode(','$parametersArray);
  664.             $model '';
  665.             if (isset($params['model']) && !empty($params['model'])) {
  666.                 $model $params['model'];
  667.             }
  668.             $source '';
  669.             if (isset($params['source']) && !empty($params['source'])) {
  670.                 $source $params['source'];
  671.             }
  672.             $onInvalid '';
  673.             if (isset($params['on_invalid']) && !empty($params['on_invalid'])) {
  674.                 $onInvalid $params['on_invalid'];
  675.             }
  676.             $startDate = new DateTime($params['startdate']);
  677.             $endDate = new DateTime($params['enddate']);
  678.             $publicQueryCacheKey $this->buildPublicQueryRedisCacheKey(
  679.                 $startDate,
  680.                 $endDate,
  681.                 $resolution,
  682.                 $parametersArray,
  683.                 $coordinate,
  684.                 $format,
  685.                 $model,
  686.                 $source,
  687.                 $onInvalid
  688.             );
  689.             $publicQueryTtlSeconds 3600;
  690.             try {
  691.                 $cachedResponse $this->redisCache->get($publicQueryCacheKey);
  692.                 if (is_array($cachedResponse)
  693.                     && ($cachedResponse['success'] ?? false) === true
  694.                     && isset($cachedResponse['data'])) {
  695.                     return $this->json($cachedResponse);
  696.                 }
  697.             } catch (\Throwable $e) {
  698.             }
  699.             $response $this->meteomaticApiService->publicRouteQuery(
  700.                 $startDate,
  701.                 $endDate,
  702.                 $resolution,
  703.                 $parametersArray,
  704.                 $coordinate,
  705.                 $format,
  706.                 $model,
  707.                 $source,
  708.                 $onInvalid,
  709.             );
  710.             try {
  711.                 if (is_array($response) && ($response['success'] ?? false) === true) {
  712.                     $this->redisCache->set($publicQueryCacheKey$response$publicQueryTtlSeconds);
  713.                 }
  714.             } catch (\Throwable $e) {
  715.             }
  716.             return $this->json($response);
  717.         } catch (\Exception $ex) {
  718.             $this->logger->error($ex->getMessage());
  719.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  720.         }
  721.     }
  722.     /**
  723.      * @Route("/get-metar-data", methods={"POST"})
  724.      */ 
  725.     public function getMetarData(Request $request): Response
  726.     {
  727.         try {
  728.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  729.             if ($response['success'] !== true) {
  730.                 return $this->json($response);
  731.             }
  732.             $params json_decode($request->getContent(), true);
  733.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  734.             $requiredParameters = ['startDate''endDate''metar''duration'];
  735.             foreach ($requiredParameters as $param) {
  736.                 if (!isset($params[$param])) {
  737.                     $missingParams[] = $param;
  738.                 }
  739.             }
  740.             if (!empty($missingParams)) {
  741.                 // Throw an exception with a message that includes the missing parameters
  742.                 $parameterList implode(", "$missingParams);
  743.                 return $this->json(['success' => false'message' => sprintf($this->translator->trans("missing_required_parameters: %s"), $parameterList)]);
  744.             }
  745.             $startDate $params['startDate'];
  746.             $endDate $params['endDate'];
  747.             $metar $params['metar'];
  748.             $duration $params['duration'];
  749.             $parameters $params['parameter'] ?? null;
  750.             $genExcel $request->get('gen_excel'false);
  751.             $metarData $this->meteomaticsWeatherService->getMetarData($startDate$endDate$metar$duration$this->translator$parameters$genExcel);
  752.             return $this->json(['success' => true'message' => $this->translator->trans("excel_file_downloaded_successfully"), 'data' => $metarData]);
  753.         } catch (\Exception $ex) {
  754.             $this->logger->error($ex->getMessage());
  755.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  756.         }
  757.     }
  758.     /**
  759.      * @Route("/get-airport-stations", methods={"POST"})
  760.      */
  761.     public function getAirportStations(Request $request): JsonResponse
  762.     {
  763.         try {
  764.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  765.             if ($response['success'] !== true) {
  766.                 return $this->json($response);
  767.             }
  768.             $params json_decode($request->getContent(), true);
  769.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  770.             $result $this->ncmWeatherApiService->getAirportStations($params['ccode'] ?? null);
  771.             return $this->json(['success' => true'data' => $result]);
  772.         } catch (\Exception $ex) {
  773.             $this->logger->error($ex->getMessage());
  774.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  775.         }
  776.     }
  777.     /**
  778.      * @Route("/get-forecast-cities", methods={"POST"})
  779.      */
  780.     public function getForecastCities(Request $request): Response
  781.     {
  782.         try {
  783.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  784.             if ($response['success'] !== true) {
  785.                 return $this->json($response);
  786.             }
  787.             $params json_decode($request->getContent(), true);
  788.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  789.             $forecastCityModel = new WeatherForecastCityModel();
  790.             $cities $forecastCityModel->getWeatherForecastCities();
  791.             return $this->json(['success' => true'data' => $cities]);
  792.         } catch (\Exception $ex) {
  793.             $this->logger->error($ex->getMessage());
  794.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  795.         }
  796.     }
  797.     /**
  798.      * @Route("/momra-generate-token", methods={"POST"})
  799.      */
  800.     public function getMomraGenerateToken(Request $request): JsonResponse
  801.     {
  802.         try {
  803.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  804.             if ($response['success'] !== true) {
  805.                 return $this->json($response);
  806.             }
  807.             $result $this->ncmWeatherApiService->generateMomraToken();
  808.             return $this->json($result);
  809.         } catch (\Exception $ex) {
  810.             $this->logger->error($ex->getMessage());
  811.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  812.         }
  813.     }
  814.     /**
  815.      * @Route("/list-weather-station", methods={"POST"})
  816.      */
  817.     public function listWeatherStation(Request $requestPaginatorInterface $paginator): JsonResponse
  818.     {
  819.         try {
  820.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  821.             if ($response['success'] !== true) {
  822.                 return $this->json($response);
  823.             }
  824.             $params  json_decode($request->getContent(), true);
  825.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  826.             $search = (isset($params['search']) && !empty($params['search'])) ? $params['search'] : null;
  827.             $id = (isset($params['id']) && !empty($params['id'])) ? $params['id'] : null;
  828.             $result $this->weatherStationModel->getWeatherStations($paginator nullnullnull$this->translator$search$id);
  829.             return $this->json($result);
  830.         } catch (\Exception $ex) {
  831.             $this->logger->error($ex->getMessage());
  832.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  833.         }
  834.     }
  835.     /**
  836.      * @Route("/get-station-data", methods={"GET"})
  837.      */
  838.     public function getWeatherStationDataAction(Request $request)
  839.     {
  840.         try {
  841.             $params json_decode($request->getContent(), true);
  842.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  843.             // check user credentials and expiry
  844.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  845.             if ($response['success'] !== true) {
  846.                 return $this->json($response);
  847.             }
  848.             $typeName $request->get('type_name');
  849.             $parameters $request->get('parameters');
  850.             $dateTime $request->get('date_time');
  851.             $bBox $request->get('b_box');
  852.             if (!$typeName) {
  853.                 throw new \InvalidArgumentException("Missing mandatory parameter: type_name");
  854.             }
  855.             if (!$parameters) {
  856.                 throw new \InvalidArgumentException("Missing mandatory parameter: parameters");
  857.             }
  858.             if (!$dateTime) {
  859.                 throw new \InvalidArgumentException("Missing mandatory parameter: date_time");
  860.             }
  861.             if (!$bBox) {
  862.                 throw new \InvalidArgumentException("Missing mandatory parameter: b_box");
  863.             }
  864.             $result $this->meteomaticsWeatherService->getWeatherStationData($typeName$parameters$dateTime$bBox);
  865.             return $result;
  866.         } catch (\Exception $ex) {
  867.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  868.         }
  869.     }
  870.     /**
  871.      * @Route("/get-push-alert-notification", methods={"POST"})
  872.      */
  873.     public function getPushAlertNotification(Request $request): Response
  874.     {
  875.         try {
  876.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  877.             if ($response['success'] !== true) {
  878.                 return $this->json($response);
  879.             }
  880.             $params json_decode($request->getContent(), true);
  881.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  882.             $result  $this->deviceModel->getPushAlertNotification($params$this->translator);
  883.             return $this->json(['success' => true'data' => $result]);
  884.         } catch (\Exception $ex) {
  885.             $this->logger->error($ex->getMessage());
  886.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  887.         }
  888.     }
  889.     /**
  890.      * @Route("/manned-alerts", methods={"POST"})
  891.      */
  892.     public function mannedAlerts(Request $requestPaginatorInterface $paginator): JsonResponse
  893.     {
  894.         try {
  895.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  896.             if ($response['success'] !== true) {
  897.                 return $this->json($response);
  898.             }
  899.             $params json_decode($request->getContent(), true);
  900.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  901.             if (!isset($params['from_date']) || !isset($params['to_date'])) {
  902.                 // throw new \Exception('Date range is required');
  903.                 return $this->json(['success' => false'message' =>  $this->translator->trans('date_range_is_required')]);
  904.             }
  905.             $result $this->customNotificationService->getMannedAlerts($params$paginator null);
  906.             return $this->json($result);
  907.         } catch (\Exception $ex) {
  908.             $this->logger->error($ex->getMessage());
  909.             return $this->json(['success' => false'message' =>  $this->translator->trans(USER_ERROR_MESSAGE)]);
  910.         }
  911.     }
  912.     /**
  913.      * @Route("/get-phenomena-list", methods={"POST"})
  914.      */
  915.     public function getPhenomenaList(Request $request): JsonResponse
  916.     {
  917.         try {
  918.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  919.             if ($response['success'] !== true) {
  920.                 return $this->json($response);
  921.             }
  922.             $params json_decode($request->getContent(), true);
  923.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  924.             $result $this->ewsNotificationModel->getPhenomenaList();
  925.             return $this->json($result);
  926.         } catch (\Exception $ex) {
  927.             $this->logger->error($ex->getMessage());
  928.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  929.         }
  930.     }
  931.     /**
  932.      * @Route("/get-today-weather-report", methods={"POST"})
  933.      */
  934.     public function getTodayWeatherReport(Request $request): JsonResponse
  935.     {
  936.         try {
  937.             // check user credentials and expiry
  938.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  939.             if ($response['success'] !== true) {
  940.                 return $this->json($response);
  941.             }
  942.             // get request params
  943.             $params json_decode($request->getContent(), true);
  944.             // set locale
  945.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  946.             // get today weather report data
  947.             $result $this->reportLogModel->getTodayWeatherReportData($request$this->translator);
  948.             return $this->json($result);
  949.         } catch (\Exception $ex) {
  950.             $this->logger->error($ex->getMessage());
  951.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  952.         }
  953.     }
  954.     /**
  955.      * @Route("/get-all-weather-report", methods={"POST"})
  956.      */
  957.     public function getAllWeatherReport(Request $request): JsonResponse
  958.     {
  959.         try {
  960.             // check user credentials and expiry
  961.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  962.             if ($response['success'] !== true) {
  963.                 return $this->json($response);
  964.             }
  965.             // get request params
  966.             $params json_decode($request->getContent(), true);
  967.             // set locale
  968.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  969.             // get today weather report data
  970.             $result $this->reportLogModel->getAllWeatherReportData($request$this->translator);
  971.             return $this->json($result);
  972.         } catch (\Exception $ex) {
  973.             $this->logger->error($ex->getMessage());
  974.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  975.         }
  976.     }
  977.     /**
  978.      * @Route("/get-mobile-assets-list", methods={"POST"})
  979.      */
  980.     public function getMobileAssetsList(Request $request): JsonResponse
  981.     {
  982.         try {
  983.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  984.             if ($response['success'] !== true) {
  985.                 return $this->json($response);
  986.             }
  987.             $data = [];
  988.             $list = new \Pimcore\Model\Asset\Listing();
  989.             $list->setCondition("path LIKE ?", ["/mobile-assets%"]);
  990.             $list->load();
  991.             foreach ($list as $key => $value) {
  992.                 if ($value->getType() == "image") {
  993.                     $data[] = BASE_URL $value->getPath() . $value->getFilename();
  994.                 } elseif ($value->getType() == 'video') {
  995.                     $data[] = BASE_URL $value->getPath() . $value->getFilename();
  996.                 }
  997.             }
  998.             $result = ["success" => true"data" => $data];
  999.             return $this->json($result);
  1000.         } catch (\Exception $ex) {
  1001.             $this->logger->error($ex->getMessage());
  1002.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  1003.         }
  1004.     }
  1005.     /**
  1006.      * @Route("/kingdom-weather-description", methods={"POST"})
  1007.      */
  1008.     public function kingdomWeatherDirect(Request $request): JsonResponse
  1009.     {
  1010.         try {
  1011.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  1012.             if ($response['success'] !== true) {
  1013.                 return $this->json($response);
  1014.             }
  1015.             // get request params
  1016.             $params json_decode($request->getContent(), true);
  1017.             // set locale
  1018.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  1019.             return $this->json($this->ncmWeatherApiService->getKingdomWeatherData());
  1020.         } catch (\Exception $ex) {
  1021.             $this->logger->error($ex->getMessage());
  1022.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  1023.         }
  1024.     }
  1025.     /**
  1026.      * @Route("/get-hijri-date", name="api_ncm_device_get_hijri_date", methods={"POST"})
  1027.      */
  1028.     public function getHijriDatetime(Request $request): Response
  1029.     {
  1030.         $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  1031.         if ($response['success'] !== true) {
  1032.             return $this->json($response);
  1033.         }
  1034.         $params json_decode($request->getContent(), true);
  1035.         if (isset($params['location']) && isset($params['rawOffset'])) {
  1036.             $offset $params['rawOffset'] / 3600;
  1037.             $locations $params['location'];
  1038.             $parameters = ["sunset:sql"];
  1039.             // Join the locations and parameters strings with ',' for the URL
  1040.             $parameterString $parameters;
  1041.             $locationString implode('+'$locations);
  1042.             // Create a DateTime object
  1043.             $date = new DateTime();
  1044.             $dateStrings = [];
  1045.             // Format the DateTime object as a string and add it to the array
  1046.             for ($i 0$i count($locations); $i++) {
  1047.                 $dateStrings[] = $date->format(DateTime::ISO8601);
  1048.             }
  1049.             // Join the date strings with ',' for the URL
  1050.             $dateString implode(','$dateStrings);
  1051.             $response $this->meteomaticApiService->getRouteQuery(
  1052.                 $dateString,
  1053.                 $parameterString,
  1054.                 $locationString
  1055.             );
  1056.             $sunsetTime "";
  1057.             if ($response['success'] == true) {
  1058.                 $sunsetTime $response['data']['data'][0]['parameters'][0]['value'];
  1059.                 $response \App\Lib\Utility::getHijriDatetime($sunsetTime$offset);
  1060.             }
  1061.         } else {
  1062.             $response \App\Lib\Utility::getHijriDatetime();
  1063.         }
  1064.         return $this->json(["success" => true"date" => $response]);
  1065.     }
  1066.     /**
  1067.      * @Route("/get-isolines", methods={"GET"})
  1068.      */
  1069.     public function getIsolinesAction(Request $request): JsonResponse
  1070.     {
  1071.         try {
  1072.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  1073.             if ($response['success'] !== true) {
  1074.                 return $this->json($response);
  1075.             }
  1076.             // Get access token
  1077.             $tokenResult $this->meteomaticApiService->getToken();
  1078.             if (!isset($tokenResult['access_token'])) {
  1079.                 throw new \RuntimeException("Failed to retrieve access token");
  1080.             }
  1081.             $accessToken $tokenResult['access_token'];
  1082.             $measure $request->get('measure');
  1083.             $dateTime $request->get('date_time');
  1084.             $accessToken $accessToken;
  1085.             $bbox $request->get('bbox');
  1086.             if (!$measure) {
  1087.                 throw new \InvalidArgumentException("Missing mandatory parameter: measure");
  1088.             }
  1089.             if (!$dateTime) {
  1090.                 throw new \InvalidArgumentException("Missing mandatory parameter: date_time");
  1091.             }
  1092.             if (!$accessToken) {
  1093.                 throw new \InvalidArgumentException("Missing mandatory parameter: access_token");
  1094.             }
  1095.             if (isset($bbox) && !empty($bbox)) {
  1096.                 $bboxString "&bbox=" $bbox;
  1097.             } else {
  1098.                 $bboxString "";
  1099.             }
  1100.             $result $this->meteomaticsWeatherService->getIsolines($measure$dateTime$accessToken$bboxString);
  1101.             return $this->json($result);
  1102.         } catch (\Exception $ex) {
  1103.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  1104.         }
  1105.     }
  1106.     private function buildPublicQueryRedisCacheKey(
  1107.         DateTime $startDate,
  1108.         DateTime $endDate,
  1109.         string $resolution,
  1110.         array $parametersArray,
  1111.         string $coordinate,
  1112.         string $format,
  1113.         string $model,
  1114.         string $source,
  1115.         string $onInvalid
  1116.     ): string {
  1117.         $startBucket $startDate->format('Y-m-d-H');
  1118.         $endBucket $endDate->format('Y-m-d-H');
  1119.         $coordKey $this->normalizeCoordinateStringForPublicQueryCache($coordinate);
  1120.         $paramsSorted $parametersArray;
  1121.         sort($paramsSortedSORT_STRING);
  1122.         $payload implode('|', [
  1123.             $coordKey,
  1124.             $startBucket,
  1125.             $endBucket,
  1126.             $resolution,
  1127.             implode(','$paramsSorted),
  1128.             $format,
  1129.             $model,
  1130.             $source,
  1131.             $onInvalid,
  1132.         ]);
  1133.         return 'public_query:v1:' md5($payload);
  1134.     }
  1135.     private function normalizeCoordinateStringForPublicQueryCache(string $coordinate): string
  1136.     {
  1137.         $normalized str_replace(['+''_'' '], ','$coordinate);
  1138.         $parts preg_split('/,+/'$normalized, -1PREG_SPLIT_NO_EMPTY);
  1139.         $rounded = [];
  1140.         foreach ($parts as $part) {
  1141.             $part trim($part);
  1142.             if ($part === '' || !is_numeric($part)) {
  1143.                 continue;
  1144.             }
  1145.             $rounded[] = sprintf('%.3f'round((float) $part3));
  1146.         }
  1147.         return implode('_'$rounded);
  1148.     }
  1149. }