src/Controller/DeviceApiController.php line 39

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.             $isPolygon $this->parseRequestBoolean($params['is_polygon'] ?? false);
  677.             $startDate = new DateTime($params['startdate']);
  678.             $endDate = new DateTime($params['enddate']);
  679.             $publicQueryCacheKey $this->buildPublicQueryRedisCacheKey(
  680.                 $startDate,
  681.                 $endDate,
  682.                 $resolution,
  683.                 $parametersArray,
  684.                 $coordinate,
  685.                 $format,
  686.                 $model,
  687.                 $source,
  688.                 $onInvalid,
  689.                 $isPolygon
  690.             );
  691.             $publicQueryTtlSeconds 3600;
  692.             try {
  693.                 $cachedResponse $this->redisCache->get($publicQueryCacheKey);
  694.                 if (is_array($cachedResponse)
  695.                     && ($cachedResponse['success'] ?? false) === true
  696.                     && isset($cachedResponse['data'])) {
  697.                     return $this->json($cachedResponse);
  698.                 }
  699.             } catch (\Throwable $e) {
  700.             }
  701.             $response $this->meteomaticApiService->publicRouteQuery(
  702.                 $startDate,
  703.                 $endDate,
  704.                 $resolution,
  705.                 $parametersArray,
  706.                 $coordinate,
  707.                 $format,
  708.                 $model,
  709.                 $source,
  710.                 $onInvalid,
  711.                 $isPolygon
  712.             );
  713.             try {
  714.                 if (is_array($response) && ($response['success'] ?? false) === true) {
  715.                     $this->redisCache->set($publicQueryCacheKey$response$publicQueryTtlSeconds);
  716.                 }
  717.             } catch (\Throwable $e) {
  718.             }
  719.             return $this->json($response);
  720.         } catch (\Exception $ex) {
  721.             $this->logger->error($ex->getMessage());
  722.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  723.         }
  724.     }
  725.     /**
  726.      * @Route("/get-metar-data", methods={"POST"})
  727.      */ 
  728.     public function getMetarData(Request $request): Response
  729.     {
  730.         try {
  731.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  732.             if ($response['success'] !== true) {
  733.                 return $this->json($response);
  734.             }
  735.             $params json_decode($request->getContent(), true);
  736.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  737.             $requiredParameters = ['startDate''endDate''metar''duration'];
  738.             foreach ($requiredParameters as $param) {
  739.                 if (!isset($params[$param])) {
  740.                     $missingParams[] = $param;
  741.                 }
  742.             }
  743.             if (!empty($missingParams)) {
  744.                 // Throw an exception with a message that includes the missing parameters
  745.                 $parameterList implode(", "$missingParams);
  746.                 return $this->json(['success' => false'message' => sprintf($this->translator->trans("missing_required_parameters: %s"), $parameterList)]);
  747.             }
  748.             $startDate $params['startDate'];
  749.             $endDate $params['endDate'];
  750.             $metar $params['metar'];
  751.             $duration $params['duration'];
  752.             $parameters $params['parameter'] ?? null;
  753.             $genExcel $request->get('gen_excel'false);
  754.             $metarData $this->meteomaticsWeatherService->getMetarData($startDate$endDate$metar$duration$this->translator$parameters$genExcel);
  755.             return $this->json(['success' => true'message' => $this->translator->trans("excel_file_downloaded_successfully"), 'data' => $metarData]);
  756.         } catch (\Exception $ex) {
  757.             $this->logger->error($ex->getMessage());
  758.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  759.         }
  760.     }
  761.     /**
  762.      * @Route("/get-airport-stations", methods={"POST"})
  763.      */
  764.     public function getAirportStations(Request $request): JsonResponse
  765.     {
  766.         try {
  767.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  768.             if ($response['success'] !== true) {
  769.                 return $this->json($response);
  770.             }
  771.             $params json_decode($request->getContent(), true);
  772.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  773.             $result $this->ncmWeatherApiService->getAirportStations($params['ccode'] ?? null);
  774.             return $this->json(['success' => true'data' => $result]);
  775.         } catch (\Exception $ex) {
  776.             $this->logger->error($ex->getMessage());
  777.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  778.         }
  779.     }
  780.     /**
  781.      * @Route("/get-forecast-cities", methods={"POST"})
  782.      */
  783.     public function getForecastCities(Request $request): Response
  784.     {
  785.         try {
  786.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  787.             if ($response['success'] !== true) {
  788.                 return $this->json($response);
  789.             }
  790.             $params json_decode($request->getContent(), true);
  791.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  792.             $forecastCityModel = new WeatherForecastCityModel();
  793.             $cities $forecastCityModel->getWeatherForecastCities();
  794.             return $this->json(['success' => true'data' => $cities]);
  795.         } catch (\Exception $ex) {
  796.             $this->logger->error($ex->getMessage());
  797.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  798.         }
  799.     }
  800.     /**
  801.      * @Route("/momra-generate-token", methods={"POST"})
  802.      */
  803.     public function getMomraGenerateToken(Request $request): JsonResponse
  804.     {
  805.         try {
  806.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  807.             if ($response['success'] !== true) {
  808.                 return $this->json($response);
  809.             }
  810.             $result $this->ncmWeatherApiService->generateMomraToken();
  811.             return $this->json($result);
  812.         } catch (\Exception $ex) {
  813.             $this->logger->error($ex->getMessage());
  814.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  815.         }
  816.     }
  817.     /**
  818.      * @Route("/list-weather-station", methods={"POST"})
  819.      */
  820.     public function listWeatherStation(Request $requestPaginatorInterface $paginator): JsonResponse
  821.     {
  822.         try {
  823.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  824.             if ($response['success'] !== true) {
  825.                 return $this->json($response);
  826.             }
  827.             $params  json_decode($request->getContent(), true);
  828.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  829.             $search = (isset($params['search']) && !empty($params['search'])) ? $params['search'] : null;
  830.             $id = (isset($params['id']) && !empty($params['id'])) ? $params['id'] : null;
  831.             $result $this->weatherStationModel->getWeatherStations($paginator nullnullnull$this->translator$search$id);
  832.             return $this->json($result);
  833.         } catch (\Exception $ex) {
  834.             $this->logger->error($ex->getMessage());
  835.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  836.         }
  837.     }
  838.     /**
  839.      * @Route("/get-station-data", methods={"GET"})
  840.      */
  841.     public function getWeatherStationDataAction(Request $request)
  842.     {
  843.         try {
  844.             $params json_decode($request->getContent(), true);
  845.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  846.             // check user credentials and expiry
  847.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  848.             if ($response['success'] !== true) {
  849.                 return $this->json($response);
  850.             }
  851.             $typeName $request->get('type_name');
  852.             $parameters $request->get('parameters');
  853.             $dateTime $request->get('date_time');
  854.             $bBox $request->get('b_box');
  855.             if (!$typeName) {
  856.                 throw new \InvalidArgumentException("Missing mandatory parameter: type_name");
  857.             }
  858.             if (!$parameters) {
  859.                 throw new \InvalidArgumentException("Missing mandatory parameter: parameters");
  860.             }
  861.             if (!$dateTime) {
  862.                 throw new \InvalidArgumentException("Missing mandatory parameter: date_time");
  863.             }
  864.             if (!$bBox) {
  865.                 throw new \InvalidArgumentException("Missing mandatory parameter: b_box");
  866.             }
  867.             $result $this->meteomaticsWeatherService->getWeatherStationData($typeName$parameters$dateTime$bBox);
  868.             return $result;
  869.         } catch (\Exception $ex) {
  870.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  871.         }
  872.     }
  873.     /**
  874.      * @Route("/get-push-alert-notification", methods={"POST"})
  875.      */
  876.     public function getPushAlertNotification(Request $request): Response
  877.     {
  878.         try {
  879.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  880.             if ($response['success'] !== true) {
  881.                 return $this->json($response);
  882.             }
  883.             $params json_decode($request->getContent(), true);
  884.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  885.             $result  $this->deviceModel->getPushAlertNotification($params$this->translator);
  886.             return $this->json(['success' => true'data' => $result]);
  887.         } catch (\Exception $ex) {
  888.             $this->logger->error($ex->getMessage());
  889.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  890.         }
  891.     }
  892.     /**
  893.      * @Route("/manned-alerts", methods={"POST"})
  894.      */
  895.     public function mannedAlerts(Request $requestPaginatorInterface $paginator): JsonResponse
  896.     {
  897.         try {
  898.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  899.             if ($response['success'] !== true) {
  900.                 return $this->json($response);
  901.             }
  902.             $params json_decode($request->getContent(), true);
  903.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  904.             if (!isset($params['from_date']) || !isset($params['to_date'])) {
  905.                 // throw new \Exception('Date range is required');
  906.                 return $this->json(['success' => false'message' =>  $this->translator->trans('date_range_is_required')]);
  907.             }
  908.             $result $this->customNotificationService->getMannedAlerts($params$paginator null);
  909.             return $this->json($result);
  910.         } catch (\Exception $ex) {
  911.             $this->logger->error($ex->getMessage());
  912.             return $this->json(['success' => false'message' =>  $this->translator->trans(USER_ERROR_MESSAGE)]);
  913.         }
  914.     }
  915.     /**
  916.      * @Route("/get-phenomena-list", methods={"POST"})
  917.      */
  918.     public function getPhenomenaList(Request $request): JsonResponse
  919.     {
  920.         try {
  921.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  922.             if ($response['success'] !== true) {
  923.                 return $this->json($response);
  924.             }
  925.             $params json_decode($request->getContent(), true);
  926.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  927.             $result $this->ewsNotificationModel->getPhenomenaList();
  928.             return $this->json($result);
  929.         } catch (\Exception $ex) {
  930.             $this->logger->error($ex->getMessage());
  931.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  932.         }
  933.     }
  934.     /**
  935.      * @Route("/get-today-weather-report", methods={"POST"})
  936.      */
  937.     public function getTodayWeatherReport(Request $request): JsonResponse
  938.     {
  939.         try {
  940.             // check user credentials and expiry
  941.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  942.             if ($response['success'] !== true) {
  943.                 return $this->json($response);
  944.             }
  945.             // get request params
  946.             $params json_decode($request->getContent(), true);
  947.             // set locale
  948.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  949.             // get today weather report data
  950.             $result $this->reportLogModel->getTodayWeatherReportData($request$this->translator);
  951.             return $this->json($result);
  952.         } catch (\Exception $ex) {
  953.             $this->logger->error($ex->getMessage());
  954.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  955.         }
  956.     }
  957.     /**
  958.      * @Route("/get-all-weather-report", methods={"POST"})
  959.      */
  960.     public function getAllWeatherReport(Request $request): JsonResponse
  961.     {
  962.         try {
  963.             // check user credentials and expiry
  964.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  965.             if ($response['success'] !== true) {
  966.                 return $this->json($response);
  967.             }
  968.             // get request params
  969.             $params json_decode($request->getContent(), true);
  970.             // set locale
  971.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  972.             // get today weather report data
  973.             $result $this->reportLogModel->getAllWeatherReportData($request$this->translator);
  974.             return $this->json($result);
  975.         } catch (\Exception $ex) {
  976.             $this->logger->error($ex->getMessage());
  977.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  978.         }
  979.     }
  980.     /**
  981.      * @Route("/get-mobile-assets-list", methods={"POST"})
  982.      */
  983.     public function getMobileAssetsList(Request $request): JsonResponse
  984.     {
  985.         try {
  986.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  987.             if ($response['success'] !== true) {
  988.                 return $this->json($response);
  989.             }
  990.             $data = [];
  991.             $list = new \Pimcore\Model\Asset\Listing();
  992.             $list->setCondition("path LIKE ?", ["/mobile-assets%"]);
  993.             $list->load();
  994.             foreach ($list as $key => $value) {
  995.                 if ($value->getType() == "image") {
  996.                     $data[] = BASE_URL $value->getPath() . $value->getFilename();
  997.                 } elseif ($value->getType() == 'video') {
  998.                     $data[] = BASE_URL $value->getPath() . $value->getFilename();
  999.                 }
  1000.             }
  1001.             $result = ["success" => true"data" => $data];
  1002.             return $this->json($result);
  1003.         } catch (\Exception $ex) {
  1004.             $this->logger->error($ex->getMessage());
  1005.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  1006.         }
  1007.     }
  1008.     /**
  1009.      * @Route("/kingdom-weather-description", methods={"POST"})
  1010.      */
  1011.     public function kingdomWeatherDirect(Request $request): JsonResponse
  1012.     {
  1013.         try {
  1014.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  1015.             if ($response['success'] !== true) {
  1016.                 return $this->json($response);
  1017.             }
  1018.             // get request params
  1019.             $params json_decode($request->getContent(), true);
  1020.             // set locale
  1021.             $this->translator->setlocale(isset($params["lang"]) ? $params["lang"] : DEFAULT_LOCALE);
  1022.             return $this->json($this->ncmWeatherApiService->getKingdomWeatherData());
  1023.         } catch (\Exception $ex) {
  1024.             $this->logger->error($ex->getMessage());
  1025.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  1026.         }
  1027.     }
  1028.     /**
  1029.      * @Route("/get-hijri-date", name="api_ncm_device_get_hijri_date", methods={"POST"})
  1030.      */
  1031.     public function getHijriDatetime(Request $request): Response
  1032.     {
  1033.         $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  1034.         if ($response['success'] !== true) {
  1035.             return $this->json($response);
  1036.         }
  1037.         $params json_decode($request->getContent(), true);
  1038.         if (isset($params['location']) && isset($params['rawOffset'])) {
  1039.             $offset $params['rawOffset'] / 3600;
  1040.             $locations $params['location'];
  1041.             $parameters = ["sunset:sql"];
  1042.             // Join the locations and parameters strings with ',' for the URL
  1043.             $parameterString $parameters;
  1044.             $locationString implode('+'$locations);
  1045.             // Create a DateTime object
  1046.             $date = new DateTime();
  1047.             $dateStrings = [];
  1048.             // Format the DateTime object as a string and add it to the array
  1049.             for ($i 0$i count($locations); $i++) {
  1050.                 $dateStrings[] = $date->format(DateTime::ISO8601);
  1051.             }
  1052.             // Join the date strings with ',' for the URL
  1053.             $dateString implode(','$dateStrings);
  1054.             $response $this->meteomaticApiService->getRouteQuery(
  1055.                 $dateString,
  1056.                 $parameterString,
  1057.                 $locationString
  1058.             );
  1059.             $sunsetTime "";
  1060.             if ($response['success'] == true) {
  1061.                 $sunsetTime $response['data']['data'][0]['parameters'][0]['value'];
  1062.                 $response \App\Lib\Utility::getHijriDatetime($sunsetTime$offset);
  1063.             }
  1064.         } else {
  1065.             $response \App\Lib\Utility::getHijriDatetime();
  1066.         }
  1067.         return $this->json(["success" => true"date" => $response]);
  1068.     }
  1069.     /**
  1070.      * @Route("/get-isolines", methods={"GET"})
  1071.      */
  1072.     public function getIsolinesAction(Request $request): JsonResponse
  1073.     {
  1074.         try {
  1075.             $response $this->publicUserPermissionService->isAuthorized($request$this->translator);
  1076.             if ($response['success'] !== true) {
  1077.                 return $this->json($response);
  1078.             }
  1079.             // Get access token
  1080.             $tokenResult $this->meteomaticApiService->getToken();
  1081.             if (!isset($tokenResult['access_token'])) {
  1082.                 throw new \RuntimeException("Failed to retrieve access token");
  1083.             }
  1084.             $accessToken $tokenResult['access_token'];
  1085.             $measure $request->get('measure');
  1086.             $dateTime $request->get('date_time');
  1087.             $accessToken $accessToken;
  1088.             $bbox $request->get('bbox');
  1089.             if (!$measure) {
  1090.                 throw new \InvalidArgumentException("Missing mandatory parameter: measure");
  1091.             }
  1092.             if (!$dateTime) {
  1093.                 throw new \InvalidArgumentException("Missing mandatory parameter: date_time");
  1094.             }
  1095.             if (!$accessToken) {
  1096.                 throw new \InvalidArgumentException("Missing mandatory parameter: access_token");
  1097.             }
  1098.             if (isset($bbox) && !empty($bbox)) {
  1099.                 $bboxString "&bbox=" $bbox;
  1100.             } else {
  1101.                 $bboxString "";
  1102.             }
  1103.             $result $this->meteomaticsWeatherService->getIsolines($measure$dateTime$accessToken$bboxString);
  1104.             return $this->json($result);
  1105.         } catch (\Exception $ex) {
  1106.             return $this->json(['success' => false'message' => $ex->getMessage()]);
  1107.         }
  1108.     }
  1109.     private function buildPublicQueryRedisCacheKey(
  1110.         DateTime $startDate,
  1111.         DateTime $endDate,
  1112.         string $resolution,
  1113.         array $parametersArray,
  1114.         string|array $coordinate,
  1115.         string $format,
  1116.         string $model,
  1117.         string $source,
  1118.         string $onInvalid,
  1119.         bool $isPolygon false
  1120.     ): string {
  1121.         $startBucket $startDate->format('Y-m-d-H');
  1122.         $endBucket $endDate->format('Y-m-d-H');
  1123.         $coordKey $this->normalizeCoordinateStringForPublicQueryCache($coordinate);
  1124.         $paramsSorted $parametersArray;
  1125.         sort($paramsSortedSORT_STRING);
  1126.         $payload implode('|', [
  1127.             $coordKey,
  1128.             $startBucket,
  1129.             $endBucket,
  1130.             $resolution,
  1131.             implode(','$paramsSorted),
  1132.             $format,
  1133.             $model,
  1134.             $source,
  1135.             $onInvalid,
  1136.             $isPolygon '1' '0',
  1137.         ]);
  1138.         return 'public_query:v2:' md5($payload);
  1139.     }
  1140.     private function normalizeCoordinateStringForPublicQueryCache(string|array $coordinate): string
  1141.     {
  1142.         if (is_array($coordinate)) {
  1143.             if (isset($coordinate[0]) && is_array($coordinate[0])) {
  1144.                 $coordinate implode('+'array_map(
  1145.                     static fn(array $point): string => implode(','$point),
  1146.                     $coordinate
  1147.                 ));
  1148.             } else {
  1149.                 $coordinate implode(','$coordinate);
  1150.             }
  1151.         }
  1152.         $normalized str_replace(['+''_'' '], ','$coordinate);
  1153.         $parts preg_split('/,+/'$normalized, -1PREG_SPLIT_NO_EMPTY);
  1154.         $rounded = [];
  1155.         foreach ($parts as $part) {
  1156.             $part trim($part);
  1157.             if ($part === '' || !is_numeric($part)) {
  1158.                 continue;
  1159.             }
  1160.             $rounded[] = sprintf('%.3f'round((float) $part3));
  1161.         }
  1162.         return implode('_'$rounded);
  1163.     }
  1164.     private function parseRequestBoolean(mixed $value): bool
  1165.     {
  1166.         if (is_bool($value)) {
  1167.             return $value;
  1168.         }
  1169.         if (is_int($value) || is_float($value)) {
  1170.             return (bool) $value;
  1171.         }
  1172.         if (is_string($value)) {
  1173.             return in_array(strtolower(trim($value)), ['1''true''yes''on'], true);
  1174.         }
  1175.         return false;
  1176.     }
  1177. }