<?php
namespace App\Model;
use DateTime;
use Pimcore\Db;
use DOMDocument;
use Pimcore\Model\DataObject\AlertAction;
use Pimcore\Model\DataObject\AlertHazard;
use Pimcore\Model\DataObject\AlertStatus;
use Pimcore\Model\DataObject\Governorate;
use Pimcore\Model\DataObject\EwsPolygon;
use SimpleXMLElement;
use Pimcore\Model\Asset;
use Doctrine\DBAL\Connection;
use Pimcore\Model\DataObject;
use Symfony\Component\Uid\UuidV4;
use Pimcore\Model\DataObject\Region;
use Pimcore\Model\DataObject\AlertType;
use Pimcore\Model\DataObject\EwsNotification;
use Symfony\Component\HttpFoundation\Request;
use Pimcore\Model\DataObject\FetchSentEwsEmail;
use App\C2IntegrationBundle\Service\C2Service;
use Pimcore\Model\Asset\MetaData\ClassDefinition\Data\DataObject as DataDataObject;
use Pimcore\Model\DataObject\EwsAndReportUserGroup;
use App\Model\UserModel;
use App\Service\Translation\Bilingual;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use App\Lib\ConsoleBackgroundProcess;
use App\Lib\PublicPortalEwsWebhookProcessLauncher;
use Psr\Log\LoggerInterface;
use Knp\Snappy\Image as SnappyImage;
class EwsNotificationModel
{
/** Pimcore class definition maxItems for center / district many-to-many fields */
private const MAX_EWS_CENTER_DISTRICT_RELATIONS = 1000;
public $c2Service;
private $userModel;
function __construct()
{
$this->c2Service = new C2Service();
$this->userModel = new UserModel();
}
/**
* Deduplicate by object id, preserve request order, enforce Pimcore maxItems on relation fields.
*
* @param array<int, DataObject\AbstractObject|null|false> $objects
* @return DataObject\AbstractObject[]
*/
private function capUniqueDataObjectRelations(array $objects, int $max = self::MAX_EWS_CENTER_DISTRICT_RELATIONS): array
{
$out = [];
$seen = [];
foreach ($objects as $obj) {
if (!$obj instanceof DataObject\AbstractObject) {
continue;
}
$oid = $obj->getId();
if (!$oid || isset($seen[$oid])) {
continue;
}
$seen[$oid] = true;
$out[] = $obj;
if (count($out) >= $max) {
break;
}
}
return $out;
}
/**
* Coerce a request ID (scalar or single-element array) to a value Pimcore getBy* can quote.
*/
private function scalarId($value)
{
if (is_array($value)) {
$value = reset($value);
}
if ($value === false || $value === null || $value === '') {
return null;
}
return $value;
}
/**
* Split a comma-separated ID string or array, skipping empty values, preserving request order.
*
* @return string[]
*/
private function parseCommaSeparatedIds($value): array
{
if ($value === null || $value === '' || $value === false) {
return [];
}
if (is_array($value)) {
$ids = [];
foreach ($value as $id) {
if (is_array($id)) {
$id = reset($id);
}
if ($id !== null && $id !== false && $id !== '') {
$ids[] = $id;
}
}
return $ids;
}
$ids = [];
foreach (explode(',', (string) $value) as $id) {
if ($id !== '') {
$ids[] = $id;
}
}
return $ids;
}
public function createNotification($request, $params, $userInterface, $translator): array
{
$result = [];
// try {
$regionId = $params['regionId'] ?? null;
$polygonId = $params['polygonId'] ?? null;
$governorateIds = $params['governateIds'] ?? null;
$mapGovernorateIds = $params['mapGovernorateIds'] ?? null;
$municipalities = $params['municipalityIds'] ?? null;
$centerIds = $params['centerIds'] ?? null;
$districtIds = $params['districtIds'] ?? null;
$ewsOtherLocationIds = $params['ewsOtherLocationIds'] ?? null;
$alertActionId = $params['alertActionId'] ?? null;
$alertTypeId = $params['alertTypeId'] ?? null;
$weatherPhenomenonId = $params['weatherPhenomenonId'] ?? null;
$coordinates = json_encode($params['coordinates'] ?? []);
$message = $params['message'] ?? [];
$alertHazardId = $params['alertHazardId'] ?? null;
$otherLocationId = $params['otherLocationId'] ?? null;
$enableTwitterNotification = $params['enableTwitterNotification'] ?? false;
$enableSMSNotification = $params['enableSMSNotification'] ?? false;
$enableEmailNotification = $params['enableEmailNotification'] ?? false;
$x_post = $params['x_post'] ?? "";
$previewText = isset($params['previewText']) ? $params['previewText'] : false;
$notificationType = $params['notificationType'] ?? null;
$alertPage = $params['alertPage'] ?? null;
$ewsNotification = new DataObject\EwsNotification();
$ewsNotification->setParent(DataObject\Service::createFolderByPath('/Notification/EWSNotification'));
$ewsNotification->setKey(md5($coordinates . $regionId . $governorateIds) . '-' . time());
$ewsNotification->setGuid(\App\Lib\Utility::getUUID()->toRfc4122());
if (isset($params['startDate'])) {
$startTime = $params['startTime'] ?? '00:00:00';
$fromDate = $params['startDate'] . ' ' . $startTime;
$ewsNotification->setStartDate(\Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $fromDate));
}
if (isset($params['endDate'])) {
$endTime = $params['endTime'] ?? '00:00:00';
$endDate = $params['endDate'] . ' ' . $endTime;
$ewsNotification->setEndDate(\Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $endDate));
}
if (isset($message['en'])) {
$ewsNotification->setMessage($message['en'], 'en');
}
if (isset($message['ar'])) {
$ewsNotification->setMessage($message['ar'], 'ar');
}
if (!empty($regionId)) {
$regionObj = DataObject\Region::getByRegionId($regionId, 1);
if (!empty($regionObj)) {
$ewsNotification->setRegion($regionObj);
if ($governorateIds) {
$governorateIdArr = explode(",", $governorateIds);
if ($governorateIdArr) {
$applyGovernates = [];
foreach ($governorateIdArr as $id) {
if (!empty($id)) {
$applyGovernates[] = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($id, true);
if (!empty(array_unique($applyGovernates))) {
$ewsNotification->setGovernorate(array_unique($applyGovernates));
}
}
}
}
if ($municipalities) {
$municipalitiesIdArr = explode(",", $municipalities);
if ($municipalitiesIdArr) {
$applyMunicipalities = [];
foreach ($municipalitiesIdArr as $id) {
$applyMunicipalities[] = \Pimcore\Model\DataObject\Municipality::getByMunicipalityid($id, true);
if (!empty(array_unique($applyMunicipalities))) {
$ewsNotification->setMunicipality(array_unique($applyMunicipalities));
}
}
}
}
} else {
$ewsNotification->setGovernorate($this->governorateByRegion($regionObj->getId()));
}
}
}
if(!empty($mapGovernorateIds)) {
$mapGovernorateIdArr = explode(",", $mapGovernorateIds);
if ($mapGovernorateIdArr) {
$applyMapGovernates = [];
foreach ($mapGovernorateIdArr as $id) {
if (!empty($id)) {
$applyMapGovernates[] = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($id, true);
if (!empty(array_unique($applyMapGovernates))) {
$ewsNotification->setMapGovernorate(array_unique($applyMapGovernates));
}
}
}
}
}
if ($ewsOtherLocationIds) {
$ewsOtherLocationIdArr = explode(",", $ewsOtherLocationIds);
if ($ewsOtherLocationIdArr) {
$applyOtherGovernates = [];
foreach ($ewsOtherLocationIdArr as $id) {
$applyOtherGovernates[] = \Pimcore\Model\DataObject\EwsOtherLocation::getByGovernoteId($id, true);
if (!empty(array_unique($applyOtherGovernates))) {
$ewsNotification->setEwsOtherLocations(array_unique($applyOtherGovernates));
}
}
}
}
if (!empty($alertHazardId)) {
$alertHazardArr = explode(",", $alertHazardId);
if ($alertHazardArr) {
$alertHazardArrRequest = [];
foreach ($alertHazardArr as $alertHazardId) {
$weatherPhenomenonAffect = DataObject\AlertHazard::getByAlertHazardId($alertHazardId, 1);
if ($weatherPhenomenonAffect) {
$alertHazardArrRequest[] = $weatherPhenomenonAffect;
}
}
$ewsNotification->setAlertHazard($alertHazardArrRequest);
}
}
if (!empty($centerIds)) {
$applyCenters = [];
foreach (array_filter(array_map('trim', explode(',', (string) $centerIds))) as $id) {
$center = DataObject\Centers::getById((int) $id);
if ($center instanceof DataObject\Centers) {
$applyCenters[] = $center;
}
}
$applyCenters = $this->capUniqueDataObjectRelations($applyCenters);
if ($applyCenters !== []) {
$ewsNotification->setCenter($applyCenters);
}
}
if (!empty($districtIds)) {
$applyDistrict = [];
foreach (array_filter(array_map('trim', explode(',', (string) $districtIds))) as $id) {
$district = DataObject\District::getById((int) $id);
if ($district instanceof DataObject\District) {
$applyDistrict[] = $district;
}
}
$applyDistrict = $this->capUniqueDataObjectRelations($applyDistrict);
if ($applyDistrict !== []) {
$ewsNotification->setDistrict($applyDistrict);
}
}
if (!empty($alertTypeId)) {
$alertType = DataObject\AlertType::getByAlertTypeId($alertTypeId, 1);
$ewsNotification->setAlertType($alertType);
}
if (!empty($alertActionId)) {
$alertActionArr = explode(",", $alertActionId);
if ($alertActionArr) {
$alertActionArrRequest = [];
foreach ($alertActionArr as $alertActionId) {
$alertAction = DataObject\AlertAction::getByAlertActionId($alertActionId, 1);
if ($alertAction) {
$alertActionArrRequest[] = $alertAction;
}
}
$ewsNotification->setAlertAction($alertActionArrRequest);
}
}
if (!empty($otherLocationId)) {
$otherLocationArr = explode(",", $otherLocationId);
if ($otherLocationArr) {
$otherLocationArrRequest = [];
foreach ($otherLocationArr as $otherLocationId) {
$otherLocation = DataObject\OtherLocation::getByOtherLocationId($otherLocationId, 1);
if ($otherLocation) {
$otherLocationArrRequest[] = $otherLocation;
}
}
$ewsNotification->setOtherLocations($otherLocationArrRequest);
}
}
if (!empty($weatherPhenomenonId)) {
$alertStatus = DataObject\AlertStatus::getByAlertStatusId($weatherPhenomenonId, true);
$ewsNotification->setAlertStatus($alertStatus);
}
if (!empty($polygonId)) {
$ewsPolygon = EwsPolygon::getById($polygonId ?? null);
$ewsNotification->setPolygon($ewsPolygon);
$ewsNotification->setIsPolygon(true);
}
$ewsNotification->setCoordinates($coordinates);
$addressComponents = $params['address_components'] ?? [];
$this->addAddressComponentsFieldCollection($addressComponents, $ewsNotification);
if (!empty($params['file'])) {
$asset = $this->createAsset($params['file'], time() . $params['filename']);
if ($asset) {
$ewsNotification->setAttachment($asset);
}
}
if (!empty($params['x_img'])) {
$asset = $this->createAsset($params['x_img'], uniqid() . "EWSTwitterImages.png");
if ($asset) {
$ewsNotification->setXAttachment($asset);
}
}
if (!empty($params['polygon_x_img'])) {
$asset = $this->createAsset($params['polygon_x_img'], uniqid() . "EWSPolygonTwitterImages.png");
if ($asset) {
$ewsNotification->setPolygonXAtachment($asset);
}
}
$ewsNotification->setXPost($x_post);
$ewsNotification->setEnableTwitterNotification($enableTwitterNotification);
$ewsNotification->setEnableSMSNotification($enableSMSNotification);
$ewsNotification->setEnableEmailNotification($enableEmailNotification);
$ewsNotification->setUser($userInterface);
$ewsNotification->setEditor($userInterface);
$ewsNotification->setPublished(false);
$ewsNotification->setPreviewText($previewText);
$ewsNotification->setNotificationType($notificationType);
if (isset($params['alertPage'])) {
$ewsNotification->setAlertPage($alertPage);
}
//$ewsNotification->save(["versionNote" => "Update"]);
//set ews search Id
$currentDate = new \DateTime();
$formattedDate = $currentDate->format('dmY') . '-' . $ewsNotification->getId();
$searchIdEn = 'Early Warning System | ' . $formattedDate . ' | ' . ucfirst($ewsNotification->getAlertType()?->getColor()) . ' Alert | ' . $ewsNotification->getWeatherPhenomenon()?->getTitle("en");
$searchIdAr = $translator->trans('Early Warning System', [], null, "ar") . ' | ' . $formattedDate . ' | ' . $translator->trans(ucfirst($ewsNotification->getAlertType()?->getColor()) . ' Alert', [], null, "ar") . ' | ' . $ewsNotification->getWeatherPhenomenon()?->getTitle("ar");
$ewsNotification->setEwsSearchId($searchIdEn, "en");
$ewsNotification->setEwsSearchId($searchIdAr, "ar");
//$ewsNotification->save(["versionNote" => "Update"]);
$ewsNotification->save();
$this->saveUserMessageSuggestions(
(int) $userInterface->getId(),
$message
);
return ['success' => true, 'message' => $translator->trans('ews_notification_added_successifully'), "notification_id" => $ewsNotification->getId()];
// } catch (\Exception $ex) {
// $result = ['success' => false, 'message' => $ex->getMessage()];
// }
return $result;
}
public function updateNotification($request, $params, $userInterface, $translator, $userGroupIds, $isPublished, $emailService, $templating, $logger): array
{
$result = [];
// try {
$id = $params['id'] ?? null;
if (!$id) {
throw new \Exception("EWS id is required");
}
$regionId = $params['regionId'] ?? null;
$governorateIds = $params['governateIds'] ?? null;
$mapGovernorateIds = $params['mapGovernorateIds'] ?? null;
$municipalities = $params['municipalityIds'] ?? null;
$centerIds = $params['centerIds'] ?? null;
$districtIds = $params['districtIds'] ?? null;
$ewsOtherLocationIds = $params['ewsOtherLocationIds'] ?? null;
$alertActionId = $params['alertActionId'] ?? null;
$alertTypeId = $params['alertTypeId'] ?? null;
$weatherPhenomenonId = $params['weatherPhenomenonId'] ?? null;
$coordinates = json_encode($params['coordinates'] ?? []);
$message = $params['message'] ?? [];
$alertHazardId = $params['alertHazardId'] ?? null;
$enableTwitterNotification = $params['enableTwitterNotification'] ?? false;
$enableSMSNotification = $params['enableSMSNotification'] ?? false;
$enableEmailNotification = $params['enableEmailNotification'] ?? false;
$previewText = isset($params['previewText']) ? $params['previewText'] : false;
$alertPage = $params['alertPage'] ?? null;
$ewsNotification = \Pimcore\Model\DataObject::getById($id);
if (!$ewsNotification instanceof \Pimcore\Model\DataObject\EwsNotification) {
// throw new \Exception("Ews notification not found");
return ['success' => false, 'message' => $translator->trans('ews_notification_not_found')];
}
if (isset($params['startDate'])) {
$startTime = $params['startTime'] ?? '00:00:00';
$fromDate = $params['startDate'] . ' ' . $startTime;
$ewsNotification->setStartDate(\Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $fromDate));
}
if (isset($params['endDate'])) {
$endTime = $params['endTime'] ?? '00:00:00';
$endDate = $params['endDate'] . ' ' . $endTime;
$ewsNotification->setEndDate(\Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $endDate));
}
if (isset($message['en'])) {
$ewsNotification->setMessage($message['en'], 'en');
}
if (isset($message['ar'])) {
$ewsNotification->setMessage($message['ar'], 'ar');
}
if (!empty($regionId)) {
$regionObj = DataObject\Region::getByRegionId($regionId, 1);
if (!empty($regionObj)) {
$ewsNotification->setRegion($regionObj);
if ($governorateIds) {
$governorateIdArr = explode(",", $governorateIds);
if ($governorateIdArr) {
$applyGovernates = [];
foreach ($governorateIdArr as $id) {
if (!empty($id)) {
$applyGovernates[] = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($id, true);
if (!empty(array_unique($applyGovernates))) {
$ewsNotification->setGovernorate(array_unique($applyGovernates));
}
}
}
}
if ($municipalities) {
$municipalitiesIdArr = explode(",", $municipalities);
if ($municipalitiesIdArr) {
$applyMunicipalities = [];
foreach ($municipalitiesIdArr as $id) {
$applyMunicipalities[] = \Pimcore\Model\DataObject\Municipality::getByMunicipalityid($id, true);
if (!empty(array_unique($applyMunicipalities))) {
$ewsNotification->setMunicipality(array_unique($applyMunicipalities));
}
}
}
}
} else {
$ewsNotification->setGovernorate($this->governorateByRegion($regionObj->getId()));
}
}
}
if(!empty($mapGovernorateIds)) {
$mapGovernorateIdArr = explode(",", $mapGovernorateIds);
if ($mapGovernorateIdArr) {
$applyMapGovernates = [];
foreach ($mapGovernorateIdArr as $id) {
if (!empty($id)) {
$applyMapGovernates[] = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($id, true);
if (!empty(array_unique($applyMapGovernates))) {
$ewsNotification->setMapGovernorate(array_unique($applyMapGovernates));
}
}
}
}
}
if ($ewsOtherLocationIds) {
$ewsOtherLocationIdArr = explode(",", $ewsOtherLocationIds);
if ($ewsOtherLocationIdArr) {
$applyOtherGovernates = [];
foreach ($ewsOtherLocationIdArr as $id) {
$applyOtherGovernates[] = \Pimcore\Model\DataObject\EwsOtherLocation::getByGovernoteId($id, true);
if (!empty(array_unique($applyOtherGovernates))) {
$ewsNotification->setEwsOtherLocations(array_unique($applyOtherGovernates));
}
}
}
}
if (!empty($alertTypeId)) {
$alertType = DataObject\AlertType::getByAlertTypeId($alertTypeId, 1);
$ewsNotification->setAlertType($alertType);
}
if (!empty($alertActionId)) {
$alertActionArr = explode(",", $alertActionId);
if ($alertActionArr) {
$alertActionArrRequest = [];
foreach ($alertActionArr as $alertActionId) {
$alertAction = DataObject\AlertAction::getByAlertActionId($alertActionId, 1);
if ($alertAction) {
$alertActionArrRequest[] = $alertAction;
}
}
$ewsNotification->setAlertAction($alertActionArrRequest);
}
}
if (!empty($weatherPhenomenonId)) {
$alertStatus = DataObject\AlertStatus::getByAlertStatusId($weatherPhenomenonId, true);
$ewsNotification->setAlertStatus($alertStatus);
}
if (!empty($centerIds)) {
$applyCenters = [];
foreach (array_filter(array_map('trim', explode(',', (string) $centerIds))) as $id) {
$center = DataObject\Centers::getById((int) $id);
if ($center instanceof DataObject\Centers) {
$applyCenters[] = $center;
}
}
$applyCenters = $this->capUniqueDataObjectRelations($applyCenters);
if ($applyCenters !== []) {
$ewsNotification->setCenter($applyCenters);
}
}
if (!empty($districtIds)) {
$applyDistrict = [];
foreach (array_filter(array_map('trim', explode(',', (string) $districtIds))) as $id) {
$district = DataObject\District::getById((int) $id);
if ($district instanceof DataObject\District) {
$applyDistrict[] = $district;
}
}
$applyDistrict = $this->capUniqueDataObjectRelations($applyDistrict);
if ($applyDistrict !== []) {
$ewsNotification->setDistrict($applyDistrict);
}
}
if (!empty($alertHazardId)) {
$alertHazardArr = explode(",", $alertHazardId);
if ($alertHazardArr) {
$alertHazardArrRequest = [];
foreach ($alertHazardArr as $alertHazardId) {
$weatherPhenomenonAffect = DataObject\AlertHazard::getByAlertHazardId($alertHazardId, 1);
if ($weatherPhenomenonAffect) {
$alertHazardArrRequest[] = $weatherPhenomenonAffect;
}
}
$ewsNotification->setAlertHazard($alertHazardArrRequest);
}
}
$ewsNotification->setCoordinates($coordinates);
$addressComponents = $params['address_components'] ?? [];
$this->addAddressComponentsFieldCollection($addressComponents, $ewsNotification);
if (!empty($params['file'])) {
$asset = $this->createAsset($params['file'], uniqid() . $params['filename']);
if ($asset) {
$ewsNotification->setAttachment($asset);
}
}else{
$ewsNotification->setAttachment(null);
}
if (!empty($params['x_img'])) {
$asset = $this->createAsset($params['x_img'], uniqid() . "EWSTwitterImages.png");
if ($asset) {
$ewsNotification->setXAttachment($asset);
}
}else{
$ewsNotification->setXAttachment(null);
}
if (!empty($params['polygon_x_img'])) {
$asset = $this->createAsset($params['polygon_x_img'], uniqid() . "EWSPolygonTwitterImages.png");
if ($asset) {
$ewsNotification->setPolygonXAtachment($asset);
}
}else{
$ewsNotification->setPolygonXAtachment(null);
}
if (!empty($params['x_post'])) {
$ewsNotification->setXPost($params['x_post']);
}
$ewsNotification->setEnableTwitterNotification($enableTwitterNotification);
$ewsNotification->setEnableSMSNotification($enableSMSNotification);
$ewsNotification->setEnableEmailNotification($enableEmailNotification);
$ewsNotification->setPreviewText($previewText);
if (isset($params['alertPage'])) {
$ewsNotification->setAlertPage($alertPage);
}
//$ewsNotification->setPublished(true);
$ewsNotification->setUser($userInterface);
$ewsNotification->setEditor($userInterface);
//set ews search Id
$currentDate = new \DateTime();
$formattedDate = $currentDate->format('dmY') . '-' . $ewsNotification->getId();
$searchIdEn = 'Early Warning System | ' . $formattedDate . ' | ' . ucfirst($ewsNotification->getAlertType()?->getColor()) . ' Alert | ' . $ewsNotification->getWeatherPhenomenon()?->getTitle("en");
$searchIdAr = $translator->trans('Early Warning System', [], null, "ar") . ' | ' . $formattedDate . ' | ' . $translator->trans(ucfirst($ewsNotification->getAlertType()?->getColor()) . ' Alert', [], null, "ar") . ' | ' . $ewsNotification->getWeatherPhenomenon()?->getTitle("ar");
$ewsNotification->setEwsSearchId($searchIdEn, "en");
$ewsNotification->setEwsSearchId($searchIdAr, "ar");
// // When publishing an updated alert to Twitter, generate Alert History image via wkhtmltoimage.
// if ($enableTwitterNotification && $templating) {
// try {
// $historyLang = method_exists($translator, 'getLocale') ? (string) $translator->getLocale() : 'en';
// $historyAsset = $this->generateAlertHistoryTwitterImage(
// $ewsNotification,
// $templating,
// $logger,
// !empty($alertActionId),
// $translator,
// $historyLang
// );
// if ($historyAsset) {
// $ewsNotification->setXHistoryAtachment($historyAsset);
// }
// } catch (\Throwable $e) {
// $logger->error('Failed to generate EWS alert history Twitter image: ' . $e->getMessage());
// }
// }
// Allow TwitterEventListner to post again when republishing an updated alert.
if ($isPublished && $enableTwitterNotification && $ewsNotification->isPublished(true)) {
$ewsNotification->setTwitterId('');
$ewsNotification->setTwitterLog('');
}
// Persist the selected user groups so the End alert can email the same groups later.
if (!empty($userGroupIds)) {
$groups = [];
foreach ($userGroupIds as $gid) {
$group = EwsAndReportUserGroup::getById($gid, true);
if ($group instanceof EwsAndReportUserGroup) {
$groups[] = $group;
}
}
$ewsNotification->setUserGroup($groups);
}
$ewsNotification->save(["versionNote" => "Update"]);
$this->saveUserMessageSuggestions(
(int) $userInterface->getId(),
$message
);
if ($this->shouldDispatchPublicPortalEwsWebhook($ewsNotification)) {
$this->dispatchPublicPortalEwsWebhookIfConfigured((int) $ewsNotification->getId(), $logger);
}
if ($isPublished) {
// send email
// Ensure you use 'php' to execute the command.
$jsonUserGroupIds = json_encode($userGroupIds);
$process = new Process(['php', 'bin/console', 'app:send-early-warning-alert-email', '--alertId=' . $ewsNotification->getId(), '--userGroupIds=' . $jsonUserGroupIds]);
$process->setWorkingDirectory(PIMCORE_PROJECT_ROOT);
try {
$process->mustRun();
$result['success'] = true;
$logger->info("update EwsNotification command executed successfully: " . $process->getOutput());
$result['message'] = $process->getOutput();
} catch (ProcessFailedException $exception) {
$logger->error("update EwsNotification command failed: " . $exception->getMessage());
return ['success' => false, 'message' => $exception->getMessage()];
}
}
return ['success' => true, 'message' => $translator->trans('ews_notification_updated_successifully'), "notification_id" => $ewsNotification->getId()];
// } catch (\Exception $ex) {
// $result = ['success' => false, 'message' => $ex->getMessage()];
// }
return $result;
}
public function governorateByRegion($regionId)
{
$governorateArr = [];
$governorateList = new DataObject\Governorate\Listing();
$governorateList->setCondition('regionId__id = ?', $regionId);
$governorateList->load();
if (!empty($governorateList)) {
foreach ($governorateList as $governorate) {
array_push($governorateArr, $governorate);
}
}
return $governorateArr;
}
public function notificationListing($params, $paginator, $translator): array
{
$response = [];
// try {
$pageSize = isset($params['page_size']) ? $params['page_size'] : LIMIT_PER_PAGE;
$page = isset($params['page']) ? $params['page'] : 1;
$notificationList = new DataObject\EwsNotification\Listing();
// $notificationList->setOffset(!empty($params['offset']) ? $params['offset'] : 0);
// $notificationList->setLimit(!empty($params['limit']) ? $params['limit'] : LIMIT_PER_PAGE);
$notificationList->setOrderKey("o_creationDate");
$notificationList->setOrder("desc");
$notificationList->setUnpublished(true);
if ($params['unpublished'] == false) {
$notificationList->filterByPublished(true);
} else {
$notificationList->filterByPublished(false);
}
$notificationList->setOrder("desc");
if (isset($params['region']) && !empty($params['region'])) {
$regionSql = null;
$alertRegion = \Pimcore\Model\DataObject\Region::getByRegionId($params['region'], true);
if ($alertRegion) {
$regionSql .= "region__id = " . $alertRegion->getId() . " OR ";
}
$notificationList->addConditionParam("(" . substr($regionSql, 0, -3) . ")");
}
if (isset($params['status']) && !empty($params['status'])) {
$notificationList->addConditionParam("status IN (?)", [$params['status']]);
}
if (isset($params['addressCmp']) && !empty($params['addressCmp'])) {
//TODO
}
if (isset($params['start_date']) && !empty($params['start_date']) && isset($params['end_date']) && !empty($params['end_date'])) {
$notificationList->filterByStartDate(strtotime($params['start_date']), ">=");
$notificationList->filterByEndDate(strtotime($params['end_date']), "<=");
}
if (isset($params['fromDate']) && isset($params['toDate']) && !empty($params['fromDate']) && !empty($params['toDate'])) {
$fromDate = new \DateTime($params['fromDate']);
$toDate = new \DateTime($params['toDate']);
// Ensure the dates are in the correct format
$fromDateStr = strtotime($fromDate->format('Y-m-d H:i:s'));
$toDateStr = strtotime($toDate->format('Y-m-d') . ' 23:59:59');
$notificationList->addConditionParam(
"(o_creationDate >= ? AND o_creationDate <= ?)",
[$fromDateStr, $toDateStr]
);
}
$notificationList->load();
if ($notificationList) {
foreach ($notificationList as $notification) {
$response[] = $this->createNotificationFormat($notification, $translator);
}
}
if ($paginator == null) {
return ["success" => TRUE, "data" => $response];
}
$paginator = $paginator->paginate(
$response,
$page,
$pageSize
);
return ["success" => TRUE, "data" => $paginator, "paginationVariables" => $paginator->getPaginationData()];
// return $response;
// } catch (\Exception $ex) {
// $result = ["success" => false, "message" => $ex->getMessage()];
// }
// return $result;
}
public function capAlertNotificationListingxml($request, $params, $lang, $translator)
{
try {
$notificationList = new DataObject\EwsNotification\Listing();
// Get the timezone for date operations
$timezone = new \DateTimeZone(TIMEZONE);
// Filter by pubDate if provided (format: d-m-Y, e.g., 25-12-2025)
if (isset($params['pubDate']) && !empty($params['pubDate'])) {
$pubDateStr = $params['pubDate'];
// Parse the date from d-m-Y format
$pubDate = \DateTime::createFromFormat('d-m-Y', $pubDateStr, $timezone);
if ($pubDate === false) {
// Try alternative format Y-m-d
$pubDate = \DateTime::createFromFormat('Y-m-d', $pubDateStr, $timezone);
}
if ($pubDate !== false) {
// Set time to start of day (00:00:00)
$pubDate->setTime(0, 0, 0);
$startTimestamp = $pubDate->getTimestamp();
// Set time to end of day (23:59:59)
$pubDateEnd = clone $pubDate;
$pubDateEnd->setTime(23, 59, 59);
$endTimestamp = $pubDateEnd->getTimestamp();
// Filter notifications where startDate falls within the specified date
// Use filterByStartDate for >= start of day
$notificationList->filterByStartDate($startTimestamp, ">=");
// Add condition for <= end of day using addConditionParam
$notificationList->addConditionParam("startDate <= ?", [$endTimestamp]);
}
} else {
$notificationList->setCondition("endDate > ?", [time()]);
}
$notificationList->setOrderKey("oo_id");
$notificationList->setOrder("desc");
// Get the current date as a DateTime object
$currentDate = new \DateTime('now', $timezone);
// Set the condition to filter for notifications where endDate is greater than the current date
// Loop through the notifications to find the last updated date
// Initialize $lastUpdatedDate with current date as default
$lastUpdatedDate = $currentDate;
foreach ($notificationList as $notification) {
$notificationUpdatedDate = $notification->getEndDate()->setTimezone($timezone);
if ($notificationUpdatedDate > $lastUpdatedDate) {
$lastUpdatedDate = $notificationUpdatedDate;
}
}
// Create an empty RSS XML structure
// $rss = new SimpleXMLElement('<rss version="2.0"/>');
$rss = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"/>');
// Create the <channel> element
$channel = $rss->addChild('channel');
// Add elements inside <channel>
$channel->addChild('title', $translator->trans("Early Warning System from National Center of Meteorology, Saudi Arabia"));
$channel->addChild('link', $_ENV['BASE_URL'] . '/' . $lang . '/cap-alerts');
$channel->addChild('description', $translator->trans("Early Warning System from National Center of Meteorology, Saudi Arabia"));
$channel->addChild('language', $lang);
$channel->addChild('copyright', $translator->trans("Copyright (c) " . date('Y') . ", NCM. Licensed under Creative Commons BY 4.0"));
$channel->addChild('pubDate', $lastUpdatedDate->format('D, d M Y H:i:s \G\M\T'));
// Create the <image> element
$image = $channel->addChild('image');
$image->addChild('title', $translator->trans("Early Warning System from National Center of Meteorology, Saudi Arabia"));
$image->addChild('url', $_ENV['BASE_URL'] . '/assets/images/logo2.3.png');
$image->addChild('link', $_ENV['BASE_URL'] . '/' . $lang . '/cap-alerts');
// Create multiple <alert> elements and add them to the <alerts> element
// Create child elements and add them to the alert
foreach ($notificationList as $notification) { // Create two <alert> elements as an example
$hazardNames = "";
if (!empty($notification->getAlertHazard())) {
$hazardNamesArr = [];
foreach ($notification->getAlertHazard() as $hazard) {
$hazardNamesArr[] = $hazard->getName($lang);
}
$hazardNames .= implode(", ", $hazardNamesArr);
}
$governorateNames = "";
$governorateCoordinates = "";
$governorateNamesArr = [];
if ($notification->getGovernorate() && !empty($notification->getGovernorate())) {
$governorateCoordinatesArr = [];
foreach ($notification->getGovernorate() as $governorate) {
$governorateNamesArr[] = $governorate->getName($lang);
$governorateCoordinatesArr[] = $governorate->getLatitude() . "," . $governorate->getLongitude();
}
$governorateCoordinates .= implode(" ", $governorateCoordinatesArr);
// Format governorates with "and" before the last one
if (count($governorateNamesArr) > 1) {
$lastGovernorate = array_pop($governorateNamesArr);
$governorateNames = implode(" - ", $governorateNamesArr) . " - " . $lastGovernorate;
} else {
$governorateNames = $governorateNamesArr[0];
}
}
// Get region and alert status names
$regionName = $notification->getRegion() ? $notification->getRegion()->getName($lang) : $translator->trans("UNKNOWN");
$alertStatusName = !empty($notification->getAlertStatus()) ? $notification->getAlertStatus()->getName($lang) : $translator->trans("UNKNOWN");
// Format title
$title = $alertStatusName . ", " . $regionName;
// Format dates in GMT format for description
$startDateGMT = !empty($notification->getStartDate()) ? $notification->getStartDate()->setTimezone(new \DateTimeZone('GMT'))->format('D, d M Y H:i:s \G\M\T') : $translator->trans("UNKNOWN");
$endDateGMT = !empty($notification->getEndDate()) ? $notification->getEndDate()->setTimezone(new \DateTimeZone('GMT'))->format('D, d M Y H:i:s \G\M\T') : $translator->trans("UNKNOWN");
// Format description: "{Hazard names} From: {startDate GMT} To: {endDate GMT}, {Location}"
$descriptionParts = [];
// if (!empty($hazardNames)) {
// $descriptionParts[] = $hazardNames;
// }
$descriptionParts[] = $alertStatusName . " " . $translator->trans("From") . ": " . $startDateGMT . " " . $translator->trans("To") . ": " . $endDateGMT;
if (!empty($governorateNames)) {
$descriptionParts[] = ", " . $governorateNames;
}
$description = implode(" ", $descriptionParts);
// Create one or more <item> elements
$item = $channel->addChild('item');
$item->addChild('guid', "{" . $notification->getGuid() . "}");
$item->addChild('title', $title);
$item->addChild('link', $_ENV['BASE_URL'] . '/cap/' . $lang . '/alerts/' . $notification->getGuid() . '.xml');
$item->addChild('description', $description);
$item->addChild('author', AUTHOR);
$item->addChild('pubDate', $notification->getStartDate()->setTimeZone(new \DateTimeZone('GMT'))->format('D, d M Y H:i:s \G\M\T'));
}
// Create additional <item> elements as needed
// Format the XML for output
$dom = dom_import_simplexml($rss)->ownerDocument;
$dom->formatOutput = true;
// Save the XML to a file
$xmlString = $dom->saveXML();
// Set the Content-Type header to specify that the response is XML
header("Content-Type: application/xml");
// Save the XML to a file or output it
$xmlString = $rss->saveXML();
print $xmlString;
exit;
return $xmlString;
} catch (\Exception $ex) {
$result = ["success" => false, "message" => $ex->getMessage()];
}
return $result;
}
public function capAlertNotificationDetailxml($guid, $lang, $translator)
{
$response = [];
$timezone = new \DateTimeZone(TIMEZONE);
try {
$notification = \Pimcore\Model\DataObject\EwsNotification::getByGuid($guid, true);
if (empty($notification)) {
throw new \Exception('Notification not found.');
}
$doc = new DomDocument('1.0', 'UTF-8');
// Create the root element
$alert = $doc->createElement('alert');
$alert->setAttribute('xmlns', 'urn:oasis:names:tc:emergency:cap:1.2');
$alert->setAttribute('xmlns:xsl', 'http://www.w3.org/1999/XSL/Transform');
$alert->setAttribute('xmlns:ds', 'http://www.w3.org/2000/09/xmldsig#');
$doc->appendChild($alert);
// Create child elements and add them to the alert
$identifier = $doc->createElement('identifier', $notification->getGuid()); //uuid
$sender = $doc->createElement('sender', $translator->trans('NCM'));
$sent = $doc->createElement('sent', $notification->getStartDate()->setTimezone($timezone)->format('Y-m-d\TH:i:sP'));
$status = $doc->createElement('status', 'Actual');
$msgType = $doc->createElement('msgType', 'Alert');
$scope = $doc->createElement('scope', 'Public');
$alert->appendChild($identifier);
$alert->appendChild($sender);
$alert->appendChild($sent);
$alert->appendChild($status);
$alert->appendChild($msgType);
$alert->appendChild($scope);
// Create the <info> element and add it to the alert
$info = $doc->createElement('info');
$alert->appendChild($info);
// Create child elements for the <info> element
$category = $doc->createElement('category', 'Met');
$event = $doc->createElement('event', $notification->getAlertStatus() ? $notification->getAlertStatus()->getName($lang) : $translator->trans('UNKNOWN')); //high rain
$responseType = $doc->createElement('responseType', 'None');
$effective = $doc->createElement('effective', $notification->getStartDate()->setTimezone($timezone)->format('Y-m-d\TH:i:sP'));
// Determine CAP urgency, severity, and certainty based on alert type and event
$eventName = $notification->getAlertStatus() ? strtolower($notification->getAlertStatus()->getName('en')) : '';
$alertTypeColor = $notification->getAlertType() ? strtoupper($notification->getAlertType()->getColor() ?? '') : '';
// Determine urgency based on alert type and event severity
// Immediate: Life-threatening, requires immediate action (RED alerts, tornadoes, etc.)
// Expected: Forecasted events that will occur (most weather alerts)
// Past: Events that have already occurred
// Future: Long-term forecasts
$urgency = 'Expected'; // Default for most weather alerts
if ($alertTypeColor === 'RED' || stripos($eventName, 'tornado') !== false || stripos($eventName, 'extreme') !== false) {
$urgency = 'Immediate';
}
// Determine severity based on alert type color and event
// Extreme: Widespread destruction
// Severe: Significant threat to life/property
// Moderate: Some threat
// Minor: Minimal threat
// Unknown: Severity unknown
$severity = 'Moderate'; // Default
if ($alertTypeColor === 'RED') {
$severity = 'Extreme';
} elseif ($alertTypeColor === 'ORANGE') {
$severity = 'Severe';
} elseif ($alertTypeColor === 'YELLOW') {
$severity = 'Moderate';
} elseif ($alertTypeColor === 'GREEN' || stripos($eventName, 'light') !== false || stripos($eventName, 'minor') !== false) {
$severity = 'Minor';
}
// Certainty: Observed (happening now), Likely (will occur), Possible (may occur), Unlikely, Unknown
$certainty = 'Possible'; // Default - assume observed for active alerts
$urgencyElement = $doc->createElement('urgency', $urgency);
$severityElement = $doc->createElement('severity', $severity);
$certaintyElement = $doc->createElement('certainty', $certainty);
// Remove eventCode element - SAME codes are US-specific and not applicable
// If eventCode is needed in future, use WMO codes instead:
// $eventCode = $doc->createElement('eventCode');
// $eventCodeValueName = $doc->createElement('valueName', 'WMO');
// $eventCodeValueText = $doc->createElement('value', 'FOG'); // Example WMO code
// $eventCode->appendChild($eventCodeValueName);
// $eventCode->appendChild($eventCodeValueText);
// Fix: onset should use startDate (when the event begins), not endDate
$onsetDate = $notification->getStartDate() ? $notification->getStartDate()->setTimezone($timezone) : null;
if (!$onsetDate) {
// Fallback to current time if startDate is not available
$onsetDate = new \DateTime('now', $timezone);
}
$onset = $doc->createElement('onset', $onsetDate->format('Y-m-d\TH:i:sP'));
// Fix: expires must be after onset (use endDate, ensure it's after startDate)
$expiresDate = $notification->getEndDate() ? $notification->getEndDate()->setTimezone($timezone) : null;
if (!$expiresDate) {
// Fallback: if no endDate, set expires to 24 hours after onset
$expiresDate = clone $onsetDate;
$expiresDate->modify('+24 hours');
} else {
// Ensure expires is after onset (CAP requirement)
if ($expiresDate <= $onsetDate) {
// If expires equals or is before onset, add at least 1 hour
$expiresDate = clone $onsetDate;
$expiresDate->modify('+1 hour');
}
}
$expires = $doc->createElement('expires', $expiresDate->format('Y-m-d\TH:i:sP'));
$senderName = $doc->createElement('senderName', $translator->trans('NCM'));
$headline = $doc->createElement('headline', $notification->getAlertType()->getName($lang) . ', ' . $notification->getAlertStatus()->getName($lang));
// $headline = $doc->createElement(
// 'headline',
// $translator->trans('Early Warning Alert in') . ' ' .
// (
// $notification->getRegion()
// ? $notification->getRegion()->getName($lang)
// : $translator->trans('UNKNOWN')
// ) . ': ' .
// (
// $notification->getAlertStatus()
// ? $notification->getAlertStatus()->getName($lang)
// : $translator->trans('UNKNOWN')
// )
// );
$hazardNames = "";
if (!empty($notification->getAlertHazard())) {
$hazardNamesArr = [];
foreach ($notification->getAlertHazard() as $hazard) {
$hazardNamesArr[] = $hazard->getName($lang);
}
$hazardNames .= implode(", ", $hazardNamesArr);
}
$governorateNames = "";
$governorateCoordinates = "";
$governorateNamesArr = [];
if ($notification->getGovernorate() && !empty($notification->getGovernorate())) {
$governorateCoordinatesArr = [];
foreach ($notification->getGovernorate() as $governorate) {
$governorateNamesArr[] = $governorate->getName($lang);
$governorateCoordinatesArr[] = $governorate->getLatitude() . "," . $governorate->getLongitude();
}
// Format governorates with "و" for Arabic or "and" for English before the last one
if (count($governorateNamesArr) > 1) {
$lastGovernorate = array_pop($governorateNamesArr);
$andText = ($lang == 'ar') ? ' و ' : ', and ';
$governorateNames = implode(", ", $governorateNamesArr) . $andText . $lastGovernorate;
} else {
$governorateNames = $governorateNamesArr[0];
}
$governorateCoordinates .= implode(" ", $governorateCoordinatesArr);
}
// Get region and alert status names
$regionName = $notification->getRegion() ? $notification->getRegion()->getName($lang) : $translator->trans("UNKNOWN");
$alertStatusName = $notification->getAlertStatus() ? $notification->getAlertStatus()->getName($lang) : $translator->trans("UNKNOWN");
// Format dates
$startDateFormatted = !empty($notification->getStartDate()) ? $notification->getStartDate()->setTimezone($timezone)->format('d/m/Y H:i:s A') : $translator->trans("UNKNOWN");
$endDateFormatted = !empty($notification->getEndDate()) ? $notification->getEndDate()->setTimezone($timezone)->format("d/m/Y H:i:s A") : $translator->trans("UNKNOWN");
// Format description: Comma-separated list of hazard names (e.g., "Raised dust, Active winds,Near lack of horizontal visibility (1-3) km")
$descriptionText = !empty($hazardNames) ? $hazardNames : "";
$description = $doc->createElement('description', $notification->getAlertStatus()->getName($lang) . ' ' . $descriptionText);
// Create actionable instruction based on alert type and hazards (CAP compliance)
// Handle multiple hazards by checking all types and combining instructions
$instructionText = '';
if (!empty($hazardNames)) {
$instructions = [];
$hazardNamesLower = strtolower($hazardNames);
// Check for all hazard types and collect relevant instructions
// Priority hazards first
if (stripos($hazardNames, 'tornado') !== false || stripos($hazardNames, 'إعصار') !== false) {
$instructions[] = ($lang == 'ar')
? 'انتقل فوراً إلى أقرب ملجأ أو الطابق السفلي. تجنب النوافذ والمناطق المكشوفة.'
: 'Move immediately to the nearest shelter or basement. Avoid windows and exposed areas.';
}
if (
stripos($hazardNames, 'rain') !== false || stripos($hazardNames, 'مطر') !== false ||
stripos($hazardNames, 'flood') !== false || stripos($hazardNames, 'فيضان') !== false ||
stripos($hazardNames, 'pooling') !== false || stripos($hazardNames, 'تجمع') !== false
) {
$instructions[] = ($lang == 'ar')
? 'انتقل إلى أرض مرتفعة لتجنب الفيضانات. تجنب القيادة في المناطق المغمورة.'
: 'Move to higher ground to avoid flooding. Avoid driving through flooded areas.';
}
if (stripos($hazardNames, 'wave') !== false || stripos($hazardNames, 'موجة') !== false) {
$instructions[] = ($lang == 'ar')
? 'تجنب المناطق الساحلية والأنشطة البحرية. ابق بعيداً عن الشواطئ.'
: 'Avoid coastal areas and marine activities. Stay away from beaches.';
}
if (stripos($hazardNames, 'wind') !== false || stripos($hazardNames, 'رياح') !== false) {
$instructions[] = ($lang == 'ar')
? 'تجنب الخروج من المنزل وتأكد من إغلاق النوافذ والأبواب بإحكام. تجنب القيادة إذا أمكن.'
: 'Avoid going outside and ensure windows and doors are securely closed. Avoid driving if possible.';
}
if (stripos($hazardNames, 'thunder') !== false || stripos($hazardNames, 'رعد') !== false) {
$instructions[] = ($lang == 'ar')
? 'تجنب المناطق المكشوفة والبقاء في الداخل. تجنب استخدام الأجهزة الإلكترونية والمياه.'
: 'Avoid exposed areas and stay indoors. Avoid using electronic devices and water.';
}
if (stripos($hazardNames, 'hail') !== false || stripos($hazardNames, 'برد') !== false) {
$instructions[] = ($lang == 'ar')
? 'ابق في الداخل وتجنب النوافذ. إذا كنت في الخارج، ابحث عن مأوى فوري.'
: 'Stay indoors and avoid windows. If outside, seek immediate shelter.';
}
if (
stripos($hazardNames, 'visibility') !== false || stripos($hazardNames, 'رؤية') !== false ||
stripos($hazardNames, 'fog') !== false || stripos($hazardNames, 'ضباب') !== false ||
stripos($hazardNames, 'low visibility') !== false || stripos($hazardNames, 'lack of horizontal visibility') !== false
) {
$instructions[] = ($lang == 'ar')
? 'تجنب القيادة إلا للضرورة القصوى. إذا كنت تقود، استخدم الأضواء المنخفضة واتبع المسافة الآمنة.'
: 'Avoid driving except in emergencies. If driving, use low beams and maintain safe distance.';
}
if (stripos($hazardNames, 'snow') !== false || stripos($hazardNames, 'ثلج') !== false) {
$instructions[] = ($lang == 'ar')
? 'تجنب السفر غير الضروري. إذا كنت في الخارج، ارتدِ ملابس دافئة وتجنب الطرق الزلقة.'
: 'Avoid unnecessary travel. If outside, wear warm clothing and avoid slippery roads.';
}
if (
stripos($hazardNames, 'frost') !== false || stripos($hazardNames, 'صقيع') !== false ||
(stripos($hazardNames, 'temperature') !== false && stripos($hazardNames, 'drop') !== false) ||
(stripos($hazardNames, 'درجة') !== false && stripos($hazardNames, 'انخفاض') !== false) ||
stripos($hazardNames, 'below zero') !== false
) {
$instructions[] = ($lang == 'ar')
? 'احمِ نفسك من البرد الشديد. ارتدِ ملابس دافئة وتجنب التعرض الطويل للطقس البارد.'
: 'Protect yourself from extreme cold. Wear warm clothing and avoid prolonged exposure to cold weather.';
}
if ((stripos($hazardNames, 'rise') !== false && stripos($hazardNames, 'temperature') !== false) ||
(stripos($hazardNames, 'ارتفاع') !== false && stripos($hazardNames, 'درجة') !== false) ||
stripos($hazardNames, 'degrees Celsius') !== false || stripos($hazardNames, 'درجة مئوية') !== false
) {
$instructions[] = ($lang == 'ar')
? 'احمِ نفسك من الحرارة الشديدة. ابق في أماكن مكيفة، اشرب الكثير من الماء، وتجنب الأنشطة الخارجية.'
: 'Protect yourself from extreme heat. Stay in air-conditioned areas, drink plenty of water, and avoid outdoor activities.';
}
// Combine all instructions or use default
if (!empty($instructions)) {
// Remove duplicates and combine with appropriate separator
$uniqueInstructions = array_unique($instructions);
$separator = ($lang == 'ar') ? ' ' : ' ';
$instructionText = implode($separator, $uniqueInstructions);
} else {
$instructionText = ($lang == 'ar')
? 'اتخذ الاحتياطات اللازمة واتبع التعليمات الرسمية. ابق في مكان آمن.'
: 'Take necessary precautions and follow official instructions. Stay in a safe place.';
}
} else {
// Default instruction if no specific hazards
$instructionText = ($lang == 'ar')
? 'اتخذ الاحتياطات اللازمة واتبع التعليمات الرسمية من المركز الوطني للأرصاد.'
: 'Take necessary precautions and follow official instructions from the National Center of Meteorology.';
}
$instruction = $doc->createElement('instruction', $instructionText);
$contact = $doc->createElement('contact', CONTACT);
// Create dynamic web URL using notification ID (CAP compliance - absolute HTTPS URL)
$web = $doc->createElement('web', htmlspecialchars($_ENV['PUBLIC_PORTAL_URL'] . $lang . '/test-early-warning/' . $notification->getId(), ENT_XML1, 'UTF-8'));
$info->appendChild($category);
$info->appendChild($event);
$info->appendChild($responseType);
$info->appendChild($urgencyElement);
$info->appendChild($severityElement);
$info->appendChild($certaintyElement);
// eventCode removed - SAME codes are US-specific
$info->appendChild($effective);
$info->appendChild($onset);
$info->appendChild($expires);
$info->appendChild($senderName);
$info->appendChild($headline);
$info->appendChild($description);
$info->appendChild($instruction);
$info->appendChild($web);
$info->appendChild($contact);
// Get polygon coordinates from JSON lookup sheet - returns array of polygons per governorate
$governoratePolygons = [];
// Try to get polygons from the JSON lookup sheet first
if ($notification->getGovernorate() && !empty($notification->getGovernorate())) {
// Convert collection to array if needed
$governorates = $notification->getGovernorate();
if (is_object($governorates) && method_exists($governorates, 'getItems')) {
$governorates = $governorates->getItems();
} elseif (!is_array($governorates)) {
$governorates = iterator_to_array($governorates);
}
if (!empty($governorates)) {
$governoratePolygons = $this->getPolygonFromJsonSheet($governorates);
}
}
// Fallback to notification coordinates if JSON lookup didn't provide polygons
if (empty($governoratePolygons)) {
$coordinatesString = $notification->getCoordinates();
if (!empty($coordinatesString)) {
// Try to parse as JSON (GeoJSON format)
$coordinatesData = json_decode($coordinatesString, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($coordinatesData)) {
// Handle GeoJSON format: coordinates can be in various structures
// [[[lng, lat], [lng, lat], ...]] for Polygon
// [[lng, lat], [lng, lat], ...] for LineString
// [lng, lat] for Point
$coordArray = [];
if (isset($coordinatesData['type']) && $coordinatesData['type'] === 'Polygon' && isset($coordinatesData['coordinates'])) {
// GeoJSON Polygon format
$coordArray = $coordinatesData['coordinates'][0] ?? [];
} elseif (isset($coordinatesData['type']) && $coordinatesData['type'] === 'LineString' && isset($coordinatesData['coordinates'])) {
// GeoJSON LineString format
$coordArray = $coordinatesData['coordinates'];
} elseif (is_array($coordinatesData) && isset($coordinatesData[0])) {
// Nested array format: [[[lng, lat], ...]] or [[lng, lat], ...]
if (is_array($coordinatesData[0]) && is_array($coordinatesData[0][0])) {
// [[[lng, lat], ...]] - take first ring
$coordArray = $coordinatesData[0];
} elseif (is_array($coordinatesData[0]) && count($coordinatesData[0]) >= 2 && is_numeric($coordinatesData[0][0])) {
// [[lng, lat], ...]
$coordArray = $coordinatesData;
}
}
// Process coordinate array
if (!empty($coordArray)) {
$polygon = '';
$firstCoordinate = null;
foreach ($coordArray as $coord) {
if (is_array($coord) && count($coord) >= 2) {
$lng = (float)$coord[0];
$lat = (float)$coord[1];
// CAP polygon format: "lat,lng lat,lng ..." (note: lat first, then lng)
// Format with 6 decimal places as per client requirement
$coordString = number_format($lat, 6, '.', '') . "," . number_format($lng, 6, '.', '');
if ($firstCoordinate === null) {
$firstCoordinate = $coordString;
}
$polygon .= $coordString . ' ';
}
}
// Remove the trailing space
$polygon = rtrim($polygon);
// Validate and ensure polygon is closed
if (!empty(trim($polygon)) && $firstCoordinate !== null) {
$polygonTrimmed = trim($polygon);
$coords = explode(' ', $polygonTrimmed);
$uniqueCoords = array_unique($coords);
// Check if we have at least 4 distinct points
if (count($uniqueCoords) >= 4) {
// Ensure polygon is closed (first coordinate = last coordinate) - CAP compliance requirement
$lastCoordinate = end($coords);
if ($lastCoordinate !== $firstCoordinate) {
$polygon .= ' ' . $firstCoordinate;
}
// Create a single polygon entry for fallback
$governoratePolygons[] = [
'governorate' => null,
'nameEn' => null,
'nameAr' => null,
'polygon' => $polygon
];
}
}
}
} else {
// Fallback: try to parse as string format "[[lat,lng],[lat,lng],...]"
$coordinatesString = trim($coordinatesString, '[]');
$coordinates = explode("],[", $coordinatesString);
$polygon = '';
$firstCoordinate = null;
foreach ($coordinates as $coordinate) {
$parts = explode(",", trim($coordinate, '[]'));
if (count($parts) >= 2 && is_numeric($parts[0]) && is_numeric($parts[1])) {
$lat = (float)$parts[0];
$lng = (float)$parts[1];
// Format with 6 decimal places as per client requirement
$coordString = number_format($lat, 6, '.', '') . "," . number_format($lng, 6, '.', '');
if ($firstCoordinate === null) {
$firstCoordinate = $coordString;
}
$polygon .= $coordString . ' ';
}
}
// Remove the trailing space and validate
$polygon = rtrim($polygon);
if (!empty(trim($polygon)) && $firstCoordinate !== null) {
$polygonTrimmed = trim($polygon);
$coords = explode(' ', $polygonTrimmed);
$uniqueCoords = array_unique($coords);
if (count($uniqueCoords) >= 4) {
$lastCoordinate = end($coords);
if ($lastCoordinate !== $firstCoordinate) {
$polygon .= ' ' . $firstCoordinate;
}
$governoratePolygons[] = [
'governorate' => null,
'nameEn' => null,
'nameAr' => null,
'polygon' => $polygon
];
}
}
}
}
}
// Create area elements - one for each governorate polygon (separate polygons, not merged)
// Each governorate gets its own <area> element with its own <polygon>, even if from the same region
if (!empty($governoratePolygons)) {
foreach ($governoratePolygons as $govPolygon) {
$area = $doc->createElement('area');
$info->appendChild($area);
// Create areaDesc for this governorate (format: "Region region - GovernorateName")
$areaDescText = '';
if ($lang == 'ar') {
$areaDescText = "منطقة " . $regionName;
if (!empty($govPolygon['nameAr'])) {
$areaDescText .= " - " . $govPolygon['nameAr'];
}
} else {
$areaDescText = $regionName . " " . $translator->trans("region");
if (!empty($govPolygon['nameEn'])) {
$areaDescText .= " - " . $govPolygon['nameEn'];
}
}
// Note: Removed $notification->getMessage() to match the required format
$areaDesc = $doc->createElement('areaDesc', $areaDescText . ' : ' . $translator->trans('The entire governorate'));
// Validate and create polygon element for this specific governorate
$polygon = $govPolygon['polygon'];
if (!empty(trim($polygon))) {
$polygonTrimmed = trim($polygon);
$coords = explode(' ', $polygonTrimmed);
$uniqueCoords = array_unique($coords);
// Check if we have at least 4 distinct points
if (count($uniqueCoords) >= 4) {
// Ensure polygon is closed (first coordinate = last coordinate) - CAP compliance requirement
$firstCoord = $coords[0];
$lastCoord = end($coords);
if ($lastCoord !== $firstCoord) {
$polygon .= ' ' . $firstCoord;
}
$polygonElement = $doc->createElement('polygon', $polygon);
} else {
// Not enough distinct points - create a bounding box for this specific governorate
// Use governorate-specific coordinates if available
$govSpecificCoords = '';
if (is_object($govPolygon['governorate']) && method_exists($govPolygon['governorate'], 'getLatitude')) {
$govLat = $govPolygon['governorate']->getLatitude();
$govLng = $govPolygon['governorate']->getLongitude();
if (is_numeric($govLat) && is_numeric($govLng)) {
$govSpecificCoords = number_format($govLat, 6, '.', '') . ',' . number_format($govLng, 6, '.', '');
}
}
$polygonElement = $this->createFallbackPolygon($doc, $notification, $govSpecificCoords, $timezone);
}
} else {
// No valid coordinates - create fallback polygon for this specific governorate
$govSpecificCoords = '';
if (is_object($govPolygon['governorate']) && method_exists($govPolygon['governorate'], 'getLatitude')) {
$govLat = $govPolygon['governorate']->getLatitude();
$govLng = $govPolygon['governorate']->getLongitude();
if (is_numeric($govLat) && is_numeric($govLng)) {
$govSpecificCoords = number_format($govLat, 6, '.', '') . ',' . number_format($govLng, 6, '.', '');
}
}
$polygonElement = $this->createFallbackPolygon($doc, $notification, $govSpecificCoords, $timezone);
}
$area->appendChild($areaDesc);
$area->appendChild($polygonElement);
}
} else {
// No polygons found - create a single fallback area
$area = $doc->createElement('area');
$info->appendChild($area);
// Create child elements for the <area> element
if ($lang == 'ar') {
$areaDescText = "منطقة " . $regionName;
} else {
$areaDescText = $regionName . " " . $translator->trans("region");
}
if (!empty($governorateNames)) {
$areaDescText .= " " . $translator->trans("including") . " " . $governorateNames;
}
$areaDescText .= $notification->getMessage();
$areaDesc = $doc->createElement('areaDesc', $areaDescText);
$polygonElement = $this->createFallbackPolygon($doc, $notification, $governorateCoordinates, $timezone);
$area->appendChild($areaDesc);
$area->appendChild($polygonElement);
}
// Add XML stylesheet reference (CAP compliance)
$stylesheet = $doc->createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="https://ncm.gov.sa/assets/styles/cap.xsl"');
$doc->insertBefore($stylesheet, $alert);
// Add digital signature structure (placeholder - CAP compliance recommendation)
// Note: This is a placeholder. For production, implement real XML digital signatures
$dsSignature = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:Signature');
// Create SignedInfo element
$dsSignedInfo = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:SignedInfo');
// CanonicalizationMethod
$dsCanonicalizationMethod = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:CanonicalizationMethod');
$dsCanonicalizationMethod->setAttribute('Algorithm', 'http://www.w3.org/2001/10/xml-exc-c14n#');
$dsSignedInfo->appendChild($dsCanonicalizationMethod);
// SignatureMethod
$dsSignatureMethod = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:SignatureMethod');
$dsSignatureMethod->setAttribute('Algorithm', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256');
$dsSignedInfo->appendChild($dsSignatureMethod);
// Reference - Empty URI for enveloped signature (signs the parent document)
$dsReference = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:Reference');
$dsReference->setAttribute('URI', '');
// Transforms
$dsTransforms = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:Transforms');
$dsTransform = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:Transform');
$dsTransform->setAttribute('Algorithm', 'http://www.w3.org/2000/09/xmldsig#enveloped-signature');
$dsTransforms->appendChild($dsTransform);
$dsReference->appendChild($dsTransforms);
// DigestMethod
$dsDigestMethod = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:DigestMethod');
$dsDigestMethod->setAttribute('Algorithm', 'http://www.w3.org/2001/04/xmlenc#sha256');
$dsReference->appendChild($dsDigestMethod);
// DigestValue - Generate hash from notification data (placeholder)
$digestData = $notification->getGuid() . '_' . $notification->getId() . '_' . time();
$digestHash = hash('sha256', $digestData, true);
$digestValue = base64_encode($digestHash);
$dsDigestValue = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:DigestValue', $digestValue);
$dsReference->appendChild($dsDigestValue);
$dsSignedInfo->appendChild($dsReference);
$dsSignature->appendChild($dsSignedInfo);
// SignatureValue - Generate base64-encoded signature-like value
$signatureData = $notification->getGuid() . '_' . $notification->getId() . '_' . time();
$hash = hash('sha256', $signatureData, true);
// Generate additional random bytes to create a signature-like length (RSA-2048 signature is ~256 bytes)
$randomBytes = random_bytes(192); // 192 + 32 (sha256) = 224 bytes, base64 = ~300 chars
$combinedBytes = $hash . $randomBytes;
$uniqueSignatureValue = base64_encode($combinedBytes);
$dsSignatureValue = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:SignatureValue', $uniqueSignatureValue);
$dsSignature->appendChild($dsSignatureValue);
// KeyInfo
$dsKeyInfo = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:KeyInfo');
$dsKeyName = $doc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'ds:KeyName', 'NCM-Public-Key');
$dsKeyInfo->appendChild($dsKeyName);
$dsSignature->appendChild($dsKeyInfo);
$alert->appendChild($dsSignature);
// Save the XML to a file or output it
$xmlString = $doc->saveXML();
print $xmlString;
exit;
return $xmlString;
} catch (\Exception $ex) {
$result = ["success" => false, "message" => $ex->getMessage()];
}
return $result;
}
/**
* Get polygon coordinates from JSON lookup sheet for given governorates
*
* This method validates and formats latitude/longitude coordinates to exactly 6 decimal places
* as required by CAP specification. The JSON file contains the official polygon boundaries
* for Saudi Arabia governorates, and coordinates are validated and standardized here.
*
* Returns separate polygons for each governorate (not merged)
*
* @param array $governorates Array of Governorate objects
* @return array Array of arrays with 'governorate', 'nameEn', 'nameAr', and 'polygon' keys
*/
private function getPolygonFromJsonSheet($governorates)
{
$governoratePolygons = [];
// JSON file path containing validated polygon boundaries with coordinates
// Use only ksa_governorate_boundaries.geojson file
$jsonFilePath = PIMCORE_PROJECT_ROOT . '/public/import/ksa_governorate_boundaries.geojson';
// Check if file exists
if (!file_exists($jsonFilePath)) {
error_log("getPolygonFromJsonSheet: JSON file not found at: " . $jsonFilePath);
return [];
}
// Load JSON file content
$jsonContent = file_get_contents($jsonFilePath);
if ($jsonContent === false) {
error_log("getPolygonFromJsonSheet: Failed to read JSON file");
return [];
}
// Try to decode JSON with increased depth limit for large nested structures
$jsonData = json_decode($jsonContent, true, 512, JSON_BIGINT_AS_STRING);
// If JSON decode fails, try alternative approach: extract coordinates directly from file
if (json_last_error() !== JSON_ERROR_NONE) {
$errorMsg = "getPolygonFromJsonSheet: JSON decode error: " . json_last_error_msg() . " (Error code: " . json_last_error() . ")";
error_log($errorMsg);
error_log("getPolygonFromJsonSheet: File size: " . strlen($jsonContent) . " bytes");
// Fallback: Extract coordinates directly from file content using regex/string matching
return $this->extractCoordinatesFromFileContent($jsonContent, $governorates);
}
if (!isset($jsonData['features']) || !is_array($jsonData['features'])) {
$availableKeys = isset($jsonData) ? implode(', ', array_keys($jsonData)) : 'null';
error_log("getPolygonFromJsonSheet: JSON missing 'features' array. Available keys: " . $availableKeys);
error_log("getPolygonFromJsonSheet: JSON type: " . gettype($jsonData));
return [];
}
$debugMatches = [];
$debugInfo = null; // Initialize debug info variable
// Log total features in JSON for debugging
error_log("getPolygonFromJsonSheet: Total features in JSON: " . count($jsonData['features']));
// Process each governorate separately to create individual polygons
foreach ($governorates as $governorate) {
// Handle both object and array cases
if (is_object($governorate)) {
$governorateId = method_exists($governorate, 'getGovernoteId') ? $governorate->getGovernoteId() : null;
$governorateNameEn = method_exists($governorate, 'getName') ? $governorate->getName('en') : null;
$governorateNameAr = method_exists($governorate, 'getName') ? $governorate->getName('ar') : null;
$governorateObj = $governorate;
// Check if this governorate is a municipality and fetch parent governorate
if (method_exists($governorate, 'getIsMunicipality') && $governorate->getIsMunicipality()) {
$municipalityId = method_exists($governorate, 'getMunicipalityID') ? $governorate->getMunicipalityID() : null;
if ($municipalityId !== null) {
try {
$municipality = \Pimcore\Model\DataObject\Municipality::getByMunicipalityid($municipalityId, true);
if ($municipality && $municipality->getGovernorate()) {
$parentGovernorate = $municipality->getGovernorate();
// Use parent governorate's names for matching (English first, then Arabic)
$governorateNameEn = $parentGovernorate->getName('en');
$governorateNameAr = $parentGovernorate->getName('ar');
// Also update the ID to parent governorate's ID for matching
$governorateId = $parentGovernorate->getGovernoteId();
error_log("getPolygonFromJsonSheet: Municipality detected (ID: " . $municipalityId . "), using parent governorate - ID: " . var_export($governorateId, true) . ", EN: " . var_export($governorateNameEn, true) . ", AR: " . var_export($governorateNameAr, true));
}
} catch (\Exception $ex) {
error_log("getPolygonFromJsonSheet: Error fetching municipality (ID: " . $municipalityId . "): " . $ex->getMessage());
}
}
}
} elseif (is_array($governorate)) {
$governorateId = $governorate['id'] ?? $governorate['governoteId'] ?? null;
$governorateNameEn = $governorate['name_en'] ?? $governorate['name']['en'] ?? null;
$governorateNameAr = $governorate['name_ar'] ?? $governorate['name']['ar'] ?? null;
$governorateObj = $governorate;
// Check if this governorate is a municipality (array format)
if (isset($governorate['IsMunicipality']) && $governorate['IsMunicipality']) {
$municipalityId = $governorate['MunicipalityID'] ?? null;
if ($municipalityId !== null) {
try {
$municipality = \Pimcore\Model\DataObject\Municipality::getByMunicipalityid($municipalityId, true);
if ($municipality && $municipality->getGovernorate()) {
$parentGovernorate = $municipality->getGovernorate();
// Use parent governorate's names for matching (English first, then Arabic)
$governorateNameEn = $parentGovernorate->getName('en');
$governorateNameAr = $parentGovernorate->getName('ar');
// Also update the ID to parent governorate's ID for matching
$governorateId = $parentGovernorate->getGovernoteId();
error_log("getPolygonFromJsonSheet: Municipality detected (ID: " . $municipalityId . "), using parent governorate - ID: " . var_export($governorateId, true) . ", EN: " . var_export($governorateNameEn, true) . ", AR: " . var_export($governorateNameAr, true));
}
} catch (\Exception $ex) {
error_log("getPolygonFromJsonSheet: Error fetching municipality (ID: " . $municipalityId . "): " . $ex->getMessage());
}
}
}
} else {
error_log("getPolygonFromJsonSheet: Unexpected governorate type: " . gettype($governorate));
continue;
}
// Log what we're looking for
error_log("getPolygonFromJsonSheet: Searching for governorate - ID: " . var_export($governorateId, true) . ", EN: " . var_export($governorateNameEn, true) . ", AR: " . var_export($governorateNameAr, true));
// Log sample features from JSON (only once) - showing REGION_N_1 field that we match against
static $loggedSamples = false;
if (!$loggedSamples) {
$sampleFeatures = [];
foreach (array_slice($jsonData['features'], 0, 10) as $sampleIdx => $sampleFeature) {
$sampleProps = $sampleFeature['properties'] ?? [];
$sampleFeatures[] = [
'REGION_N_1' => $sampleProps['REGION_N_1'] ?? 'N/A',
];
}
error_log("getPolygonFromJsonSheet: Sample REGION_N_1 values from JSON (first 10): " . json_encode($sampleFeatures, JSON_UNESCAPED_UNICODE));
$loggedSamples = true;
}
$matched = false;
$matchReason = '';
$matchAttempts = [];
$foundAnyMatch = false; // Track if we found at least one match for this governorate
// Find matching feature in JSON by matching governorate nameEn with REGION_N_1 field only
foreach ($jsonData['features'] as $featureIndex => $feature) {
$matched = false; // Reset for each feature
if (!isset($feature['properties']) || !isset($feature['geometry'])) {
continue;
}
$properties = $feature['properties'];
$geometry = $feature['geometry'];
// Match by English name only (exact case-insensitive match - using REGION_N_1 field only)
if ($governorateNameEn !== null) {
$govNameEn = trim((string)$governorateNameEn);
if (!empty($govNameEn) && isset($properties['REGION_N_1'])) {
// Normalize the governorate name (lowercase, normalize whitespace)
$govNameNormalized = strtolower(preg_replace('/\s+/', ' ', $govNameEn));
$jsonNameEn = trim((string)$properties['REGION_N_1']);
if (!empty($jsonNameEn)) {
$jsonNameNormalized = strtolower(preg_replace('/\s+/', ' ', $jsonNameEn));
// Only exact case-insensitive match (normalized)
if ($jsonNameNormalized === $govNameNormalized) {
$matched = true;
$matchReason = 'Name_EN (exact via REGION_N_1): ' . $governorateNameEn . ' matches ' . $jsonNameEn;
}
}
}
}
if ($matched && isset($geometry['type']) && isset($geometry['coordinates'])) {
error_log("getPolygonFromJsonSheet: Match found! Reason: " . $matchReason . ", Geometry type: " . $geometry['type'] . ", ADMIN: " . ($properties['ADMIN'] ?? 'N/A'));
// Extract coordinates based on geometry type
$coords = $this->extractCoordinatesFromGeometry($geometry);
error_log("getPolygonFromJsonSheet: Extracted " . count($coords) . " coordinates");
if (!empty($coords)) {
// Format coordinates for this governorate separately
$polygonCoords = $this->formatCoordinates($coords);
// Check if we already have a polygon for THIS SPECIFIC governorate
// IMPORTANT: We do NOT merge polygons from different features to avoid creating straight connecting lines.
// If a governorate appears in multiple features, we use only the first match to prevent invalid polygon connections.
$existingIndex = null;
foreach ($governoratePolygons as $idx => $existingPoly) {
if (($existingPoly['governorate'] === $governorateObj) ||
(is_object($governorateObj) && is_object($existingPoly['governorate']) &&
method_exists($governorateObj, 'getGovernoteId') &&
method_exists($existingPoly['governorate'], 'getGovernoteId') &&
$governorateObj->getGovernoteId() === $existingPoly['governorate']->getGovernoteId())
) {
$existingIndex = $idx;
break;
}
}
if ($existingIndex !== null) {
// Governorate already has a polygon - skip this match to avoid creating straight connecting lines
// Using only the first match ensures clean polygon boundaries without invalid connections
error_log("getPolygonFromJsonSheet: Skipping duplicate match for governorate ID: " . $governorateId . " (already has polygon)");
} else {
// Store polygon with governorate info (separate polygon per governorate)
// Each governorate gets its own entry, even if multiple governorates are from the same region
$governoratePolygons[] = [
'governorate' => $governorateObj,
'nameEn' => $governorateNameEn,
'nameAr' => $governorateNameAr,
'polygon' => $polygonCoords
];
error_log("getPolygonFromJsonSheet: Successfully added polygon for governorate ID: " . $governorateId);
}
$matchDebug = [
'match_reason' => $matchReason,
'coords_count' => count($coords),
'geometry_type' => $geometry['type'],
'matched' => true
];
$debugMatches[] = $matchDebug;
if ($debugInfo !== null) $debugInfo['matches'][] = $matchDebug;
$foundAnyMatch = true; // Mark that we found at least one match
// Continue searching for more matching features (same governorate can have multiple regions)
// We'll merge all matching polygons for this governorate
continue;
} else {
$errorMsg = "Matched but extracted 0 coordinates. Geometry type: " . $geometry['type'] . ", Coordinates structure: " . json_encode($geometry['coordinates']);
error_log("getPolygonFromJsonSheet: " . $errorMsg);
if ($debugInfo !== null) {
$debugInfo['errors'][] = $errorMsg;
$debugInfo['matches'][] = ['match_reason' => $matchReason, 'matched' => true, 'coords_extracted' => 0];
}
}
}
}
if (!$foundAnyMatch) {
error_log("getPolygonFromJsonSheet: No match found for governorate ID: " . var_export($governorateId, true) . ", EN: " . var_export($governorateNameEn, true) . ", AR: " . var_export($governorateNameAr, true));
$noMatchDebug = [
'governorate_id' => $governorateId,
'governorate_id_type' => gettype($governorateId),
'name_en' => $governorateNameEn,
'name_ar' => $governorateNameAr,
'matched' => false,
'match_attempts' => array_slice($matchAttempts, 0, 10) // Limit to first 10 attempts
];
$debugMatches[] = $noMatchDebug;
if ($debugInfo !== null) $debugInfo['matches'][] = $noMatchDebug;
}
}
// Log summary
error_log("getPolygonFromJsonSheet: Processed " . count($governorates) . " governorates, found " . count($governoratePolygons) . " polygons");
if (empty($governoratePolygons)) {
// Log available REGION_N_1 values from JSON for debugging
$availableRegionNames = [];
foreach ($jsonData['features'] as $feature) {
$props = $feature['properties'] ?? [];
$regionName = $props['REGION_N_1'] ?? null;
if ($regionName !== null) {
$availableRegionNames[] = $regionName;
}
}
error_log("getPolygonFromJsonSheet: No coordinates found. Debug matches: " . json_encode($debugMatches));
error_log("getPolygonFromJsonSheet: Available REGION_N_1 values in JSON (first 20): " . implode(', ', array_slice($availableRegionNames, 0, 20)));
} else {
error_log("getPolygonFromJsonSheet: Successfully extracted polygons for " . count($governoratePolygons) . " governorates");
}
return $governoratePolygons;
}
/**
* Extract coordinates from GeoJSON geometry (supports Polygon and MultiPolygon)
*
* @param array $geometry GeoJSON geometry object
* @return array Array of coordinate pairs [lng, lat]
*/
private function extractCoordinatesFromGeometry($geometry)
{
$coordinates = [];
if (!isset($geometry['type']) || !isset($geometry['coordinates'])) {
return $coordinates;
}
$type = $geometry['type'];
$coords = $geometry['coordinates'];
if ($type === 'Polygon' && is_array($coords)) {
// Polygon: coordinates[0] is the outer ring
if (isset($coords[0]) && is_array($coords[0])) {
foreach ($coords[0] as $coord) {
if (is_array($coord) && count($coord) >= 2) {
$coordinates[] = $coord;
}
}
}
} elseif ($type === 'MultiPolygon' && is_array($coords)) {
// MultiPolygon: array of polygons, each polygon has coordinates[0] as outer ring
// IMPORTANT: Use only the FIRST polygon to avoid creating straight connecting lines between disconnected parts
// CAP XML polygons should represent a single continuous boundary
if (!empty($coords) && is_array($coords[0]) && isset($coords[0][0]) && is_array($coords[0][0])) {
foreach ($coords[0][0] as $coord) {
if (is_array($coord) && count($coord) >= 2) {
$coordinates[] = $coord;
}
}
}
}
return $coordinates;
}
/**
* Extract coordinates directly from file content when JSON parsing fails
* This is a fallback method that uses string/regex matching
*/
private function extractCoordinatesFromFileContent($fileContent, $governorates)
{
$governoratePolygons = [];
foreach ($governorates as $governorate) {
$governorateCoords = [];
// Get governorate ID and names
if (is_object($governorate)) {
$governorateId = method_exists($governorate, 'getGovernoteId') ? $governorate->getGovernoteId() : null;
$governorateNameEn = method_exists($governorate, 'getName') ? $governorate->getName('en') : null;
$governorateNameAr = method_exists($governorate, 'getName') ? $governorate->getName('ar') : null;
$governorateObj = $governorate;
// Check if this governorate is a municipality and fetch parent governorate
if (method_exists($governorate, 'getIsMunicipality') && $governorate->getIsMunicipality()) {
$municipalityId = method_exists($governorate, 'getMunicipalityID') ? $governorate->getMunicipalityID() : null;
if ($municipalityId !== null) {
try {
$municipality = \Pimcore\Model\DataObject\Municipality::getByMunicipalityid($municipalityId, true);
if ($municipality && $municipality->getGovernorate()) {
$parentGovernorate = $municipality->getGovernorate();
// Use parent governorate's names for matching (English first, then Arabic)
$governorateNameEn = $parentGovernorate->getName('en');
$governorateNameAr = $parentGovernorate->getName('ar');
// Also update the ID to parent governorate's ID for matching
$governorateId = $parentGovernorate->getGovernoteId();
error_log("extractCoordinatesFromFileContent: Municipality detected (ID: " . $municipalityId . "), using parent governorate - ID: " . var_export($governorateId, true) . ", EN: " . var_export($governorateNameEn, true) . ", AR: " . var_export($governorateNameAr, true));
}
} catch (\Exception $ex) {
error_log("extractCoordinatesFromFileContent: Error fetching municipality (ID: " . $municipalityId . "): " . $ex->getMessage());
}
}
}
} elseif (is_array($governorate)) {
$governorateId = $governorate['id'] ?? $governorate['governoteId'] ?? null;
$governorateNameEn = $governorate['name_en'] ?? $governorate['name']['en'] ?? null;
$governorateNameAr = $governorate['name_ar'] ?? $governorate['name']['ar'] ?? null;
$governorateObj = $governorate;
// Check if this governorate is a municipality (array format)
if (isset($governorate['IsMunicipality']) && $governorate['IsMunicipality']) {
$municipalityId = $governorate['MunicipalityID'] ?? null;
if ($municipalityId !== null) {
try {
$municipality = \Pimcore\Model\DataObject\Municipality::getByMunicipalityid($municipalityId, true);
if ($municipality && $municipality->getGovernorate()) {
$parentGovernorate = $municipality->getGovernorate();
// Use parent governorate's names for matching (English first, then Arabic)
$governorateNameEn = $parentGovernorate->getName('en');
$governorateNameAr = $parentGovernorate->getName('ar');
// Also update the ID to parent governorate's ID for matching
$governorateId = $parentGovernorate->getGovernoteId();
error_log("extractCoordinatesFromFileContent: Municipality detected (ID: " . $municipalityId . "), using parent governorate - ID: " . var_export($governorateId, true) . ", EN: " . var_export($governorateNameEn, true) . ", AR: " . var_export($governorateNameAr, true));
}
} catch (\Exception $ex) {
error_log("extractCoordinatesFromFileContent: Error fetching municipality (ID: " . $municipalityId . "): " . $ex->getMessage());
}
}
}
} else {
continue;
}
if ($governorateId === null) {
continue;
}
// Search for the governorate section in the file
// Try multiple patterns for both old (GovID) and new (ADMIN) formats
// Patterns: "GovID": "76", "GovID": 76, "ADMIN": 12, "ADMIN": "12"
$idStr = (string)$governorateId;
$patterns = [
'"ADMIN"\s*:\s*"' . preg_quote($idStr, '/') . '"',
'"ADMIN"\s*:\s*' . preg_quote($idStr, '/'),
'"GovID"\s*:\s*"' . preg_quote($idStr, '/') . '"',
'"GovID"\s*:\s*' . preg_quote($idStr, '/'),
];
$found = false;
foreach ($patterns as $pattern) {
if (preg_match('/' . $pattern . '/', $fileContent, $matches, PREG_OFFSET_CAPTURE)) {
$matchPos = $matches[0][1];
$found = true;
// Find the coordinates array that follows this ADMIN/GovID (within next 100000 chars)
$searchArea = substr($fileContent, $matchPos, 100000);
// Look for coordinates array: "coordinates": [[[lng, lat], ...]]
if (preg_match('/"coordinates"\s*:\s*\[/', $searchArea, $coordMatch, PREG_OFFSET_CAPTURE)) {
$coordStartInArea = $coordMatch[0][1];
$coordStart = $matchPos + $coordStartInArea;
// Extract the coordinates array by finding matching brackets
$depth = 0;
$startBracket = false;
$coordString = '';
$bracketCount = 0;
// Find the opening bracket
for ($i = $coordStart; $i < strlen($fileContent) && $i < $coordStart + 200000; $i++) {
$char = $fileContent[$i];
if ($char === '[') {
if (!$startBracket) {
$startBracket = true;
}
$depth++;
$bracketCount++;
$coordString .= $char;
} elseif ($char === ']') {
$depth--;
$bracketCount++;
$coordString .= $char;
if ($depth === 0 && $startBracket) {
break; // Found complete coordinates array
}
} elseif ($startBracket) {
$coordString .= $char;
} elseif (preg_match('/\S/', $char)) {
// Non-whitespace before bracket - might be start
if ($char === '[') {
$startBracket = true;
$depth = 1;
$coordString = '[';
}
}
}
// Extract coordinates using regex (more reliable than JSON parsing for malformed JSON)
// Pattern: [ lng, lat ] or [lng,lat] - matches coordinate pairs
$coordPattern = '/\[\s*([+-]?\d+\.?\d*)\s*,\s*([+-]?\d+\.?\d*)\s*\]/';
preg_match_all($coordPattern, $coordString, $coordMatches, PREG_SET_ORDER);
if (!empty($coordMatches)) {
foreach ($coordMatches as $match) {
$lng = (float)$match[1];
$lat = (float)$match[2];
// Validate ranges
if ($lat >= -90 && $lat <= 90 && $lng >= -180 && $lng <= 180) {
$governorateCoords[] = [$lng, $lat];
}
}
} else {
// Fallback: Try JSON decode if regex fails
$coordsArray = json_decode($coordString, true);
if ($coordsArray !== null && is_array($coordsArray)) {
$coords = $this->extractCoordsFromArray($coordsArray);
if (!empty($coords)) {
$governorateCoords = $coords;
}
}
}
// Format coordinates for this governorate separately
if (!empty($governorateCoords)) {
$polygonCoords = $this->formatCoordinates($governorateCoords);
$governoratePolygons[] = [
'governorate' => $governorateObj,
'nameEn' => $governorateNameEn,
'nameAr' => $governorateNameAr,
'polygon' => $polygonCoords
];
}
}
break; // Found match, no need to try other patterns
}
}
}
return $governoratePolygons;
}
/**
* Extract coordinate pairs from nested array structure (Polygon/MultiPolygon)
*/
private function extractCoordsFromArray($coordsArray)
{
$coordinates = [];
if (!is_array($coordsArray)) {
return $coordinates;
}
// Check if it's a coordinate pair [lng, lat]
if (count($coordsArray) === 2 && is_numeric($coordsArray[0]) && is_numeric($coordsArray[1])) {
$coordinates[] = $coordsArray;
return $coordinates;
}
// Recursively process nested arrays
foreach ($coordsArray as $item) {
if (is_array($item)) {
$subCoords = $this->extractCoordsFromArray($item);
$coordinates = array_merge($coordinates, $subCoords);
}
}
return $coordinates;
}
/**
* Format coordinates for CAP format
*/
private function formatCoordinates($allCoordinates)
{
$formattedCoords = [];
foreach ($allCoordinates as $coord) {
if (is_array($coord) && count($coord) >= 2) {
if (!is_numeric($coord[0]) || !is_numeric($coord[1])) {
continue;
}
$lng = (float)$coord[0];
$lat = (float)$coord[1];
// Validate ranges
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
continue;
}
$formattedLat = number_format($lat, 6, '.', '');
$formattedLng = number_format($lng, 6, '.', '');
$formattedCoords[] = $formattedLat . ',' . $formattedLng;
}
}
if (!empty($formattedCoords)) {
$polygonCoords = implode(' ', $formattedCoords);
// Ensure polygon is closed
$coordsArray = explode(' ', $polygonCoords);
$firstCoord = $coordsArray[0];
$lastCoord = end($coordsArray);
if ($firstCoord !== $lastCoord) {
$polygonCoords .= ' ' . $firstCoord;
}
return $polygonCoords;
}
return '';
}
/**
* Create a fallback polygon when coordinates are invalid or missing
* Creates a bounding box from region/governorate coordinates
*/
private function createFallbackPolygon($doc, $notification, $governorateCoordinates, $timezone)
{
$polygonCoords = '';
// Try to use governorate coordinates first
if (!empty($governorateCoordinates)) {
$coords = explode(' ', trim($governorateCoordinates));
$validCoords = [];
foreach ($coords as $coord) {
if (strpos($coord, ',') !== false) {
$parts = explode(',', $coord);
if (count($parts) == 2 && is_numeric($parts[0]) && is_numeric($parts[1])) {
$validCoords[] = $coord;
}
}
}
if (count($validCoords) >= 4) {
// Use governorate coordinates
$polygonCoords = implode(' ', $validCoords);
// Close the polygon
if (!empty($validCoords)) {
$firstCoord = $validCoords[0];
$lastCoord = end($validCoords);
if ($firstCoord !== $lastCoord) {
$polygonCoords .= ' ' . $firstCoord;
}
}
}
}
// Fallback to region coordinates if governorate coordinates are insufficient
if (empty($polygonCoords) && $notification->getRegion()) {
$lat = $notification->getRegion()->getLatitude();
$lng = $notification->getRegion()->getLongitude();
if (is_numeric($lat) && is_numeric($lng)) {
// Create a small bounding box around the region center (0.1 degree radius)
$offset = 0.1;
$coords = [
($lat - $offset) . ',' . ($lng - $offset), // SW
($lat - $offset) . ',' . ($lng + $offset), // SE
($lat + $offset) . ',' . ($lng + $offset), // NE
($lat + $offset) . ',' . ($lng - $offset), // NW
($lat - $offset) . ',' . ($lng - $offset) // Close polygon
];
$polygonCoords = implode(' ', $coords);
}
}
// Final fallback: use default coordinates if nothing else works
if (empty($polygonCoords)) {
// Default coordinates for Saudi Arabia center (Riyadh area)
$defaultCoords = [
'24.5,46.5', // SW
'24.5,46.7', // SE
'24.7,46.7', // NE
'24.7,46.5', // NW
'24.5,46.5' // Close polygon
];
$polygonCoords = implode(' ', $defaultCoords);
}
return $doc->createElement('polygon', $polygonCoords);
}
public function viewNotification($params, $translator): array
{
$decodedJwtToken = $params['decodedJwtToken'] ?? null;
$userPermission = $params['userPermission'] ?? null;
if (is_array($params['id'])) {
$ids = array_values(array_unique(array_filter($params['id'], static function ($v) {
return $v !== null && $v !== '' && $v !== false;
})));
if ($ids === []) {
return ['success' => false, 'message' => $translator->trans('ews_notification_does_not_exists')];
}
$items = [];
foreach ($ids as $id) {
$viewNotification = DataObject\EwsNotification::getById($id, false);
if (!$viewNotification) {
$viewNotification = DataObject\EwsNotification::getById($id, true);
}
if ($viewNotification) {
$items[] = [
'id' => $id,
'success' => true,
'data' => $this->formatNotificationWithUpdateChanges($viewNotification, $translator, $decodedJwtToken, $userPermission),
'history' => $this->getVersions($viewNotification, $translator, $decodedJwtToken, $userPermission),
];
} else {
$items[] = [
'id' => $id,
'success' => false,
'message' => $translator->trans('ews_notification_does_not_exists'),
];
}
}
$anyOk = false;
foreach ($items as $row) {
if (!empty($row['success'])) {
$anyOk = true;
break;
}
}
return $anyOk
? ['success' => true, 'items' => $items]
: ['success' => false, 'message' => $translator->trans('ews_notification_does_not_exists'), 'items' => $items];
}
$viewNotification = DataObject\EwsNotification::getById($params['id'], false);
if (!$viewNotification) {
$viewNotification = DataObject\EwsNotification::getById($params['id'], true);
}
if ($viewNotification) {
$notificationData = $this->formatNotificationWithUpdateChanges($viewNotification, $translator, $decodedJwtToken, $userPermission);
$history = $this->getVersions($viewNotification, $translator, $decodedJwtToken, $userPermission);
return ['success' => true, 'data' => $notificationData, 'history' => $history];
}
return ['success' => false, 'message' => $translator->trans('ews_notification_does_not_exists')];
}
public function searchEwsNotificationByRegion($regionId, $translator)
{
$items = [];
// The "region" field on EwsNotification is a manyToOneRelation, so the
// magic getByRegion() expects an ElementInterface (or ['id','type'] array),
// NOT the raw regionId integer that comes from the API request. We must
// resolve the Region object first via its custom regionId field.
$regionObj = DataObject\Region::getByRegionId($regionId, 1);
if (!$regionObj) {
return ['success' => false, 'message' => $translator->trans('Region not found.')];
}
$currentTimestamp = time();
$ewsNotificationList = DataObject\EwsNotification::getByRegion($regionObj);
if (!empty($ewsNotificationList)) {
$ewsNotificationList->addConditionParam("status != ?", ["ended"]);
$ewsNotificationList->filterByEndDate($currentTimestamp, ">=");
foreach ($ewsNotificationList as $ewsNotification) {
$items[] = $ewsNotification->getId();
}
}
return ['success' => true, 'items' => $items];
}
public function addAddressComponentsFieldCollection($addressComponents, DataObject\EwsNotification $object)
{
$items = new \Pimcore\Model\DataObject\Fieldcollection();
foreach ($addressComponents as $Data) {
#creating field collections
$item = new DataObject\Fieldcollection\Data\AddressComponents();
$item->setAddressValue(strip_tags($Data['long_name']));
$item->setAddressKey(strip_tags($Data['types'][0]));
$items->add($item);
}
return $object->setAddressComponents($items);
}
public function getAlertAction($id)
{
$alertActionObj = DataObject\AlertAction::getById($id);
$data = [];
if (!empty($alertActionObj)) {
$data['id'] = $alertActionObj->getAlertActionId();
$data['severity'] = $alertActionObj->getSeverity();
$data['nameEn'] = $alertActionObj->getName('en');
$data['nameAr'] = $alertActionObj->getName('ar');
}
return $data;
}
public function getAlertType($id)
{
$alertTypeObj = DataObject\AlertType::getById($id);
$data = [];
if (!empty($alertTypeObj)) {
$data['id'] = $alertTypeObj->getAlertTypeId();
$data['nameEn'] = $alertTypeObj->getName('en');
$data['nameAr'] = $alertTypeObj->getName('ar');
}
return $data;
}
public function getAlertStatus($id)
{
$alertStatus = DataObject\AlertStatus::getById($id);
$data = [];
if (!empty($alertStatus)) {
$alertType = $alertStatus->getAlertType();
$alertData = [];
if (!empty($alertType)) {
$alertData = [
"id" => $alertType->getAlertTypeId(),
"nameEn" => $alertType->getName('en'),
"nameAr" => $alertType->getName('ar')
];
}
$data['id'] = $alertStatus->getAlertStatusId();
$data['nameEn'] = $alertStatus->getName('en');
$data['nameAr'] = $alertStatus->getName('ar');
$data['alertType'] = $alertData;
}
return $data;
}
public function getAlertHazard($id)
{
$alertHazardObj = DataObject\AlertHazard::getById($id);
$data = [];
if (!empty($alertHazardObj)) {
$data['id'] = $alertHazardObj->getAlertHazardId();
$data['nameEn'] = $alertHazardObj->getName('en');
$data['nameAr'] = $alertHazardObj->getName('ar');
}
return $data;
}
public function getRegion($id)
{
$regionObj = DataObject\Region::getById($id);
$data = [];
if (!empty($regionObj)) {
$data['id'] = $regionObj->getRegionId();
$data['nameEn'] = $regionObj->getName('en');
$data['nameAr'] = $regionObj->getName('ar');
$data['longitude'] = $regionObj->getLongitude();
$data['latitude'] = $regionObj->getLatitude();
}
return $data;
}
public function getEvent($id)
{
$eventObj = DataObject\Event::getById($id);
$data = [];
if (!empty($eventObj)) {
$data['id'] = $eventObj->getEventId();
$data['nameEn'] = $eventObj->getName('en');
$data['nameAr'] = $eventObj->getName('ar');
}
return $data;
}
public function getWeatherPhenomenon($id)
{
$weatherPhenObj = DataObject\PhenomenaList::getById($id);
$data = [];
if (!empty($weatherPhenObj)) {
$data['id'] = $weatherPhenObj->getPhenomenaListId();
$data['nameEn'] = $weatherPhenObj->getTitle('en');
$data['nameAr'] = $weatherPhenObj->getTitle('ar');
}
return $data;
}
public function getWeatherPhenAffect($id)
{
$weatherPhenObj = DataObject\WeatherPhenomenonAffect::getById($id);
$data = [];
if (!empty($weatherPhenObj)) {
$data['id'] = $weatherPhenObj->getWeatherPhenomenonAffectId();
$data['nameEn'] = $weatherPhenObj->getName('en');
$data['nameAr'] = $weatherPhenObj->getName('ar');
}
return $data;
}
// public function getGovernorateDetail($governorateList)
// {
// $result = [];
// if (!empty($governorateList)) {
// for ($i = 0; $i < count($governorateList); $i++) {
// $parentGovernates = null;
// if ($governorateList[$i]->getIsMunicipality()) {
// $municipatlity = \Pimcore\Model\DataObject\Municipality::getByMunicipalityId($governorateList[$i]->getMunicipalityID(), true);
// if ($municipatlity && $municipatlity->getGovernorate()) {
// $parentGovernates = $municipatlity->getGovernorate();
// }
// }
// $result[$i]['id'] = $governorateList[$i]->getGovernoteId();
// $result[$i]['nameEn'] = $governorateList[$i]->getName('en');
// $result[$i]['nameAr'] = $governorateList[$i]->getName('ar');
// $result[$i]['longitude'] = $governorateList[$i]->getLongitude();
// $result[$i]['latitude'] = $governorateList[$i]->getLatitude();
// $result[$i]['parentId'] = $parentGovernates ? $parentGovernates->getGovernoteId() : null;
// $result[$i]['parenNameEn'] = $parentGovernates ? $parentGovernates->getName('en') : null;
// $result[$i]['parenNameAr'] = $parentGovernates ? $parentGovernates->getName('ar') : null;
// $result[$i]['parentLongitude'] = $parentGovernates ? $parentGovernates->getLongitude() : null;
// $result[$i]['parentLatitude'] = $parentGovernates ? $parentGovernates->getLatitude() : null;
// $result[$i]['isMunicipality'] = $governorateList[$i]->getIsMunicipality();
// $result[$i]['municipalities'] = [];
// }
// }
// return $result;
// }
public function getGovernorateDetail($governorateList)
{
$result = [];
if (!empty($governorateList)) {
$uniqueGovernorateArray = [];
$isCapital = [];
$hasCapital = false;
foreach ($governorateList as $governorate) {
$parentGovernates = null;
// Check if it is a municipality and fetch parent governorate
if ($governorate->getIsMunicipality()) {
$municipality = \Pimcore\Model\DataObject\Municipality::getByMunicipalityId($governorate->getMunicipalityID(), true);
if ($municipality && $municipality->getGovernorate()) {
$parentGovernates = $municipality->getGovernorate();
}
}
// Initialize a unique governorate array if needed
$parentId = $parentGovernates ? $parentGovernates->getGovernoteId() : $governorate->getGovernoteId();
if (!isset($uniqueGovernorateArray[$parentId])) {
$uniqueGovernorateArray[$parentId] = [
'id' => $parentId,
'nameEn' => $parentGovernates ? $parentGovernates->getName('en') : $governorate->getName('en'),
'nameAr' => $parentGovernates ? $parentGovernates->getName('ar') : $governorate->getName('ar'),
'longitude' => $parentGovernates ? $parentGovernates->getLongitude() : $governorate->getLongitude(),
'latitude' => $parentGovernates ? $parentGovernates->getLatitude() : $governorate->getLatitude(),
'municipalities' => []
];
$gov = $parentGovernates ?: \Pimcore\Model\DataObject\Governorate::getByGovernoteId($parentId, true) ?: $governorate;
$isCapital[$parentId] = method_exists($gov, 'getIsCapital') ? (bool) $gov->getIsCapital() : false;
}
// Add municipality details if it is a municipality
if ($governorate->getIsMunicipality()) {
$uniqueGovernorateArray[$parentId]['municipalities'][] = [
'id' => $governorate->getGovernoteId(),
'nameEn' => $governorate->getName('en'),
'nameAr' => $governorate->getName('ar'),
'longitude' => $governorate->getLongitude(),
'latitude' => $governorate->getLatitude()
];
}
}
// Flatten the associative array to a numerical array for output
$result = array_values($uniqueGovernorateArray);
if (in_array(true, $isCapital, true)) {
foreach ($result as $i => &$row) {
$row['_'] = $i;
}
unset($row);
usort($result, static function ($a, $b) use ($isCapital) {
$c = (int) !empty($isCapital[$b['id']]) - (int) !empty($isCapital[$a['id']]);
return $c ?: ($a['_'] <=> $b['_']);
});
foreach ($result as &$row) {
unset($row['_']);
}
unset($row);
}
}
return $result;
}
public function getMessageDetail($notificationObj)
{
$data = [];
if (!empty($notificationObj)) {
$data['messageEn'] = $notificationObj->getMessage('en');
$data['messageAr'] = $notificationObj->getMessage('ar');
}
return $data;
}
public function createAsset($fileData, $filename)
{
// Validate the file data
if (preg_match('/^data:(image\/(png|jpe?g)|application\/(pdf|vnd.openxmlformats-officedocument.wordprocessingml.document|vnd.ms-excel));base64,/', $fileData) !== 1) {
return false; // Invalid file data or MIME type
}
// Extract the file extension from the MIME type
$extension = '';
if (preg_match('/^data:image\/(png|jpe?g);base64,/', $fileData)) {
$extension = 'jpg'; // Assume JPG for base64-encoded image data (PNG or JPEG)
} elseif (preg_match('/^data:application\/(pdf|vnd.openxmlformats-officedocument.wordprocessingml.document|vnd.ms-excel);base64,/', $fileData)) {
$extension = 'pdf'; // PDF, DOCX, or Excel
}
// Remove the "data:image/png;base64," or "data:application/pdf;base64," prefix to get the actual base64-encoded content
$base64Content = preg_replace('/^data:(image\/png|application\/(pdf|vnd.openxmlformats-officedocument.wordprocessingml.document|vnd.ms-excel));base64,/', '', $fileData);
// Decode the base64-encoded content
$fileContent = base64_decode($base64Content);
// Validate the decoded content
if ($fileContent === false) {
return false; // Invalid base64-encoded content
}
// Create the Pimcore asset
$asset = new \Pimcore\Model\Asset();
$parent = Asset\Service::createFolderByPath('/EWSNotification');
$asset->setFilename($filename); // Replace with the desired filename and extension
$asset->setParent($parent);
$asset->setData($fileContent);
// Set the MIME type based on the file extension
$mimeType = '';
if ($extension === 'pdf') {
$mimeType = 'application/pdf';
} elseif ($extension === 'jpg') {
$mimeType = 'image/jpeg';
} elseif ($extension === 'png') {
$mimeType = 'image/png';
} elseif ($extension === 'docx') {
$mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
} elseif ($extension === 'xlsx') {
$mimeType = 'application/vnd.ms-excel';
}
// Validate the MIME type
if (!in_array($mimeType, ['image/jpeg', 'image/png', 'application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel'])) {
return false; // Invalid MIME type
}
$asset->setType($mimeType);
// Save the asset
$asset->save();
return $asset;
}
public function getPhenomenaListByAlertId($alertID, $lang = "en")
{
$response = [];
$alertType = \Pimcore\Model\DataObject\AlertType::getByAlertTypeId($alertID, true);
if ($alertType instanceof \Pimcore\Model\DataObject\AlertType) {
$phenomenaList = $alertType->getPhenomenaList();
if ($phenomenaList) {
foreach ($phenomenaList as $phenomena) {
$phenomenaId = $phenomena->getPhenomena();
if ($phenomenaId) {
$phenomenaObj = \Pimcore\Model\DataObject::getById($phenomenaId);
$response[] = ["id" => $phenomenaObj->getphenomenaListId(), "nameEn" => $phenomenaObj->getTitle("en"), "nameAr" => $phenomenaObj->getTitle("ar"), "criteria" => $phenomena->getCriteria()];
}
}
} else {
return ["success" => false, "message" => "No criteria is set"];
}
} else {
return ["success" => false, "message" => "Invalid alert type id"];
}
return ["success" => true, "data" => $response];
}
public function getPhenomenaListByAlertIds($alertIDs, $lang = "en")
{
$response = [];
if (count($alertIDs) > 0) {
foreach ($alertIDs as $alertID) {
$alertType = \Pimcore\Model\DataObject\AlertType::getByAlertTypeId($alertID, true);
if ($alertType instanceof \Pimcore\Model\DataObject\AlertType) {
$phenomenaList = $alertType->getPhenomenaList();
if ($phenomenaList) {
foreach ($phenomenaList as $phenomena) {
$phenomenaId = $phenomena->getPhenomena();
if ($phenomenaId) {
$phenomenaObj = \Pimcore\Model\DataObject::getById($phenomenaId);
if ($phenomenaObj instanceof \Pimcore\Model\DataObject) {
$currentId = $phenomenaObj->getphenomenaListId();
$existingIds = array_column($response, 'id');
if (!in_array($currentId, $existingIds)) {
$response[] = [
"id" => $currentId,
"nameEn" => $phenomenaObj->getTitle("en"),
"nameAr" => $phenomenaObj->getTitle("ar"),
"criteria" => $phenomena->getCriteria()
];
} else {
$index = array_search($currentId, $existingIds);
$response[$index]['criteria'] .= ', ' . $phenomena->getCriteria();
}
}
}
}
} else {
return ["success" => false, "message" => "No criteria is set"];
}
}
}
return ["success" => true, "data" => $response];
}
return ["success" => false, "message" => "Invalid alert type id"];
}
public function getAlertActions()
{
$response = [];
$alertActions = new DataObject\AlertAction\Listing();
$alertActions = $alertActions->load();
if ($alertActions) {
foreach ($alertActions as $alertAction) {
$response[] = [
"id" => $alertAction->getAlertActionId(),
"nameEn" => $alertAction->getName('en'),
"nameAr" => $alertAction->getName('ar'),
"severity" => $alertAction->getSeverity()
];
}
}
return ["success" => true, "data" => $response];
}
public function getAlertHazards()
{
$response = [];
$alertHazards = new DataObject\AlertHazard\Listing();
$alertHazards = $alertHazards->load();
if ($alertHazards) {
foreach ($alertHazards as $alertHazard) {
$response[] = [
"id" => $alertHazard->getalertHazardId(),
"nameEn" => $alertHazard->getName('en'),
"nameAr" => $alertHazard->getName('ar'),
"OrderId" => $alertHazard->getOrderId()
];
}
}
return ["success" => true, "data" => $response];
}
public function getAlertTypes()
{
$response = [];
$alertTypes = new DataObject\AlertType\Listing();
$alertTypes = $alertTypes->load();
if ($alertTypes) {
foreach ($alertTypes as $alertType) {
$response[] = [
"id" => $alertType->getAlertTypeId(),
"color" => $alertType->getColor(),
"nameEn" => $alertType->getName('en'),
"nameAr" => $alertType->getName('ar')
];
}
}
return ["success" => true, "data" => $response];
}
public function getEvents()
{
$response = [];
$events = new DataObject\Event\Listing();
$events = $events->load();
if ($events) {
foreach ($events as $event) {
$response[] = [
"id" => $event->getEventId(),
"nameEn" => $event->getName('en'),
"nameAr" => $event->getName('ar')
];
}
}
return ["success" => true, "data" => $response];
}
public function getWeatherPhenomenones()
{
$response = [];
$weatherPhenomenons = new DataObject\PhenomenaList\Listing();
$weatherPhenomenons = $weatherPhenomenons->load();
if ($weatherPhenomenons) {
foreach ($weatherPhenomenons as $weatherPhenomenon) {
$response[] = [
"id" => $weatherPhenomenon->getPhenomenaListId(),
"nameEn" => $weatherPhenomenon->getTitle('en'),
"nameAr" => $weatherPhenomenon->getTitle('ar')
];
}
}
return ["success" => true, "data" => $response];
}
public function getWeatherPhenomenonAffect()
{
$response = [];
$getWeatherPhenomAffects = new DataObject\WeatherPhenomenonAffect\Listing();
$getWeatherPhenomAffects = $getWeatherPhenomAffects->load();
if ($getWeatherPhenomAffects) {
foreach ($getWeatherPhenomAffects as $getWeatherPhenomAffect) {
$response[] = [
"id" => $getWeatherPhenomAffect->getWeatherPhenomenonAffectId(),
"nameEn" => $getWeatherPhenomAffect->getName('en'),
"nameAr" => $getWeatherPhenomAffect->getName('ar')
];
}
}
return ["success" => true, "data" => $response];
}
public function getAlertStatuses($params)
{
$response = [];
$alertStatuss = new DataObject\AlertStatus\Listing();
// Handle search by name
if (isset($params['search']) && !empty($params['search'])) {
$alertStatuss->addConditionParam("name LIKE ?", "%" . $params['search'] . "%");
}
// Handle alert type filter
if (isset($params['alert_id']) && !empty($params['alert_id'])) {
$alertTypeIds = [];
$alertTypeList = new DataObject\AlertType\Listing();
$alertTypeList->addConditionParam("alertTypeId IN (?)", [$params['alert_id']]);
foreach ($alertTypeList as $alertType) {
$alertTypeIds[] = $alertType->getId();
}
$alertStatuss->addConditionParam("alertType__id IN (?)", [$alertTypeIds]);
}
// Check for regionId array and isLandLocked logic
$excludeWavesRising = false;
if (isset($params['regionId']) && is_array($params['regionId']) && count($params['regionId']) > 0) {
foreach ($params['regionId'] as $regionId) {
$region = \Pimcore\Model\DataObject\Region::getByRegionId($regionId, true);
if ($region && $region->getIsLandLocked()) {
$excludeWavesRising = true;
break;
}
}
}
$alertStatuss = $alertStatuss->load();
if ($alertStatuss) {
foreach ($alertStatuss as $alertStatus) {
// Exclude "Waves rising" if needed
if ($excludeWavesRising && ((strtolower(trim($alertStatus->getName('en'))) === 'waves rising'))) {
continue;
}
$alertType = $alertStatus->getAlertType();
$alertData = [];
if (!empty($alertType)) {
$alertData = [
"id" => $alertType->getAlertTypeId(),
"nameEn" => $alertType->getName('en'),
"nameAr" => $alertType->getName('ar')
];
}
$response[] = [
"id" => $alertStatus->getalertStatusId(),
"nameEn" => $alertStatus->getName('en'),
"nameAr" => $alertStatus->getName('ar'),
"imageTemplate" => $alertStatus->getimageTemplate(),
"alertType" => $alertData
];
}
}
return ["success" => true, "data" => $response];
}
public function getGovernorates()
{
$response = [];
$governorates = new DataObject\Governorate\Listing();
$Governorates = $governorates->load();
if ($governorates) {
foreach ($governorates as $governorate) {
$region = $governorate->getregionId();
$regionData = [];
if (!empty($region)) {
$regionData = [
"id" => $region->getRegionId(),
"nameEn" => $region->getName('en'),
"nameAr" => $region->getName('ar'),
"longitude" => $region->getLongitude(),
"latitude" => $region->getLongitude()
];
}
$response[] = [
"id" => $governorate->getgovernoteId(),
"nameEn" => $governorate->getName('en'),
"nameAr" => $governorate->getName('ar'),
"regionId" => $regionData,
"longitude" => $governorate->getLongitude(),
"latitude" => $governorate->getLatitude(),
"isHidden" => $governorate->getisHidden(),
"IsMunicipality" => $governorate->getIsMunicipality(),
"MunicipalityID" => $governorate->getMunicipalityID()
];
}
}
return ["success" => true, "data" => $response];
}
public function getMunicipality($governorateId = null, $lang = 'en')
{
$response = [];
$municipalities = new DataObject\Municipality\Listing();
if (is_array($governorateId)) {
$govIdsArr = [];
foreach ($governorateId as $govId) {
$governorate = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($govId, true);
if ($governorate) {
array_push($govIdsArr, $governorate->getId());
}
}
$municipalities->setCondition("governorate__id IN (" . implode(", ", $govIdsArr) . ")");
} else {
if ($governorateId) {
$governorate = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($governorateId, true);
$municipalities->setCondition("governorate__id = ?", [$governorate->getId()]);
}
}
$municipalities = $municipalities->load();
if ($municipalities) {
foreach ($municipalities as $municipality) {
$response[] = [
"id" => $municipality->getMunicipalityid(),
"nameEn" => $municipality->getName('en'),
"nameAr" => $municipality->getName('ar'),
"longitude" => $municipality->getLongitude(),
"latitude" => $municipality->getLatitude(),
"governate" => $municipality->getGovernorate()->getGovernoteId()
];
}
}
// Determine sorting field based on language
$sortField = 'nameEn'; // Default sorting by English
if (isset($lang) && strtolower($lang) === 'ar') {
$sortField = 'nameAr'; // Sorting by Arabic
}
// Sort manually using usort()
usort($response, function ($a, $b) use ($sortField) {
return strcmp($a[$sortField], $b[$sortField]);
});
return ["success" => true, "data" => $response];
}
public function getGovernoratesByRegion($params)
{
$response = [];
$governorates = new DataObject\Governorate\Listing();
if (isset($params['region_id'])) {
$region = \Pimcore\Model\DataObject\Region::getByRegionId($params['region_id'], true);
if (!$region) {
throw new \Exception("Region not available");
}
$governorates->filterByRegionId($region);
}
if (isset($params['region_ids']) && !empty($params['region_ids'])) {
$regionIds = [];
$regionList = new DataObject\Region\Listing();
$regionList->addConditionParam("regionId IN (?)", [$params['region_ids']]);
foreach ($regionList as $region) {
$regionIds[] = $region->getId();
}
$governorates->addConditionParam("regionId__id IN (?)", [$regionIds]);
}
// Load governorates without sorting in Pimcore
$governorates = $governorates->load();
// Convert to array for manual sorting
$count = 0;
if ($governorates) {
foreach ($governorates as $governorate) {
$region = $governorate->getregionId();
$regionData = [];
if (!empty($region)) {
$regionData = [
"id" => $region->getRegionId(),
"nameEn" => $region->getName('en'),
"nameAr" => $region->getName('ar'),
"longitude" => $region->getLongitude(),
"latitude" => $region->getLatitude()
];
}
$response[$count] = [
"id" => $governorate->getgovernoteId(),
"nameEn" => $governorate->getName('en'),
"nameAr" => $governorate->getName('ar'),
"regionId" => $regionData,
"longitude" => $governorate->getLongitude(),
"latitude" => $governorate->getLatitude(),
"isHidden" => $governorate->getisHidden(),
"IsMunicipality" => $governorate->getIsMunicipality(),
"MunicipalityID" => $governorate->getMunicipalityID(),
"bbox" => $governorate->getBbox()
];
if ($governorate->getMunicipalityID() && $governorate->getIsMunicipality()) {
$municipality = \Pimcore\Model\DataObject\Municipality::getBymunicipalityid($governorate->getMunicipalityID(), true);
if ($municipality) {
$muniGovernate = $municipality->getgovernorate();
if ($muniGovernate) {
$response[$count]["parent_id"] = $muniGovernate->getgovernoteId();
$response[$count]["parent_longitude"] = $muniGovernate->getLongitude();
$response[$count]["parent_latitude"] = $muniGovernate->getLatitude();
$response[$count]["parent_name_en"] = $muniGovernate->getName("en");
$response[$count]["parent_name_ar"] = $muniGovernate->getName("ar");
}
}
}
$count++;
}
}
// Determine sorting field based on language
$sortField = 'nameEn'; // Default sorting by English
if (isset($params['lang']) && strtolower($params['lang']) === 'ar') {
$sortField = 'nameAr'; // Sorting by Arabic
}
// Sort manually using usort()
usort($response, function ($a, $b) use ($sortField) {
return strcmp($a[$sortField], $b[$sortField]);
});
return ["success" => true, "data" => $response];
}
public function getVersions($notificationObject, $translator, $decodedJwtToken = null, $userPermission = null)
{
$snapshots = $this->collectUpdateVersionSnapshots($notificationObject);
$resultAscending = [];
foreach ($snapshots as $index => $snap) {
/** @var EwsNotification $viewNotification */
$viewNotification = $snap['obj'];
$previous = $index > 0 ? $snapshots[$index - 1]['obj'] : null;
$formatted = $this->createNotificationFormat($viewNotification, $translator, $decodedJwtToken, $userPermission);
$formatted['updateChanges'] = $this->buildNotificationUpdateChanges(
$viewNotification,
$previous instanceof EwsNotification ? $previous : null,
$translator
);
$resultAscending[] = $formatted;
}
// Newest first (matches Pimcore getVersions() iteration order).
return array_reverse($resultAscending);
}
/**
* Format live notification payload and attach updateChanges vs previous Update version.
*/
private function formatNotificationWithUpdateChanges(
EwsNotification $notification,
$translator,
$decodedJwtToken = null,
$userPermission = null
): array {
$formatted = $this->createNotificationFormat($notification, $translator, $decodedJwtToken, $userPermission);
$formatted['updateChanges'] = $this->buildNotificationUpdateChanges(
$notification,
$this->resolvePreviousUpdateForCurrent($notification),
$translator
);
return $formatted;
}
/**
* Collect Update-note version snapshots oldest → newest.
*
* @return array<int, array{ts: int, obj: EwsNotification}>
*/
private function collectUpdateVersionSnapshots(EwsNotification $notification): array
{
$snapshots = [];
$versions = $notification->getVersions() ?: [];
foreach ($versions as $version) {
if ($version->getNote() !== 'Update') {
continue;
}
$view = $version->loadData();
if (!$view instanceof EwsNotification) {
continue;
}
$snapshots[] = [
'ts' => (int) ($version->getDate() ?: $view->getModificationDate() ?: $view->getCreationDate()),
'obj' => $view,
];
}
usort($snapshots, static fn($a, $b) => ($a['ts'] ?? 0) <=> ($b['ts'] ?? 0));
return array_values($snapshots);
}
private function resolvePreviousUpdateForCurrent(EwsNotification $current): ?EwsNotification
{
$snapshots = $this->collectUpdateVersionSnapshots($current);
if ($snapshots === []) {
return null;
}
$lastIndex = count($snapshots) - 1;
$last = $snapshots[$lastIndex];
$lastMod = (int) ($last['obj']->getModificationDate() ?: 0);
$currentMod = (int) ($current->getModificationDate() ?: 0);
// Latest Update version is usually the same state as the live object.
if ($lastMod === $currentMod || (int) ($last['ts'] ?? 0) >= $currentMod) {
return $lastIndex >= 1 ? $snapshots[$lastIndex - 1]['obj'] : null;
}
return $last['obj'];
}
/**
* Text describing what changed, plus map images from polygonXAtachment.
* - Location or alertType change → previous + after images
* - Any other update → single current image
* Text fields are always returned in both en and ar.
*
* @return array{
* textEn: string,
* textAr: string,
* items: array<int, array{key: string, titleEn: string, titleAr: string, detailEn: string, detailAr: string}>,
* images: array{previous: string, current: string}
* }
*/
private function buildNotificationUpdateChanges(
EwsNotification $current,
?EwsNotification $previous,
$translator
): array {
$timezone = new \DateTimeZone(defined('TIMEZONE') ? TIMEZONE : 'Asia/Riyadh');
// Canonical action keys (language-independent), titles resolved per locale below.
$actionKeys = [];
$seenKeys = [];
foreach ($this->extractRelevantHistoryActions($current, $translator, 'en') as $actionMeta) {
$key = (string) ($actionMeta['key'] ?? '');
if ($key === '' || isset($seenKeys[$key])) {
continue;
}
$seenKeys[$key] = true;
$actionKeys[] = $key;
}
$hasLocationUpdate = in_array('Update Alert Location', $actionKeys, true);
$hasAlertTypeUpdate = in_array('Raise Alert', $actionKeys, true) || in_array('Lower Alert', $actionKeys, true);
// Fallback when alertAction labels are missing but fields clearly changed.
if ($previous) {
if (!$hasAlertTypeUpdate && $this->hasSeverityChanged($current, $previous)) {
$hasAlertTypeUpdate = true;
$actionKeys[] = $this->resolveRaiseOrLowerKey($previous, $current);
}
if (!$hasLocationUpdate && $this->hasLocationChanged($current, $previous)) {
$hasLocationUpdate = true;
$actionKeys[] = 'Update Alert Location';
}
}
$items = [];
$textPartsEn = [];
$textPartsAr = [];
foreach ($actionKeys as $key) {
$titleEn = $this->resolveUpdateChangeTitle($key, $current, $translator, 'en');
$titleAr = $this->resolveUpdateChangeTitle($key, $current, $translator, 'ar');
$detailEn = $this->buildAlertHistoryActionDetail(
$key,
$current,
$previous,
$timezone,
$translator,
'en'
);
$detailAr = $this->buildAlertHistoryActionDetail(
$key,
$current,
$previous,
$timezone,
$translator,
'ar'
);
$items[] = [
'key' => $key,
'titleEn' => $titleEn,
'titleAr' => $titleAr,
'detailEn' => $detailEn,
'detailAr' => $detailAr,
];
$textPartsEn[] = $detailEn !== '' ? ($titleEn . ': ' . $detailEn) : $titleEn;
$textPartsAr[] = $detailAr !== '' ? ($titleAr . ': ' . $detailAr) : $titleAr;
}
$currentImage = $this->getPolygonXAttachmentUrl($current) ?? '';
if ($hasLocationUpdate || $hasAlertTypeUpdate) {
// Location / alertType change → before & after map images
$images = [
'previous' => $previous ? ($this->getPolygonXAttachmentUrl($previous) ?? '') : '',
'current' => $currentImage,
];
} else {
// All other updates → same shape, previous empty
$images = [
'previous' => '',
'current' => $currentImage,
];
}
return [
'textEn' => implode("\n", array_filter($textPartsEn, static fn($t) => $t !== '')),
'textAr' => implode("\n", array_filter($textPartsAr, static fn($t) => $t !== '')),
'items' => $items,
'images' => $images,
];
}
private function resolveUpdateChangeTitle(
string $key,
EwsNotification $notification,
$translator,
string $lang
): string {
$actions = $notification->getAlertAction() ?: [];
foreach ($actions as $action) {
if (!$action) {
continue;
}
$mapped = $this->mapAlertActionToHistoryTitle((string) $action->getName('en'));
if ($mapped === $key) {
$localized = (string) ($action->getName($lang) ?: '');
if ($localized !== '') {
return $localized;
}
break;
}
}
return $this->transAlertHistory($key, $translator, $lang);
}
private function getPolygonXAttachmentUrl(?EwsNotification $notification): ?string
{
if (!$notification) {
return null;
}
$asset = $notification->getPolygonXAtachment();
if (!$asset || !method_exists($asset, 'getFullPath')) {
return null;
}
$path = (string) $asset->getFullPath();
if ($path === '') {
return null;
}
return BASE_URL . $path;
}
private function hasSeverityChanged(EwsNotification $current, EwsNotification $previous): bool
{
$from = strtolower((string) ($previous->getAlertType()?->getColor() ?? ''));
$to = strtolower((string) ($current->getAlertType()?->getColor() ?? ''));
return $from !== '' && $to !== '' && $from !== $to;
}
private function hasLocationChanged(EwsNotification $current, EwsNotification $previous): bool
{
$currentGov = $this->extractGovernorateNamesForHistory($current, 'en');
$previousGov = $this->extractGovernorateNamesForHistory($previous, 'en');
sort($currentGov);
sort($previousGov);
if ($currentGov !== $previousGov) {
return true;
}
$currentCoords = (string) ($current->getCoordinates() ?? '');
$previousCoords = (string) ($previous->getCoordinates() ?? '');
if ($currentCoords !== $previousCoords) {
return true;
}
$currentPolygonId = $current->getPolygon()?->getId();
$previousPolygonId = $previous->getPolygon()?->getId();
return $currentPolygonId !== $previousPolygonId;
}
private function resolveAlertSeverityLabel(EwsNotification $notification, string $lang = 'en', $translator = null): string
{
$color = (string) ($notification->getAlertType()?->getColor() ?? '');
if ($color === '') {
return '';
}
$label = ucwords(strtolower($color));
return $this->transAlertHistory($label, $translator, $lang);
}
private function resolveRaiseOrLowerKey(EwsNotification $previous, EwsNotification $current): string
{
$rank = ['green' => 1, 'yellow' => 2, 'orange' => 3, 'red' => 4];
$from = strtolower((string) ($previous->getAlertType()?->getColor() ?? ''));
$to = strtolower((string) ($current->getAlertType()?->getColor() ?? ''));
$fromRank = $rank[$from] ?? 0;
$toRank = $rank[$to] ?? 0;
return $toRank < $fromRank ? 'Lower Alert' : 'Raise Alert';
}
private function resolveRaiseOrLowerTitle(
EwsNotification $previous,
EwsNotification $current,
$translator,
string $lang
): string {
return $this->transAlertHistory(
$this->resolveRaiseOrLowerKey($previous, $current),
$translator,
$lang
);
}
public function ewsNotificationHasVersionUpdateHistory(EwsNotification $notification): bool
{
$updateCount = 0;
foreach ($notification->getVersions() ?: [] as $version) {
if ($version->getNote() === 'Update') {
$updateCount++;
if ($updateCount > 1) {
return true;
}
}
}
return false;
}
private function createNotificationFormat($notification, $translator, $decodedJwtToken = null, $userPermission = null)
{
$response = [];
$critetia = '';
$critetiaAr = '';
$status = 'active';
$alertType = $notification->getAlertType();
$phenomena = $notification->getWeatherPhenomenon();
// Set the timezone to Asia/Riyadh
$timezone = new \DateTimeZone(TIMEZONE);
// Convert start and end dates to Asia/Riyadh timezone
$startDate = $notification->getStartDate() ? $notification->getStartDate()->setTimezone($timezone) : null;
$endDate = $notification->getEndDate() ? $notification->getEndDate()->setTimezone($timezone) : null;
$alertEndDate = $notification->getAlertEndDate() ? $notification->getAlertEndDate()->setTimezone($timezone) : null;
if ($alertType && $phenomena) {
$phenomenaList = $alertType->getPhenomenaList();
if ($phenomenaList) {
$items = $phenomenaList->getItems();
if ($items) {
foreach ($items as $item) {
if ($item->getPhenomena() == $phenomena->getId()) {
$critetia = $item->getCriteria();
$critetiaAr = $item->getCriteriaAr();
break;
}
}
}
}
}
if ($notification->getStatus() != "ended") {
if ($endDate && $endDate < new \DateTime('now', $timezone)) {
$status = "expired";
}
$twentyFourHoursAgo = (new \DateTime('now', $timezone))->modify('-24 hours');
if ($endDate && $endDate < $twentyFourHoursAgo) {
$status = "archived";
}
} else {
$twentyFourHoursAgo = (new \DateTime('now', $timezone))->modify('-24 hours');
if ($alertEndDate && $alertEndDate < $twentyFourHoursAgo) {
$status = "archived";
} else {
$status = "ended";
}
}
$hideUnpublishedBackup = DataObject::doHideUnpublished();
DataObject::setHideUnpublished(false);
$polygonId = $notification->getPolygon()?->getId();
$linkedAlertIds = array_map(
fn($obj) => $obj->getId(),
$notification->getPolygon()?->getEwsAlerts(true) ?? []
);
$polCoords = $notification->getPolygon() ? json_decode($notification->getPolygon()->getcoordinates()) : [];
$polExpired = $notification->getPolygon() && $notification->getPolygon()->getExpire() ? $notification->getPolygon()->getExpire() : null;
$polPublished = $notification->getPolygon() ? $notification->getPolygon()->getPublished() : false;
DataObject::setHideUnpublished($hideUnpublishedBackup);
// Initialize edit and delete flags
$editFlag = false;
// Get the current report's type key
$notificationType = $notification->getNotificationType();
// Map report type keys to permission names
$permissionMapping = [
'Default' => 'edit_ews_notification',
'Polygon' => 'update_ews_notification_polygon',
'Map' => 'update_ews_notification_map'
];
// Check permissions only for the current report type
// if ($notificationType && isset($permissionMapping[$notificationType]) && !empty($userPermission)) {
// $permissionName = $permissionMapping[$notificationType];
// $permissionStatus = $userPermission->getUserPermissions($decodedJwtToken, $translator);
// // Check edit permission
// if (isset($permissionStatus['grants']["$permissionName"]) && $permissionStatus['grants']["$permissionName"] === true) {
// $editFlag = true;
// }
// }
$response = [
"id" => $notification->getId(),
"searchEwsIdEn" => $notification->getEwsSearchId("en"),
"searchEwsIdAr" => $notification->getEwsSearchId("ar"),
"title" => $startDate ? $startDate->format("dmY") . '-' . $notification->getId() : '',
"alertType" => !empty($notification->getAlertType()) ? $notification->getAlertType()->getAlertTypeId() : "",
"alertTypeAr" => !empty($notification->getAlertType()) ? $notification->getAlertType()->getName("ar") : "",
"alertTypeEn" => !empty($notification->getAlertType()) ? ucwords($notification->getAlertType()->getColor()) : "",
"fromDate" => $startDate ? $startDate->format("Y-m-d H:i:s") : "",
"toDate" => $endDate ? $endDate->format("Y-m-d H:i:s") : "",
"alertEndDate" => $alertEndDate && $status == "ended" ? $alertEndDate->format("Y-m-d H:i:s") : null,
"alertStatusID" => !empty($notification->getAlertStatus()) ? $notification->getAlertStatus()->getAlertStatusId() : "",
"alertStatusAr" => !empty($notification->getAlertStatus()) ? $notification->getAlertStatus()->getName("ar") : "",
"alertStatusEn" => !empty($notification->getAlertStatus()) ? $notification->getAlertStatus()->getName("en") : "",
"alertStatusCategory" => "",
"alertHazard" => !empty($notification->getAlertHazard()) ? $this->getAlertHazardArr($notification->getAlertHazard()) : [],
"regionID" => !empty($notification->getRegion()) ? $notification->getRegion()->getRegionId() : "",
"regionAR" => !empty($notification->getRegion()) ? $notification->getRegion()->getName("ar") : "",
"regionEn" => !empty($notification->getRegion()) ? $notification->getRegion()->getName("en") : "",
"governorates" => ($notification->getRegion()) ? $this->getGovernorateDetail($notification->getGovernorate()) : [],
"mapGovernorates" => $notification->getMapGovernorate() ? $this->getGovernorateDetail($notification->getMapGovernorate()) : [],
"ewsOtherLocations" => ($notification->getRegion()) ? $this->getGovernorateDetail($notification->getEwsOtherLocations()) : [],
"otherLocationsAr" => $this->getOtherLocationsNames($notification->getEwsOtherLocations(), 'ar'),
"otherLocationsEn" => $this->getOtherLocationsNames($notification->getEwsOtherLocations(), 'en'),
"tweetID" => "",
"enableTwitterNotification" => $notification->getEnableTwitterNotification(),
"enableSMSNotification" => $notification->getEnableSMSNotification(),
"enableEmailNotification" => $notification->getEnableEmailNotification(),
"alertActions" => $this->getAlertActionsByArr($notification->getAlertAction()),
"municipalities" => $this->getMunicipalityArr($notification->getMunicipality()),
"centers" => $this->getCenterArr($notification->getCenter()),
"districts" => $this->getDistrictArr($notification->getDistrict()),
"lastModified" => ($notification->getModificationDate()) ? date("Y-m-d H:i:s", $notification->getModificationDate()) : "",
"last_modified_date" => ($notification->getModificationDate()) ? date("Y-m-d", $notification->getModificationDate()) : "",
'coordinates' => $notification->getCoordinates(),
'message' => $notification->getMessage("en"),
"message_en" => Bilingual::to($notification->getMessage("en"), 'en'),
"message_ar" => Bilingual::to($notification->getMessage("en"), 'ar'),
"file" => (!empty($notification->getAttachment())) ? API_BASE_URL . $notification->getAttachment()->getFullPath() : [],
'criteria' => $critetia,
'criteriaAr' => $critetiaAr,
'created_at' => ($notification->getCreationDate()) ? date("Y-m-d H:i:s", $notification->getCreationDate()) : "",
'created_by' => ($notification->getUser()) ? $notification->getUser()->getName() : "",
'edited_by' => ($notification->getEditor()) ? $notification->getEditor()->getName() : (($notification->getUser()) ? $notification->getUser()->getName() : ""),
'status_en' => ucfirst($status),
'status_ar' => $translator->trans(ucfirst($status), [], null, 'ar'),
'previewText' => $notification->getPreviewText() ?? false,
'isPolygon' => $notification->getIsPolygon(),
'linkedAlerts' => $linkedAlertIds,
'polygonId' => $polygonId,
'polygonCoordinates' => $polCoords,
'polygonExpired' => $polExpired,
'polygonPublished' => $polPublished,
'canEdit' => $editFlag,
'notificationType' => $notification->getNotificationType(),
'alertPage' => $notification->getAlertPage(),
'x_post' => $notification->getXPost(),
'user_group_ids' => array_map(
fn($group) => $group->getId(),
$notification->getUserGroup()
),
];
return $response;
}
/**
* Same payload shape as one element in device API search-ews-notification "data" (plus optional caller fields).
*/
public function buildSearchEwsNotificationAlertPayload(
EwsNotification $notification,
$translator,
$decodedJwtToken = null,
$userPermission = null
): array {
return $this->createNotificationFormat($notification, $translator, $decodedJwtToken, $userPermission);
}
private function getAlertHazardArr($alertHazardArr)
{
$result = [];
if ($alertHazardArr) {
foreach ($alertHazardArr as $affect) {
$result[] = [
"pim_id" => $affect->getId(),
"id" => $affect->getAlertHazardId(),
"nameEn" => $affect->getName("en"),
"nameAr" => $affect->getName("ar"),
];
}
}
return $result;
}
private function getAlertActionsByArr($alertActions)
{
$result = [];
if ($alertActions) {
foreach ($alertActions as $alert) {
$result[] = [
"pim_id" => $alert->getId(),
"id" => $alert->getAlertActionId(),
"descriptionEn" => $alert->getName("en"),
"descriptionAr" => $alert->getName("ar"),
];
}
}
return $result;
}
private function getMunicipalityArr($municipalities)
{
$result = [];
if ($municipalities) {
foreach ($municipalities as $municipality) {
// p_R($municipality->getName());
$result[] = [
"id" => $municipality->getMunicipalityId(),
"nameEn" => $municipality->getName("en"),
"nameAr" => $municipality->getName("ar"),
"governate" => $municipality->getGovernorate()->getGovernoteId()
];
}
}
return $result;
}
private function getDistrictArr($districts)
{
$result = [];
if ($districts) {
foreach ($districts as $district) {
$result[] = [
"id" => $district->getId(),
"nameEn" => $district->getName("en"),
"nameAr" => $district->getName("ar"),
"longitude" => $district->getLongitude(),
"latitude" => $district->getLatitude(),
"governate" => $district->getGovernorate()->getGovernoteId()
];
}
}
return $result;
}
private function getCenterArr($centers)
{
$result = [];
if ($centers) {
foreach ($centers as $center) {
$result[] = [
"id" => $center->getId(),
"nameEn" => $center->getName("en"),
"nameAr" => $center->getName("ar"),
"longitude" => $center->getLongitude(),
"latitude" => $center->getLatitude(),
"governate" => $center->getGovernorate()->getGovernoteId()
];
}
}
return $result;
}
private function getOtherLocationsNames($otherLocations, $lang = 'ar')
{
$names = []; // Step 1: Initialize an array to hold the names
if ($otherLocations) {
foreach ($otherLocations as $otherLocation) {
$names[] = $otherLocation->getName($lang); // Step 2 & 3: Extract and collect names
}
}
if (empty($names)) { // Check if the names array is empty
return ''; // Return an empty string if there are no names
}
$namesString = implode(', ', $names); // Step 4: Convert the array to a comma-separated string
return $namesString; // Return or use the comma-separated string as needed
}
private function shouldDispatchPublicPortalEwsWebhook(EwsNotification $notification): bool
{
if ($notification->getPublished()) {
return true;
}
$status = (string) $notification->getStatus();
return $status === 'ended' || $status === 'archived';
}
private function dispatchPublicPortalEwsWebhookIfConfigured(int $notificationId, ?LoggerInterface $logger = null): void
{
try {
PublicPortalEwsWebhookProcessLauncher::dispatch($notificationId, $logger);
} catch (\Throwable $e) {
if ($logger !== null) {
$logger->warning('Public portal EWS webhook dispatch failed: ' . $e->getMessage());
}
}
}
public function publishEwsNotification($notificationId, $published, $userGroupIds, $translator, $emailService, $templating, $logger)
{
$result = [];
$viewNotification = DataObject\EwsNotification::getById($notificationId, false);
if (!$viewNotification) {
$viewNotification = DataObject\EwsNotification::getById($notificationId, true);
}
if ($published) {
$viewNotification->setStatus("active");
} else {
$viewNotification->setStatus("");
}
$viewNotification->setPublished($published);
//set ews search Id
$currentDate = new \DateTime();
$formattedDate = $currentDate->format('dmY') . '-' . $viewNotification->getId();
$searchIdEn = 'Early Warning System | ' . $formattedDate . ' | ' . ucfirst($viewNotification->getAlertType()?->getColor()) . ' Alert | ' . $viewNotification->getWeatherPhenomenon()?->getTitle("en");
$searchIdAr = $translator->trans('Early Warning System', [], null, "ar") . ' | ' . $formattedDate . ' | ' . $translator->trans(ucfirst($viewNotification->getAlertType()?->getColor()) . ' Alert', [], null, "ar") . ' | ' . $viewNotification->getWeatherPhenomenon()?->getTitle("ar");
$viewNotification->setEwsSearchId($searchIdEn, "en");
$viewNotification->setEwsSearchId($searchIdAr, "ar");
// // When publishing an updated alert to Twitter, also generate history on republish path.
// if ($published && $viewNotification->getEnableTwitterNotification() && $templating) {
// try {
// if ($this->ewsNotificationHasVersionUpdateHistory($viewNotification)) {
// $historyLang = method_exists($translator, 'getLocale') ? (string) $translator->getLocale() : 'en';
// $historyAsset = $this->generateAlertHistoryTwitterImage(
// $viewNotification,
// $templating,
// $logger,
// false,
// $translator,
// $historyLang
// );
// if ($historyAsset) {
// $viewNotification->setXHistoryAtachment($historyAsset);
// }
// }
// } catch (\Throwable $e) {
// $logger->error('Failed to generate EWS alert history Twitter image on publish: ' . $e->getMessage());
// }
// }
// Allow TwitterEventListner to post again on republish after an alert update.
if ($published && $viewNotification->getEnableTwitterNotification()) {
$viewNotification->setTwitterId('');
$viewNotification->setTwitterLog('');
}
// Persist the selected user groups so the End alert can email the same groups later.
if (!empty($userGroupIds)) {
$groups = [];
foreach ($userGroupIds as $gid) {
$group = EwsAndReportUserGroup::getById($gid, true);
if ($group instanceof EwsAndReportUserGroup) {
$groups[] = $group;
}
}
$viewNotification->setUserGroup($groups);
}
$viewNotification->save(["versionNote" => "Update"]);
// $alert = $this->createNotificationFormat($viewNotification, $translator);
// Commenting out the public portal EWS webhook dispatch as not working correctly 2026-08-17.
// $this->dispatchPublicPortalEwsWebhookIfConfigured((int) $viewNotification->getId(), $logger);
if ($published) {
// Emails stay on the same console command; run it in the background so this API is not blocked.
$jsonUserGroupIds = json_encode($userGroupIds);
try {
ConsoleBackgroundProcess::start([
'php',
'bin/console',
'app:send-early-warning-alert-email',
'--alertId=' . $viewNotification->getId(),
'--userGroupIds=' . $jsonUserGroupIds,
]);
$logger->info('Started send-early-warning-alert-email in background for EwsNotification ' . $viewNotification->getId());
} catch (\Throwable $exception) {
$logger->error("published EwsNotification command failed to start: " . $exception->getMessage());
return ['success' => false, 'message' => $exception->getMessage()];
}
}
return ["success" => true, "message" => $translator->trans("ews_notification_published")];
}
public function previewEwsNotification($params, $user, $translator, $templating)
{
$regionId = $params['regionId'] ?? null;
$governorateIds = $params['governateIds'] ?? null;
$startDate = $params['startDate'] ?? null;
$endDate = $params['endDate'] ?? null;
$startTime = $params['startTime'] ?? null;
$endTime = $params['endTime'] ?? null;
$alertActionId = $params['alertActionId'] ?? null;
$alertTypeId = $params['alertTypeId'] ?? null;
$alertStatusId = $params['alertStatusId'] ?? null;
$alertHazardId = $params['alertHazardId'] ?? null;
$alertItem = AlertType::getByAlertTypeId($this->scalarId($alertTypeId), 1);
$regionItem = Region::getByRegionId($this->scalarId($regionId), 1);
$alertStatusItem = AlertStatus::getByAlertStatusId($this->scalarId($alertStatusId), 1);
$govIds = $this->parseCommaSeparatedIds($governorateIds);
$govById = [];
if (!empty($govIds)) {
$governorateArr = new Governorate\Listing();
$governorateArr->setCondition('governoteId IN (?)', [$govIds]);
foreach ($governorateArr as $item) {
$govById[(string) $item->getgovernoteId()] = $item;
}
}
$govItems = [];
foreach ($govIds as $govId) {
if (!isset($govById[(string) $govId])) {
continue;
}
$item = $govById[(string) $govId];
$govItems[] = [
"id" => $item->getgovernoteId(),
"nameEn" => $item->getName('en'),
"nameAr" => $item->getName('ar'),
"longitude" => (float)$item->getLongitude(),
"latitude" => (float)$item->getLatitude(),
];
}
$hazardIds = $this->parseCommaSeparatedIds($alertHazardId);
$hazardById = [];
if (!empty($hazardIds)) {
$hazardArr = new AlertHazard\Listing();
$hazardArr->setCondition('alertHazardId IN (?)', [$hazardIds]);
foreach ($hazardArr as $item) {
$hazardById[(string) $item->getAlertHazardId()] = $item;
}
}
$hazardItems = [];
foreach ($hazardIds as $hazardId) {
if (!isset($hazardById[(string) $hazardId])) {
continue;
}
$item = $hazardById[(string) $hazardId];
$hazardItems[] = [
"pim_id" => $item->getId(),
"id" => $item->getAlertHazardId(),
"nameEn" => $item->getName('en'),
"nameAr" => $item->getName('ar'),
];
}
$actionIds = $this->parseCommaSeparatedIds($alertActionId);
$actionById = [];
if (!empty($actionIds)) {
$actionArr = new AlertAction\Listing();
$actionArr->setCondition('alertActionId IN (?)', [$actionIds]);
foreach ($actionArr as $item) {
$actionById[(string) $item->getAlertActionId()] = $item;
}
}
$actionItems = [];
foreach ($actionIds as $actionId) {
if (!isset($actionById[(string) $actionId])) {
continue;
}
$item = $actionById[(string) $actionId];
$actionItems[] = [
"pim_id" => $item->getId(),
"id" => $item->getAlertActionId(),
"descriptionEn" => $item->getName('en'),
"descriptionAr" => $item->getName('ar'),
];
}
// Preview only: reuse an existing token or mint a display token. Do not persist
// a new unsubscribe token on the user (that was a write on every preview).
$tokenURL = '';
$token = '';
if (is_object($user) && method_exists($user, 'getEwsNotificationToken')) {
$token = (string) $user->getEwsNotificationToken();
}
if ($token === '') {
$token = base64_encode($user->getEmail() . time() . uniqid());
}
if ($token !== '') {
$tokenURL = BASE_URL . '/unsubscribe/notification?ews=true&token=' . $token;
}
$currentDate = new \DateTime();
$dummyId = "ID";
$dummyIdAr = "الهوية";
$formattedDate = $currentDate->format('dmY') . '-' . $dummyId;
$formattedDateAr = $currentDate->format('dmY') . '-' . $dummyIdAr;
$searchIdEn = 'Early Warning System | ' . $formattedDate . ' | ' . ucfirst($alertItem->getColor()) . ' Alert | ' ;
$searchIdAr = $translator->trans('Early Warning System', [], null, "ar") . ' | ' . $formattedDateAr . ' | ' . $translator->trans(ucfirst($alertItem->getColor()) . ' Alert', [], null, "ar") . ' | ' ;
$fromDate = "{$startDate} " . ($startTime ? $startTime : '00:00:00');
$toDate = "{$endDate} " . ($endTime ? $endTime : '00:00:00');
$message = [
"en" => "There is {$alertStatusItem->getName('en')} alert in {$regionItem->get('name', 'en')} Start Time : {$fromDate} End Time : {$toDate}",
"ar" => "يوجد تنبيه {$alertStatusItem->getName('ar')} في {$regionItem->get('name', 'ar')} وقت البداية: {$fromDate} وقت الانتهاء: {$toDate}"
];
$alert = [
"user_name" => $user->getName(),
"host" => API_BASE_URL,
"tokenURL" => $tokenURL,
"title" => $formattedDate,
"alertColor" => $alertItem->getColor(),
"alertType" => $alertItem->getAlertTypeId(),
"alertTypeAr" => $alertItem->getName('ar'),
"alertTypeEn" => $alertItem->getColor(),
"fromDate" => "{$startDate} {$startTime}",
"toDate" => "{$endDate} {$endTime}",
"mannedAlertDatailUrl" => null,
"searchEwsIdEn" => $searchIdEn,
"searchEwsIdAr" => $searchIdAr,
"alertHazard" => $hazardItems,
"alertActions" => $actionItems,
"alertStatusID" => $alertStatusItem->getalertStatusId(),
"alertStatusEn" => $alertStatusItem->getName('en'),
"alertStatusAr" => $alertStatusItem->getName('ar'),
"regionID" => $regionId,
"regionEn" => $regionItem->get('name', 'en'),
"regionAR" => $regionItem->get('name', 'ar'),
"governorates" => $govItems,
"last_modified_date" => date("Y-m-d"),
];
$html = $templating->render('web2print/_manned_alert_notification_ar.html.twig', $alert);
return ["success" => true, "emailHtml" => $html, "smsMsg" => $message['ar']];
}
public function reportEwsNotification($param, $translator)
{
$result = [];
$region = null;
try {
$listing = new EwsNotification\Listing();
$reportType = isset($param['report_type']) ? $param['report_type'] : null;
$regionId = isset($param['region_id']) ? $param['region_id'] : null;
if ($regionId) {
$region = \Pimcore\Model\DataObject\Region::getByRegionId($regionId, true);
if (!$region) {
throw new \Exception("Region not exists");
}
}
switch ($reportType) {
case 'published_in_week':
$listing->setCondition("o_published = true AND o_creationDate >= " . strtotime("-1 week"));
break;
case 'published_in_day':
$listing->setCondition("o_published = true AND o_creationDate >= " . strtotime("-1 day"));
break;
case 'published_in_month':
$listing->setCondition("o_published = true AND o_creationDate >= " . strtotime("-1 month"));
break;
case 'published_in_year':
$listing->setCondition("o_published = true AND o_creationDate >= " . strtotime("-1 year"));
break;
case 'published_in_region':
$listing->setCondition("o_published = true AND region__id = ?", [$region->getId()]);
break;
case 'published_in_draft':
$listing->setCondition("o_published = false");
break;
default:
throw new \Exception("Invalid request");
break;
}
$result['count'] = $listing->getCount();
$notifications = $listing->load();
if ($notifications) {
foreach ($notifications as $notification) {
$result['data'][] = $this->createNotificationFormat($notification, $translator);
}
}
return $result;
} catch (\Exception $ex) {
return new \Exception($ex->getMessage());
}
return $result;
}
private function sendEmailNotification($users, $alert, $emailService, $templating)
{
$mailSent = null;
if ($alert) {
$governatesArr = $alert['governorates'];
$governates = [];
if ($governatesArr) {
foreach ($governatesArr as $gov) {
$governates[] = $gov['nameEn'];
}
}
$data = $alert;
$data['sender'] = 'National Center for Meteorology';
//$subject = $alert['searchEwsIdAr'] ??'Severe Weather Alert - ' . $gov['nameEn'];
// $subject = $alert['alertStatusAr'] . ' - ' . $alert['regionAR'];
// $subject = 'النظام الالي للإنذار المبكر :' . ' - ' . $alert['searchEwsIdAr'];
$parts = explode('|', $alert['searchEwsIdAr']);
$excluded = implode('|', array_slice($parts, 1));
$subject = 'النظام الالي للإنذار المبكر :' . $excluded;
// $html = $templating->render('web2print/_manned_alert_notification_ar.html.twig', $data);
//sending an email document (pimcore document)
// $emailAlertTemplate = '/email/alert_notification';
if ($users) {
foreach ($users as $currentUser) {
if (isset($alert['enableEmailNotification']) && $alert['enableEmailNotification']) {
if ($currentUser->getSendEwsEmail() || is_null($currentUser->getSendEwsEmail())) {
# code...
// $email = $currentUser->getEmail();
// $param = ['user' => $currentUser, 'message' => $html, 'url' => null];
// $mailSent = $emailService->sendMail($param, $email, $emailAlertTemplate, $subject);
// $data['name'] = $user->getName();
// extra param js
$alertObj = \Pimcore\Model\DataObject\AlertType::getByAlertTypeId($alert['alertType'], true);
$alertName = $alertObj->getColor() ? $alertObj->getColor() : '';
$alert['user_name'] = $currentUser->getName();
$alert['host'] = API_BASE_URL;
$alert['alertColor'] = $alertName;
$backgroundColor = '#fcb82526';
$borderColor = '#000000';
$textColor = '#000000';
// unsubscribe ews notificaiton token
$tokenURL = '';
$token = $this->userModel->unSubscribeEwsGenerateToken($currentUser->getEmail());
if (!empty($token)) {
$tokenURL = BASE_URL . '/unsubscribe/notification?ews=true&token=' . $token;
}
if (isset($alert['alertColor']) && !empty(trim($alert['alertColor']))) {
$alertColorLower = strtolower(trim($alert['alertColor']));
if ($alertColorLower === 'red') {
$backgroundColor = '#f6000017';
$borderColor = '#F60000';
$textColor = '#F60000';
} elseif ($alertColorLower === 'orange') {
$backgroundColor = '#ff66001f';
$borderColor = '#FF6600';
$textColor = '#FF6600';
} elseif ($alertColorLower === 'yellow') {
$backgroundColor = '#fcb82526';
$borderColor = '#FCB825';
$textColor = '#FCB825';
}
}
$purpose = EWS_MESSAGE;
$alert['backgroundColor'] = $backgroundColor;
$alert['borderColor'] = $borderColor;
$alert['textColor'] = $textColor;
$alert['tokenURL'] = $tokenURL;
$alert['mannedAlertDatailUrl'] = null;
$html = $templating->render('web2print/_manned_alert_notification_ar.html.twig', $alert);
$mailSent = $this->c2Service->sendNotificationEmail($_ENV['EWS_MAIL_TEMPLATE'], $alert['id'], $currentUser->getId(), $html, $subject, $purpose);
// $this->c2Service->sendMannedAlertEmails($html,$currentUser->getId(),$alert['id']);
// $mailSent=$this->c2Service->sendMannedAlertEmails($html,$currentUser->getId(),$alert['id'],$subject);
if ($mailSent) {
$status = "sent";
$this->saveEmailStatus($currentUser->getEmail(), $currentUser->getName(), $alert, $status);
} else {
$status = "not sent";
$this->saveEmailStatus($currentUser->getEmail(), $currentUser->getName(), $alert, $status);
}
}
}
}
}
}
return $mailSent;
}
private function sendExcelEmailNotification($userGroupIds, $alert, $emailService, $templating)
{
$mailSent = null;
if ($alert) {
$governatesArr = $alert['governorates'];
$governates = [];
if ($governatesArr) {
foreach ($governatesArr as $gov) {
$governates[] = $gov['nameEn'];
}
}
$data = $alert;
$data['sender'] = 'National Center for Meteorology';
// $subject = $alert['searchEwsIdAr'] ??'Severe Weather Alert - ' . $gov['nameEn'];
//$subject = $alert['alertStatusAr'] . ' - ' . $alert['regionAR'];
// $subject = 'النظام الالي للإنذار المبكر :' . ' - ' . $alert['searchEwsIdAr'];
$parts = explode('|', $alert['searchEwsIdAr']);
$excluded = implode('|', array_slice($parts, 1));
$subject = 'النظام الالي للإنذار المبكر :' . $excluded;
if ($userGroupIds) {
foreach ($userGroupIds as $userGroupId) {
if ($userGroupId) {
$userGroup = \Pimcore\Model\DataObject\EwsAndReportUserGroup::getById($userGroupId, true);
if ($userGroup instanceof \Pimcore\Model\DataObject\EwsAndReportUserGroup) {
foreach (json_decode($userGroup->getJsonData()) as $currentUser) {
$entityUserSubscription = $this->userModel->getEntitySubscription($currentUser);
$packageExpired = $entityUserSubscription ? (new \DateTime() > $entityUserSubscription->getEndDate() ? true : false) : false;
$subActive = $entityUserSubscription ? $entityUserSubscription->getIsActive() : false;
$nonSubUser = $subActive && !$packageExpired;
if (isset($currentUser->firstName) && isset($currentUser->lastName) && isset($currentUser->email) && $nonSubUser) {
// extra param js
$alertObj = \Pimcore\Model\DataObject\AlertType::getByAlertTypeId($alert['alertType'], true);
$alertName = $alertObj->getColor() ? $alertObj->getColor() : '';
$alert['user_name'] = $currentUser->firstName . ' ' . $currentUser->lastName;
$alert['host'] = API_BASE_URL;
$alert['alertColor'] = $alertName;
$backgroundColor = '#fcb82526';
$borderColor = '#000000';
$textColor = '#000000';
if (isset($alert['alertColor']) && !empty(trim($alert['alertColor']))) {
$alertColorLower = strtolower(trim($alert['alertColor']));
if ($alertColorLower === 'red') {
$backgroundColor = '#f6000017';
$borderColor = '#F60000';
$textColor = '#F60000';
} elseif ($alertColorLower === 'orange') {
$backgroundColor = '#ff66001f';
$borderColor = '#FF6600';
$textColor = '#FF6600';
} elseif ($alertColorLower === 'yellow') {
$backgroundColor = '#fcb82526';
$borderColor = '#FCB825';
$textColor = '#FCB825';
}
}
$alert['backgroundColor'] = $backgroundColor;
$alert['borderColor'] = $borderColor;
$alert['textColor'] = $textColor;
$alert['tokenURL'] = null;
$alert['mannedAlertDatailUrl'] = null;
$html = $templating->render('web2print/_manned_alert_notification_ar.html.twig', $alert);
// $email = $currentUser->email;
// $param = ['user' => $currentUser, 'message' => $html, 'url' => null];
// $mailSent = $emailService->sendMail($param, "abdul.muqeet@centric.ae", '/email/alert_notification', $subject);
$purpose = EWS_MESSAGE;
$mailSent = $this->c2Service->sendDefaultEmail($_ENV['EWS_MAIL_TEMPLATE'], $alert['id'], $currentUser->email, $html, $subject, $purpose);
if ($mailSent) {
$status = "sent";
$this->saveEmailStatus($currentUser->email, $currentUser->firstName . ' ' . $currentUser->lastName, $alert, $status);
} else {
$status = "not sent";
$this->saveEmailStatus($currentUser->email, $currentUser->firstName . ' ' . $currentUser->lastName, $alert, $status);
}
}
}
}
}
}
}
}
return $mailSent;
}
private function saveEmailStatus($userEmail, $userName, $alert, $emailStatus)
{
// Get all governorates names in string seprated by ,
$nameEnArray = array_column($alert["governorates"], "nameEn");
$nameEnString = implode(", ", $nameEnArray);
$nameArArray = array_column($alert["governorates"], "nameAr");
$nameArString = implode(", ", $nameArArray);
$ewsEmailStatus = new FetchSentEwsEmail();
$ewsEmailStatus->setParent(\Pimcore\Model\DataObject\Service::createFolderByPath("/Other/FetchSentEwsEmail/"));
$ewsEmailStatus->setKey(\Pimcore\Model\Element\Service::getValidKey($userEmail . "-" . strtotime("now") . "-" . rand(1, 10000), 'object'));
$ewsEmailStatus->setEwsId($alert['id']);
$ewsEmailStatus->setUserName($userName);
$ewsEmailStatus->setEmail($userEmail);
//$ewsEmailStatus->setLocationName($locationName);
$ewsEmailStatus->setAlertType($alert['alertTypeEn'], 'en');
$ewsEmailStatus->setAlertType($alert['alertTypeAr'], 'ar');
$ewsEmailStatus->setAlertStatus($alert['alertStatusEn'], 'en');
$ewsEmailStatus->setAlertStatus($alert['alertStatusAr'], 'ar');
$ewsEmailStatus->setRegionName($alert['regionEn'], 'en');
$ewsEmailStatus->setRegionName($alert['regionAR'], 'ar');
$ewsEmailStatus->setGovernorateNames($nameEnString, 'en');
$ewsEmailStatus->setGovernorateNames($nameArString, 'ar');
$ewsEmailStatus->setStatus($emailStatus);
$ewsEmailStatus->setPublished(true);
$ewsEmailStatus->save();
return $ewsEmailStatus;
}
public function getEwsNotificationEmailStatus($params, $paginator, $translator)
{
$result = [];
try {
$pageSize = isset($params['page_size']) ? $params['page_size'] : LIMIT_PER_PAGE;
$page = isset($params['page']) ? $params['page'] : 1;
$notificationId = $params["id"] ? $params["id"] : null;
$listing = new FetchSentEwsEmail\Listing();
$listing->setCondition("ewsId = ? ", [$notificationId]);
$paginator = $paginator->paginate(
$listing,
$page,
$pageSize
);
if ($paginator->count() > 0) {
foreach ($paginator as $EwsNotificationEmail) {
$result[] = [
"ewsId" => $EwsNotificationEmail->getEwsId(),
"userName" => $EwsNotificationEmail->getUserName(),
"Email" => $EwsNotificationEmail->getEmail(),
"locationName" => $EwsNotificationEmail->getLocationName(),
"alertType" => $EwsNotificationEmail->getAlertType('en'),
"alertTypeAr" => $EwsNotificationEmail->getAlertType('ar'),
"alertStatus" => $EwsNotificationEmail->getAlertStatus('en'),
"alertStatusAr" => $EwsNotificationEmail->getAlertStatus('ar'),
"regionName" => $EwsNotificationEmail->getRegionName('en'),
"regionNameAr" => $EwsNotificationEmail->getRegionName('ar'),
"governorateNames" => explode(",", $EwsNotificationEmail->getGovernorateNames('en')),
"governorateNamesAr" => explode(",", $EwsNotificationEmail->getGovernorateNames('ar')),
"emailStatus" => $EwsNotificationEmail->getStatus(),
"emailStatusAr" => $translator->trans($EwsNotificationEmail->getStatus(), [], null, "ar"),
];
}
}
return ["success" => TRUE, "data" => $result, "paginationVariables" => $paginator->getPaginationData()];
} catch (\Exception $ex) {
return new \Exception($ex->getMessage());
}
return $result;
}
public function getOtherLocations()
{
$response = [];
$otherLocations = new DataObject\OtherLocation\Listing();
$otherLocations = $otherLocations->load();
if ($otherLocations) {
foreach ($otherLocations as $otherLocation) {
$response[] = [
"id" => $otherLocation->getOtherLocationId(),
"nameEn" => $otherLocation->getName('en'),
"nameAr" => $otherLocation->getName('ar')
];
}
}
return ["success" => true, "data" => $response];
}
public function getEwsNotificationByByRegionName($regionName)
{
$db = Db::get();
$selectedLocalities = $db->fetchAll("SELECT * FROM `object_collection_AddressComponents_location` WHERE (`addressKey` = 'locality' || `addressKey` = 'administrative_area_level_1') and (TRIM(REPLACE(`addressValue`,'Province','')) = '" . $regionName . "')");
return $selectedLocalities;
}
public function ewsAnalytics($params, $connection, $lang): array
{
$response = [];
$status = isset($params['status']) ? $params['status'] : "";
$isManned = null;
if (isset($params['isManned']) && $params['isManned'] == true) {
$isManned = true;
} elseif (isset($params['isManned']) && $params['isManned'] == false) {
$isManned = false;
}
if (isset($params['region']) && !empty($params['region'])) {
$region = Region::getByRegionId($params['region'], true);
$response[] = $this->ewsAnalyticsCount($region, $status, $connection, $lang, $isManned);
} else {
$regions = new Region\Listing();
foreach ($regions as $region) {
$response[] = $this->ewsAnalyticsCount($region, $status, $connection, $lang, $isManned);
}
}
return $response;
}
public function ewsAnalyticsCount($region, $status, $connection, $lang, $isManned)
{
$regionNameEn = $region->getName("en");
$regionNameAr = $region->getName("ar");
$statuses = ["active", "archived", "expired"];
$count = ['regionEn' => $regionNameEn, "regionAr" => $regionNameAr];
if (in_array($status, $statuses)) {
$count[$status] = $this->getStatusData($region, $status, $connection, $isManned);
} else {
foreach ($statuses as $s) {
$count[$s] = $this->getStatusData($region, $s, $connection, $isManned);
}
}
return $count;
}
public function getStatusData($region, $status, $connection, $isManned)
{
$totalNotifications = $this->statusCount($region, $status, $isManned);
$alertTypeCount = $this->alertCount($region, $status, $connection, $isManned);
return [
'total_notifications' => $totalNotifications,
'alert_type_count' => $alertTypeCount,
];
}
public function alertCount($region, $status, $connection, $isManned)
{
$currentTimestamp = time(); // Current UNIX timestamp
$twentyFourHoursAgoTimestamp = strtotime('-24 hours'); // 24 hours ago timestamp
$claim_report_sql = $connection->createQueryBuilder();
$claim_report_sql
->select('oa.Color AS color', 'COUNT(oq.oo_id) AS count')
->from('object_ews_notification', 'oq')
->join('oq', 'object_query_alert_type', 'oa', 'oq.alertType__id = oa.oo_id')
->andWhere('oq.region__id = :regionId')
->andWhere('oq.status = :status');
if ($isManned !== null && $isManned == true) {
$claim_report_sql->andWhere('oq.isManned = 1');
} elseif ($isManned !== null && $isManned == false) {
$claim_report_sql->andWhere('oq.isManned = 0');
}
$claim_report_sql->andWhere('oq.o_published = 1'); // added new line
// Initialize parameters array
$params = [
'regionId' => $region->getId(),
'status' => $status,
];
// Add conditions based on status
if ($status == "active") {
$claim_report_sql->andWhere('oq.endDate > :currentTimestamp');
$params['currentTimestamp'] = $currentTimestamp;
} else if ($status == "expired") {
$claim_report_sql->andWhere('oq.endDate < :currentTimestamp AND oq.endDate > :twentyFourHoursAgoTimestamp');
$params['currentTimestamp'] = $currentTimestamp;
$params['twentyFourHoursAgoTimestamp'] = $twentyFourHoursAgoTimestamp;
} else if ($status == "archived") {
$claim_report_sql->andWhere('oq.endDate < :twentyFourHoursAgoTimestamp');
$params['twentyFourHoursAgoTimestamp'] = $twentyFourHoursAgoTimestamp;
}
// Set all parameters at once
$claim_report_sql->setParameters($params);
$claim_report_sql->groupBy('color');
$claimReportstmt = $claim_report_sql->execute();
$results = $claimReportstmt->fetchAllAssociative();
// Fetch all possible colors
$alertTypes = new AlertType\Listing();
$allColors = [];
foreach ($alertTypes as $alertType) {
if ($alertType) {
$allColors[] = $alertType->getColor();
}
}
if (empty($allColors)) {
$allColors = ["yellow", "orange", "red"];
}
// Create an associative array to store counts by color
$countsByColor = [];
foreach ($results as $result) {
$countsByColor[$result['color']] = $result['count'];
}
// Merge counts for existing colors with counts for all colors, including those with count 0
$finalResult = [];
foreach ($allColors as $color) {
$finalResult[] = [
"color" => $color,
"count" => isset($countsByColor[$color]) ? $countsByColor[$color] : 0
];
}
return $finalResult;
}
public function statusCount($region, $status, $isManned)
{
$notifications = new EwsNotification\Listing();
$currentTimestamp = time(); // Get the current UNIX timestamp
$twentyFourHoursAgoTimestamp = strtotime('-24 hours'); // 24 hours ago timestamp
$conditions = "region__id = ? AND status = ?";
$params = [$region->getId(), $status];
if ($status == "active") {
$conditions .= " AND endDate > ?";
$params[] = $currentTimestamp;
} else if ($status == "expired") {
$conditions .= " AND endDate < ? AND endDate > ?";
$params[] = $currentTimestamp;
$params[] = $twentyFourHoursAgoTimestamp;
} else if ($status == "archived") {
$conditions .= " AND endDate < ?";
$params[] = $twentyFourHoursAgoTimestamp;
}
$notifications->addConditionParam($conditions, $params);
// Add condition for manned notifications
if ($isManned !== null) {
$notifications->addConditionParam("isManned = ?", [$isManned]);
}
return $notifications->count();
}
public function searchEwsNotification($params, $lang, $paginator, $translator): array
{
$response = [];
$pageSize = isset($params['page_size']) ? $params['page_size'] : LIMIT_PER_PAGE;
$page = isset($params['page']) ? $params['page'] : 1;
$notificationList = new DataObject\EwsNotification\Listing();
// If unpublished parameter exists and is true, show unpublished items
// Handle boolean true, string "true", or integer 1
$isUnpublished = isset($params['unpublished']) && (
$params['unpublished'] === true ||
$params['unpublished'] === 'true' ||
$params['unpublished'] === 1 ||
$params['unpublished'] === '1'
);
if ($isUnpublished) {
$notificationList->setUnpublished(true);
$notificationList->filterByPublished(false);
}
// No else needed - default behavior already shows published only
if (isset($params['allowedNotificationTypes'])) {
$notificationList->addConditionParam("notificationType IN (?)", [$params["allowedNotificationTypes"]]);
}
if (isset($params['isManned']) && $params['isManned'] == true) {
$notificationList->addConditionParam("isManned = 1");
} elseif (isset($params['isManned']) && $params['isManned'] == false) {
$notificationList->addConditionParam("isManned = 0");
}
if (isset($params['id']) && !empty($params['id'])) {
$idz = $params['id'];
$notificationList->addConditionParam("oo_id IN (?)", [$idz]);
}
if (isset($params['alertAction']) && !empty($params['alertAction'])) {
$alertActionIds = [];
$alertActionList = new DataObject\AlertAction\Listing();
$alertActionList->addConditionParam("alertActionId IN (?)", [$params['alertAction']]);
foreach ($alertActionList as $alertAction) {
$alertActionIds[] = $alertAction->getId();
}
$alertActionids = implode(",", $alertActionIds);
$notificationList->addConditionParam("alertAction REGEXP CONCAT('(^|,)(', REPLACE('$alertActionids',',', '|'), ')(,|$)')");
}
if (isset($params['alertType']) && !empty($params['alertType'])) {
$alertTypeIds = [];
$alertTypeList = new DataObject\AlertType\Listing();
$alertTypeList->addConditionParam("alertTypeId IN (?)", [$params['alertType']]);
foreach ($alertTypeList as $alertType) {
$alertTypeIds[] = $alertType->getId();
}
$notificationList->addConditionParam("alertType__id IN (?)", [$alertTypeIds]);
}
// if (isset($params['phenomena']) && !empty($params['phenomena'])) {
// $phenomenaIds = [];
// $phenomenaList = new DataObject\PhenomenaList\Listing();
// $phenomenaList->addConditionParam("phenomenalistid IN (?)", [$params['phenomena']]);
// foreach ($phenomenaList as $phenomena) {
// $phenomenaIds[] = $phenomena->getId();
// }
// $notificationList->addConditionParam("weatherPhenomenon__id IN (?)", [$phenomenaIds]);
// }
if (!empty($params['phenomena'])) {
$alertStatusSql = null;
foreach ($params['phenomena'] as $alertStatus) {
$alertStatus = \Pimcore\Model\DataObject\AlertStatus::getByAlertStatusId($alertStatus, true);
if ($alertStatus) {
$alertStatusSql .= "alertStatus__id = " . $alertStatus->getId() . " OR ";
}
}
$notificationList->addConditionParam("(" . substr($alertStatusSql, 0, -3) . ")");
}
if (isset($params['region']) && !empty($params['region'])) {
$regionIds = [];
$regionList = new DataObject\Region\Listing();
$regionList->addConditionParam("regionId IN (?)", [$params['region']]);
foreach ($regionList as $region) {
$regionIds[] = $region->getId();
}
$notificationList->addConditionParam("region__id IN (?)", [$regionIds]);
}
if (isset($params['governate']) && !empty($params['governate'])) {
$governatesIds = [];
$governateList = new DataObject\Governorate\Listing();
$governateList->addConditionParam("governoteId IN (?)", [$params['governate']]);
foreach ($governateList as $governate) {
$governatesIds[] = $governate->getId();
}
$governatesIds = implode(",", $governatesIds);
$notificationList->addConditionParam("governorate REGEXP CONCAT('(^|,)(', REPLACE('$governatesIds',',', '|'), ')(,|$)')");
}
if (isset($params['status'][0]) && !empty($params['status'][0])) {
$currentTimestamp = time();
$twentyFourHoursAgo = strtotime('-24 hours');
if ($params['status'][0] == "active" && (!isset($params['status'][1]) || empty($params['status'][1]))) {
// Filter notifications with an end date in the future or equal to current time
$notificationList->addConditionParam("status != ?", ["ended"]);
$notificationList->filterByEndDate($currentTimestamp, ">=");
} else if ($params['status'][0] == "expired" && (!isset($params['status'][1]) || empty($params['status'][1]))) {
// Filter notifications with an end date in the past, but not older than 24 hours
$notificationList->addConditionParam("status != ?", ["ended"]);
$notificationList->filterByEndDate($currentTimestamp, "<");
$notificationList->filterByEndDate($twentyFourHoursAgo, ">=");
} else if ($params['status'][0] == "archived" && (!isset($params['status'][1]) || empty($params['status'][1]))) {
// Filter notifications with an end date older than 24 hours
$notificationList->addConditionParam("endDate < ? OR alertEndDate < ? ", [$twentyFourHoursAgo, $twentyFourHoursAgo]);
// $notificationList->filterByEndDate($twentyFourHoursAgo, "<");
} else if ($params['status'][0] == "ended" && (!isset($params['status'][1]) || empty($params['status'][1]))) {
$notificationList->filterByAlertEndDate($twentyFourHoursAgo, ">");
$notificationList->filterByStatus("ended");
// $notificationList->addConditionParam("status = ?",["ended"]);
} else if (isset($params['status'][1]) && !empty($params['status'][1])) {
$conditions = [];
if (in_array('ended', $params['status'])) {
$conditions[] = "(status = 'ended' AND AlertEndDate > {$twentyFourHoursAgo})";
}
if (in_array('active', $params['status'])) {
$conditions[] = "(status != 'ended' AND endDate >= {$currentTimestamp})";
}
if (in_array('expired', $params['status'])) {
$conditions[] = "(status != 'ended' AND endDate < {$currentTimestamp} AND endDate >= {$twentyFourHoursAgo})";
}
if (in_array('archived', $params['status'])) {
$conditions[] = "(status != 'ended' AND (endDate < {$twentyFourHoursAgo} OR alertEndDate < {$twentyFourHoursAgo}))";
}
if (!empty($conditions)) {
$notificationList->addConditionParam('(' . implode(' OR ', $conditions) . ')');
}
}
} else {
// If no status is provided, return 0 records (only for published records)
// For unpublished records, don't require status filter
if (!$isUnpublished) {
$notificationList->addConditionParam("1 = 0");
}
}
$notificationList->setLocale($lang);
if (isset($params['searchId']) && !empty($params['searchId'])) {
$notificationList->addConditionParam(
"(ewsSearchId LIKE ?)",
[
'%' . $params['searchId'] . '%'
]
);
}
if (isset($params['alertHazard']) && !empty($params['alertHazard'])) {
$alertHazardIds = [];
$alertHazardList = new DataObject\AlertHazard\Listing();
$alertHazardList->addConditionParam("alertHazardId IN (?)", [$params['alertHazard']]);
foreach ($alertHazardList as $alertHazard) {
$alertHazardIds[] = $alertHazard->getId();
}
$alertHazardIds = implode(",", $alertHazardIds);
$notificationList->addConditionParam("alertHazard REGEXP CONCAT('(^|,)(', REPLACE('$alertHazardIds',',', '|'), ')(,|$)')");
}
if (isset($params['municipality']) && !empty($params['municipality'])) {
$municipalityIds = [];
$municipalityList = new DataObject\Municipality\Listing();
$municipalityList->addConditionParam("municipalityid IN (?)", [$params['municipality']]);
foreach ($municipalityList as $municipality) {
$municipalityIds[] = $municipality->getId();
}
$municipalityIds = implode(",", $municipalityIds);
$notificationList->addConditionParam("municipality REGEXP CONCAT('(^|,)(', REPLACE('$municipalityIds',',', '|'), ')(,|$)')");
}
if (isset($params['startDate']) && !empty($params['startDate'])) {
$notificationList->filterByStartDate(strtotime($params['startDate']), ">=");
}
if (isset($params['endDate']) && !empty($params['endDate'])) {
$notificationList->filterByEndDate(strtotime($params['endDate']), "<=");
}
if (isset($params['fromDate']) && isset($params['toDate']) && !empty($params['fromDate']) && !empty($params['toDate'])) {
$fromDate = new \DateTime($params['fromDate']);
$toDate = new \DateTime($params['toDate']);
// Ensure the dates are in the correct format
$fromDateStr = strtotime($fromDate->format('Y-m-d H:i:s'));
$toDateStr = strtotime($toDate->format('Y-m-d') . ' 23:59:59');
$notificationList->addConditionParam(
"(o_creationDate >= ? AND o_creationDate <= ?)",
[$fromDateStr, $toDateStr]
);
}
$notifications = $notificationList->load();
$sortedData = [];
foreach ($notifications as $notification) {
$regionEn = $notification->getRegion() ? $notification->getRegion()->getName("en") : "";
$startDate = $notification->getStartDate() ? $notification->getStartDate()->format('Y-m-d H:i:s') : "";
$endDate = $notification->getEndDate() ? $notification->getEndDate()->format('Y-m-d H:i:s') : "";
$alertStatusEn = $notification->getWeatherPhenomenon()?->getTitle("en");
$creationDate = $notification->getCreationDate();
$searchIdEn = $notification->getEwsSearchId("en");
$searchIdAr = $notification->getEwsSearchId("ar");
$sortedData[] = [
'notification' => $notification,
'regionName' => $regionEn,
'alertStatus' => $alertStatusEn,
'startDate' => $startDate,
'endDate' => $endDate,
'creationDate' => $creationDate, // Add creation date to sorting criteria
'searchIdEn' => $searchIdEn,
'searchIdAr' => $searchIdAr
];
}
// Define default sort orders and detect if any sort parameter is provided
$sortOrderRegion = $params['sortByRegion'] ?? null;
$sortOrderStartDate = $params['sortByStartDate'] ?? null;
$sortOrderEndDate = $params['sortByEndDate'] ?? null;
$sortOrderAlertStatus = $params['sortByAlertStatus'] ?? null;
$sortOrderSearchIdEn = $params['sortBySearchIdEn'] ?? null;
$sortOrderSearchIdAr = $params['sortBySearchIdAr'] ?? null;
$sortOrderCreatedAt = $params['sortByCreated'] ?? null;
// Check if any sort parameter is provided
$anySortParamProvided = $sortOrderRegion || $sortOrderStartDate || $sortOrderEndDate || $sortOrderAlertStatus || $sortOrderSearchIdEn || $sortOrderSearchIdAr;
usort($sortedData, function ($a, $b) use ($sortOrderRegion, $sortOrderStartDate, $sortOrderEndDate, $sortOrderAlertStatus, $sortOrderSearchIdEn, $sortOrderSearchIdAr, $sortOrderCreatedAt, $anySortParamProvided) {
if (!$anySortParamProvided || $sortOrderCreatedAt) {
if ($sortOrderCreatedAt == null) {
$sortOrderCreatedAt = "desc";
}
// Default to sorting by creation date if no sort parameter is provided
$compareCreatedAt = strcmp($a['creationDate'], $b['creationDate']) * ($sortOrderCreatedAt === 'desc' ? -1 : 1);
if ($compareCreatedAt !== 0) return $compareCreatedAt; // Assuming 'asc' as default sort order for creation date
}
// Else, sort based on the provided parameters
if ($sortOrderRegion) {
$compareRegion = strcmp($a['regionName'], $b['regionName']) * ($sortOrderRegion === 'desc' ? -1 : 1);
if ($compareRegion !== 0) return $compareRegion;
}
if ($sortOrderSearchIdEn) {
$compareSearchIdEn = strcmp($a['searchIdEn'], $b['searchIdEn']) * ($sortOrderSearchIdEn === 'desc' ? -1 : 1);
if ($compareSearchIdEn !== 0) return $compareSearchIdEn;
}
if ($sortOrderSearchIdAr) {
$compareSearchIdAr = strcmp($a['searchIdAr'], $b['searchIdAr']) * ($sortOrderSearchIdAr === 'desc' ? -1 : 1);
if ($compareSearchIdAr !== 0) return $compareSearchIdAr;
}
if ($sortOrderAlertStatus) {
$compareAlertStatus = strcmp($a['alertStatus'], $b['alertStatus']) * ($sortOrderAlertStatus === 'desc' ? -1 : 1);
if ($compareAlertStatus !== 0) return $compareAlertStatus;
}
if ($sortOrderStartDate) {
$compareStartDate = strcmp($a['startDate'], $b['startDate']) * ($sortOrderStartDate === 'desc' ? -1 : 1);
if ($compareStartDate !== 0) return $compareStartDate;
}
if ($sortOrderEndDate) {
return strcmp($a['endDate'], $b['endDate']) * ($sortOrderEndDate === 'desc' ? -1 : 1);
}
// If all provided parameters are equal, or no specific sort orders are provided, default to creation date
return ($b['creationDate'] ?? 0) - ($a['creationDate'] ?? 0);
});
$sortedNotifications = array_map(function ($item) {
return $item['notification'];
}, $sortedData);
if (isset($params['dashboard']) && $params['dashboard'] === 'dashboard') {
return [
'success' => true,
'data' => array_values(array_map(
static fn ($notification) => (int) $notification->getId(),
$sortedNotifications
)),
];
}
// Extract the sorted notifications
if ($paginator == null) {
// if ($sortedNotifications->count()) {
foreach ($sortedNotifications as $notification) {
// if($notification->getPublished())
$item = $this->createNotificationFormat($notification, $translator);
$item['history'] = false;
if ($this->ewsNotificationHasVersionUpdateHistory($notification)) {
$item['history'] = true;
}
$response[] = $item;
}
// }
return ["success" => TRUE, "data" => $response];
}
$paginator = $paginator->paginate(
$sortedNotifications,
$page,
$pageSize
);
$decodedJwtToken = isset($params['decodedJwtToken']) ?? null;
$userPermission = isset($params['userPermission']) ?? null;
// if ($sortedNotifications->count()) {
foreach ($paginator as $notification) {
// if($notification->getPublished())
$item = $this->createNotificationFormat($notification, $translator, $decodedJwtToken, $userPermission);
$item['history'] = false;
if ($this->ewsNotificationHasVersionUpdateHistory($notification)) {
$item['history'] = true;
}
$response[] = $item;
}
// }
return ["data" => $response, "paginationVariables" => $paginator->getPaginationData()];
}
public function archiveNotification($params, $translator): array
{
$id = $params['id'] ?? null;
if (!$id) {
throw new \Exception("EWS id is required");
}
$ewsNotification = \Pimcore\Model\DataObject::getById($id);
if (!$ewsNotification instanceof \Pimcore\Model\DataObject\EwsNotification) {
// throw new \Exception("Ews notification not found");
return ['success' => false, 'message' => $translator->trans('ews_notification_not_found')];
}
if (isset($params['status']) && $params['status']) {
$ewsNotification->setStatus("archived");
}
$ewsNotification->save(["versionNote" => "Update"]);
if ($this->shouldDispatchPublicPortalEwsWebhook($ewsNotification)) {
$this->dispatchPublicPortalEwsWebhookIfConfigured((int) $ewsNotification->getId());
}
return ['success' => true, 'message' => $translator->trans('archive_ews_notification'), "notification_id" => $ewsNotification->getId()];
}
public function getId(): ?UuidV4
{
$uuid = UuidV4::v4();
return $uuid;
}
public function getPhenomenaList()
{
$phenomenas = new \Pimcore\Model\DataObject\PhenomenaList\Listing();
$phenomenas->setOrderKey("o_creationDate");
$phenomenas->setOrder("desc");
$phenomenas->load();
$response = [];
if (count($phenomenas) > 0) {
foreach ($phenomenas as $phenomena) {
$response[] = [
"id" => $phenomena->getPhenomenaListId(),
"nameEN" => $phenomena->getTitle('en'),
"nameAr" => $phenomena->getTitle('ar'),
];
}
return ["success" => true, "data" => $response];
}
return ["success" => false, "data" => $response];
}
public function getHazardListByAlertStatusId($statusId, $regionIds = [], $lang = "en")
{
$response = [];
$excludeWavesRising = false;
// Check if any region is landlocked
if (!empty($regionIds) && is_array($regionIds)) {
foreach ($regionIds as $regionId) {
$region = \Pimcore\Model\DataObject\Region::getByRegionId($regionId, true);
if ($region && $region->getIsLandLocked()) {
$excludeWavesRising = true;
break;
}
}
}
$alertStatus = \Pimcore\Model\DataObject\AlertStatus::getByAlertStatusId($statusId, true);
if ($alertStatus instanceof \Pimcore\Model\DataObject\AlertStatus) {
if ($alertStatus && $alertStatus->getAlertHazards()) {
foreach ($alertStatus->getAlertHazards() as $value) {
$alertHazardObj = $value->getLocalizedfields()->getItems();
// Exclude "Waves rising" if needed
if (
$excludeWavesRising &&
isset($alertHazardObj['en']['name']) &&
strtolower(trim($alertHazardObj['en']['name'])) === 'waves rising'
) {
continue;
}
$response[] = [
"id" => (int)$value->getAlertHazardId(),
"nameEn" => $alertHazardObj['en']['name'],
"nameAr" => $alertHazardObj['ar']['name']
];
}
} else {
return ["success" => false, "message" => "No Hazard is set"];
}
} else {
return ["success" => false, "message" => "Invalid alert status id"];
}
return ["success" => true, "data" => $response];
}
// public function getHazardListByAlertStatusId($statusId, $lang = "en")
// {
// $response = [];
// $alertPhenomena = \Pimcore\Model\DataObject\PhenomenaList::getByPhenomenaListId($statusId, true);
// if ($alertPhenomena instanceof \Pimcore\Model\DataObject\PhenomenaList) {
// if ($alertPhenomena && $alertPhenomena->getAlertHazards()) {
// foreach ($alertPhenomena->getAlertHazards() as $value) {
// $alertHazardObj = $value->getLocalizedfields()->getItems();
// $response[] = ["id" => (int)$value->getAlertHazardId(), "nameEn" => $alertHazardObj['en']['name'], "nameAr" => $alertHazardObj['ar']['name']];
// }
// } else {
// return ["success" => false, "message" => "No Hazard is set"];
// }
// } else {
// return ["success" => false, "message" => "Invalid alert status id"];
// }
// return ["success" => true, "data" => $response];
// }
public function getOtherGovernoratesByRegion($regionId)
{
$response = [];
$region = \Pimcore\Model\DataObject\Region::getByRegionId($regionId, true);
if (!$region) {
throw new \Exception("Region not available");
}
$otherGovernorates = new DataObject\EwsOtherLocation\Listing();
//$otherGovernorates->filterByRegionId($region);
$otherGovernorates = $otherGovernorates->load();
if ($otherGovernorates) {
foreach ($otherGovernorates as $governorate) {
$region = $governorate->getregionId();
$regionData = [];
if (!empty($region)) {
$regionData = [
"id" => $region->getRegionId(),
"nameEn" => $region->getName('en'),
"nameAr" => $region->getName('ar'),
"longitude" => $region->getLongitude(),
"latitude" => $region->getLongitude()
];
}
$response[] = [
"id" => $governorate->getgovernoteId(),
"nameEn" => $governorate->getName('en'),
"nameAr" => $governorate->getName('ar'),
"regionId" => $regionData,
"longitude" => $governorate->getLongitude(),
"latitude" => $governorate->getLatitude(),
"isHidden" => $governorate->getisHidden(),
"IsMunicipality" => $governorate->getIsMunicipality(),
"MunicipalityID" => $governorate->getMunicipalityID()
];
}
}
return ["success" => true, "data" => $response];
}
public function createEwsReportUserGroup($params, $translator)
{
$response = [];
$checkGroup = EwsAndReportUserGroup::getByName($params['name'], true);
if ($checkGroup instanceof EwsAndReportUserGroup) {
return ["success" => false, "message" => $translator->trans("group_name_already_exists"), "name" => $params['name']];
}
$ewsReportUserGroup = new DataObject\EwsAndReportUserGroup();
$ewsReportUserGroup->setParent(\Pimcore\Model\DataObject\Service::createFolderByPath("/Notification/userGroups/" . $params['type'] . "/"));
$ewsReportUserGroup->setKey(\Pimcore\Model\Element\Service::getValidKey($params['name'] . "-" . strtotime("now") . "-" . rand(1, 10000), 'object', 'object'));
$ewsReportUserGroup->setName(strip_tags($params['name']));
$ewsReportUserGroup->setJsonData(json_encode($params['data']));
$ewsReportUserGroup->setGroupType($params['type']);
$ewsReportUserGroup->setPublished(true);
$ewsReportUserGroup->save();
return ["success" => true, "message" => $translator->trans("ews_report_user_group_created")];
}
public function updateEwsReportUserGroup($params, $translator)
{
$response = [];
$updateGroup = EwsAndReportUserGroup::getById($params['id']);
if (!$updateGroup instanceof EwsAndReportUserGroup) {
return ["success" => false, "message" => $translator->trans('ews_report_user_group_not_found'), "id" => $params['id']];
}
if (isset($params['name']) && !empty($params['name']) && $updateGroup->getName() != $params['name']) {
$checkGroup = EwsAndReportUserGroup::getByName($params['name'], true);
if ($checkGroup instanceof EwsAndReportUserGroup) {
return ["success" => false, "message" => $translator->trans("group_name_already_exists"), "name" => $params['name']];
}
$updateGroup->setName(strip_tags($params['name']));
$updateGroup->setKey(\Pimcore\Model\Element\Service::getValidKey($params['name'] . "-" . strtotime("now") . "-" . rand(1, 10000), 'object', 'object'));
}
if (isset($params['data']) && !empty($params['data'])) {
$updateGroup->setJsonData(json_encode($params['data']));
}
if (isset($params['type']) && !empty($params['type'])) {
$updateGroup->setGroupType($params['type']);
$updateGroup->setParent(\Pimcore\Model\DataObject\Service::createFolderByPath("/Notification/userGroups/" . $params['type'] . "/"));
}
$updateGroup->setJsonData(json_encode($params['data']));
$updateGroup->setGroupType($params['type']);
$updateGroup->setPublished(true);
$updateGroup->save();
return ["success" => true, "message" => $translator->trans("ews_report_user_group_updated")];
}
public function deleteEwsReportUserGroup($params, $translator)
{
$response = [];
$deleteGroup = EwsAndReportUserGroup::getById($params['id']);
if (!$deleteGroup instanceof EwsAndReportUserGroup) {
return ["success" => false, "message" => $translator->trans('ews_report_user_group_not_found'), "id" => $params['id']];
}
$deleteGroup->delete();
return ["success" => true, "message" => $translator->trans("ews_report_user_group_deleted")];
}
public function listEwsReportUserGroup($params, $paginator, $translator)
{
$data = [];
$listing = new EwsAndReportUserGroup\Listing();
$listing->setOrderKey("oo_id");
$listing->setOrder("DESC");
if (isset($params['type']) && !empty($params['type'])) {
$listing->addConditionParam("groupType = ?", [$params['type']]);
}
if (isset($params['search']) && !empty($params['search'])) {
$listing->addConditionParam("name LIKE ? ", ['%' . $params['search'] . '%']);
}
$pageSize = isset($params['page_size']) ? $params['page_size'] : LIMIT_PER_PAGE;
$page = isset($params['page']) ? $params['page'] : 1;
$paginator = $paginator->paginate(
$listing,
$page,
$pageSize
);
foreach ($paginator as $group) {
$data[] = [
"id" => $group->getId(),
"name" => $group->getName(),
"type" => $group->getGroupType(),
"data" => json_decode($group->getJsonData(), true)
];
}
if (count($data) > 0) {
return ["success" => true, "data" => $data, "paginationVariables" => $paginator->getPaginationData()];
} else {
return ["success" => false, "message" => $translator->trans('no_ews_report_user_group_found')];
}
}
public function getSortCriteria()
{
// This is a placeholder; you'll need to adapt this based on how you can access the region's name or other criteria
return $this->getRegion()->getName();
}
public function getCenters($governorateId = null, $lang = 'en')
{
$response = [];
$centers = new DataObject\Centers\Listing();
if (is_array($governorateId)) {
$govIdsArr = [];
foreach ($governorateId as $govId) {
$governorate = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($govId, true);
if ($governorate) {
array_push($govIdsArr, $governorate->getId());
}
}
$centers->setCondition("governorate__id IN (" . implode(", ", $govIdsArr) . ")");
} else {
if ($governorateId) {
$governorate = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($governorateId, true);
$centers->setCondition("governorate__id = ?", [$governorate->getId()]);
}
}
$centers = $centers->load();
if ($centers) {
foreach ($centers as $center) {
$response[] = [
'id' => $center->getId(),
"object_id" => $center->getObjectId(),
"nameEn" => $center->getName('en'),
"nameAr" => $center->getName('ar'),
"longitude" => $center->getLongitude(),
"latitude" => $center->getLatitude(),
"governate" => $center->getGovernorate()->getGovernoteId()
];
}
}
// Determine sorting field based on language
$sortField = 'nameEn'; // Default sorting by English
if (isset($lang) && strtolower($lang) === 'ar') {
$sortField = 'nameAr'; // Sorting by Arabic
}
// Sort manually using usort()
usort($response, function ($a, $b) use ($sortField) {
return strcmp($a[$sortField], $b[$sortField]);
});
return ["success" => true, "data" => $response];
}
public function getDistricts($governorateId = null, $lang = 'en')
{
$response = [];
$districts = new DataObject\District\Listing();
if (is_array($governorateId)) {
$govIdsArr = [];
foreach ($governorateId as $govId) {
$governorate = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($govId, true);
if ($governorate) {
array_push($govIdsArr, $governorate->getId());
}
}
$districts->setCondition("governorate__id IN (" . implode(", ", $govIdsArr) . ")");
} else {
if ($governorateId) {
$governorate = \Pimcore\Model\DataObject\Governorate::getByGovernoteId($governorateId, true);
$districts->setCondition("governorate__id = ?", [$governorate->getId()]);
}
}
$districts = $districts->load();
if ($districts) {
foreach ($districts as $district) {
$response[] = [
"id" => $district->getId(),
"object_id" => $district->getObjectId(),
"nameEn" => $district->getName('en'),
"nameAr" => $district->getName('ar'),
"longitude" => $district->getLongitude(),
"latitude" => $district->getLatitude(),
"governate" => $district->getGovernorate()->getGovernoteId()
];
}
}
// Determine sorting field based on language
$sortField = 'nameEn'; // Default sorting by English
if (isset($lang) && strtolower($lang) === 'ar') {
$sortField = 'nameAr'; // Sorting by Arabic
}
// Sort manually using usort()
usort($response, function ($a, $b) use ($sortField) {
return strcmp($a[$sortField], $b[$sortField]);
});
return ["success" => true, "data" => $response];
}
public function createEwsPolygon($params, $translator)
{
$coordinates = $params['coordinates'] ?? null;
$alertTypeId = $params['alertTypeId'] ?? null;
$expiryDate = $params['expiryDate'] ?? null;
$type = $params['type'] ?? null;
// if (!is_array($coordinates) || empty($coordinates)) {
// return ['success' => false, 'message' => $translator->trans('invalid_coordinates')];
// }
$ewsPolygon = new DataObject\EwsPolygon();
$ewsPolygon->setParent(DataObject\Service::createFolderByPath('/Notification/EWSPolygon'));
$ewsPolygon->setKey(uniqid("polygon-"));
$ewsPolygon->setCoordinates(json_encode($coordinates));
if (!empty($type)) {
$ewsPolygon->setEwsAlertType($type);
}
if ($alertTypeId && !empty($alertTypeId)) {
$alertType = DataObject\AlertType::getByAlertTypeId($alertTypeId, 1);
if (!$alertType) {
return ['success' => false, 'message' => $translator->trans('invalid_alert_type')];
}
$ewsPolygon->setAlertType($alertType);
}
if ($expiryDate && !empty($expiryDate)) {
$ewsPolygon->setExpire(\Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $expiryDate));
}
// $ewsPolygon->setPublished(true);
try {
$ewsPolygon->save();
} catch (\Exception $e) {
return ['success' => false, 'message' => $translator->trans('error_saving_polygon'), 'error' => $e->getMessage()];
}
return [
'success' => true,
'message' => $translator->trans('ews_polygon_added_successfully'),
'polygonId' => $ewsPolygon->getId(),
'type' => $ewsPolygon->getEwsAlertType()
];
}
public function updateEwsPolygon($params, $translator)
{
$ewsPolygon = EwsPolygon::getById($params['id'] ?? null);
if (!$ewsPolygon instanceof EwsPolygon) {
return [
"success" => false,
"message" => $translator->trans('ews_polygon_not_found'),
"id" => $params['id'] ?? null
];
}
// Update coordinates if provided and valid
if (isset($params['coordinates'])) {
$coordinates = $params['coordinates'];
// if (!is_array($coordinates) || empty($coordinates)) {
// return ['success' => false, 'message' => $translator->trans('invalid_coordinates')];
// }
$ewsPolygon->setCoordinates(json_encode($coordinates));
}
if (!empty($params['type'])) {
$ewsPolygon->setEwsAlertType($params['type']);
}
// Update alert type if provided
if (!empty($params['alertTypeId'])) {
$alertTypeId = $params['alertTypeId'];
$alertType = DataObject\AlertType::getByAlertTypeId($alertTypeId, 1);
if (!$alertType) {
return ['success' => false, 'message' => $translator->trans('invalid_alert_type')];
}
$ewsPolygon->setAlertType($alertType);
}
// Update EWS alerts if provided
if (!empty($params['alertIds']) && is_array($params['alertIds'])) {
$alertIds = $params['alertIds'];
$ewsNotifications = [];
foreach ($alertIds as $alertId) {
$ewsNotification = \Pimcore\Model\DataObject\EwsNotification::getById($alertId, true);
if (!$ewsNotification instanceof \Pimcore\Model\DataObject\EwsNotification) {
return [
'success' => false,
'message' => $translator->trans('ews_notification_not_found'),
'invalid_alert_id' => $alertId
];
}
$ewsNotification->setPolygon($ewsPolygon);
$ewsNotification->save();
$ewsNotifications[] = $ewsNotification;
}
$ewsPolygon->setEwsAlerts(array_unique($ewsNotifications));
}
if (!empty($params['expiryDate'])) {
$ewsPolygon->setExpire(\Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $params['expiryDate']));
}
if (!empty($params['isPublished']) && $params['isPublished'] == true) {
$ewsPolygon->setPublished(true);
}
// Try saving
try {
$ewsPolygon->save();
} catch (\Exception $e) {
return [
'success' => false,
'message' => $translator->trans('error_saving_polygon'),
'error' => $e->getMessage()
];
}
return [
'success' => true,
'message' => $translator->trans('ews_polygon_updated_successfully'),
'polygonId' => $ewsPolygon->getId()
];
}
public function listEwsPolygon($params, $paginator, $translator)
{
$ewsId = $params['id'] ?? null;
$isPublished = array_key_exists('isPublished', $params) ? (bool) $params['isPublished'] : true;
\Pimcore\Model\DataObject::setHideUnpublished(false);
$listing = new EwsPolygon\Listing();
// $listing->addConditionParam('expire >= ?', [time()]);
if ($ewsId) {
$listing->addConditionParam('o_id = ?', [(int)$ewsId]);
}
$listing->setOrderKey("oo_id");
$listing->setOrder("DESC");
$data = [];
foreach ($listing as $polygon) {
if ($polygon->getPublished() !== $isPublished) {
continue; // skip if it doesn't match the required published status
}
$alertIds = [];
$isEnded = false;
$alerts = $polygon->getEwsAlerts();
if (is_array($alerts)) {
foreach ($alerts as $alert) {
if ($alert instanceof \Pimcore\Model\DataObject\EwsNotification) {
$alertIds[] = $alert->getId();
$isEnded = $alert->getStatus() == "ended" ? true : false;
}
}
}
$alertTypeObj = $polygon->getAlertType();
$alertTypeId = ($alertTypeObj instanceof \Pimcore\Model\DataObject\AlertType)
? $alertTypeObj->getAlertTypeId()
: null;
$data[] = [
"id" => $polygon->getId(),
"coordinates" => json_decode($polygon->getCoordinates(), true),
"alertType" => $alertTypeId,
"type" => $polygon->getEwsAlertType(),
"alerts" => $alertIds,
"expiryDate" => $polygon->getExpire(),
"isExpired" => $polygon->getExpire() ? $polygon->getExpire()->getTimestamp() < time() : null,
"isEnded" => $isEnded
];
}
if (!empty($data)) {
return [
"success" => true,
"data" => $data,
];
}
return [
"success" => false,
"message" => $translator->trans('polygons_not_found'),
];
}
public function endEwsNotification($params, $translator, $logger)
{
$result = [];
$viewNotification = DataObject\EwsNotification::getById($params['id'], true);
if (!$viewNotification instanceof EwsNotification) {
return [
"success" => false,
"message" => $translator->trans('ews_notification_not_found'),
"id" => $params['id'] ?? null
];
}
//set ews search Id
$viewNotification->setStatus("ended");
$viewNotification->setAlertEndDate(\Carbon\Carbon::now());
// Persist the browser-rendered image (x_img) as an asset on the alert so the Twitter end
// listener posts that instead of generating one with wkhtmltoimage. The save() below
// fires the listener, which reads endXAttachment.
if (!empty($params['x_img'])) {
$dataUri = $params['x_img'];
if (($commaPos = strpos($dataUri, ',')) !== false && stripos($dataUri, 'base64') !== false) {
$dataUri = substr($dataUri, $commaPos + 1);
}
$binary = base64_decode(strtr($dataUri, ' ', '+'), true);
if ($binary !== false && $binary !== '') {
$endAsset = \App\Lib\Utility::createAsset(
$binary,
$viewNotification->getId() . '_' . time() . '_end_ews_alert.png',
'END EWS Twitter Images'
);
if ($endAsset) {
$viewNotification->setEndXAttachment($endAsset);
}
}
}
// DIAGNOSTIC (temporary): confirm the end method runs and log the values the
// TwitterEwsEndEventListner gates on. Written to Pimcore ApplicationLogger so it
// lands in the application_logs table. Remove once the End-tweet issue is resolved.
\Pimcore\Log\ApplicationLogger::getInstance('END-DIAG', true)->info(sprintf(
'[END-DIAG] endEwsNotification saving EWS ID %s | published=%s | enableTwitter=%s | status=%s',
$viewNotification->getId(),
var_export($viewNotification->isPublished(true), true),
var_export($viewNotification->getEnableTwitterNotification(), true),
var_export($viewNotification->getStatus(), true)
));
$viewNotification->save(["versionNote" => "End alert"]);
$viewNotification->save();
if ($this->shouldDispatchPublicPortalEwsWebhook($viewNotification)) {
$this->dispatchPublicPortalEwsWebhookIfConfigured((int) $viewNotification->getId(), $logger);
}
$alert = $this->createNotificationFormat($viewNotification, $translator, $params['decodedJwtToken'], $params['userPermission']);
if ($viewNotification) {
// Email the same user groups that were selected when the alert was published/edited.
$userGroupIds = array_map(
fn($group) => $group->getId(),
$viewNotification->getUserGroup()
);
// Ensure you use 'php' to execute the command.
$command = ['php', 'bin/console', 'app:send-early-warning-alert-email', '--alertId=' . $viewNotification->getId(), '--endAlert=1'];
if (!empty($userGroupIds)) {
$command[] = '--userGroupIds=' . json_encode($userGroupIds);
}
$process = new Process($command);
$process->setWorkingDirectory(PIMCORE_PROJECT_ROOT);
// Set timeout to 5 minutes (300 seconds) or null for no timeout
$process->setTimeout(300); // or use null for unlimited time
try {
$process->mustRun();
$result['success'] = true;
$logger->info("End EwsNotification command executed successfully: " . $process->getOutput());
$result['message'] = $process->getOutput();
} catch (ProcessFailedException $exception) {
$logger->error("End EwsNotification command failed: " . $exception->getMessage());
return ['success' => false, 'message' => $exception->getMessage()];
}
}
return ["success" => true, "message" => $translator->trans("ews_notification_end")];
}
/**
* Persist distinct message texts for a user so create/update history is preserved.
* Re-using the same message bumps last_used_at; new text inserts a new row.
*
* @param array<string, string> $messages keyed by locale (e.g. en/ar)
*/
public function saveUserMessageSuggestions(int $userId, array $messages): void
{
if ($userId <= 0 || empty($messages)) {
return;
}
$db = Db::get();
$now = (new \DateTime())->format('Y-m-d H:i:s');
foreach ($messages as $language => $text) {
$text = trim((string) $text);
if ($text === '') {
continue;
}
$hash = hash('sha256', $text);
$existingId = $db->fetchOne(
'SELECT id FROM ews_user_message_suggestions WHERE user_id = ? AND message_hash = ?',
[$userId, $hash]
);
if ($existingId) {
$db->executeStatement(
'UPDATE ews_user_message_suggestions SET last_used_at = ?, language = COALESCE(?, language) WHERE id = ?',
[$now, is_string($language) ? $language : null, $existingId]
);
} else {
$db->insert('ews_user_message_suggestions', [
'user_id' => $userId,
'message' => $text,
'message_hash' => $hash,
'language' => is_string($language) ? $language : null,
'last_used_at' => $now,
'created_at' => $now,
]);
}
}
}
/**
* Search previously used EWS notification messages for an authenticated user.
* Only searches when $q has at least 2 characters.
* Reads from ews_user_message_suggestions so updates keep prior message history.
*
* @return array{success: bool, data: list<array{message: string, last_used_at: string|null, created_at: string|null}>}
*/
public function getUserMessageSuggestions(int $userId, string $q, int $limit = 10): array
{
$q = trim($q);
$limit = max(1, min(10, $limit));
if (mb_strlen($q) < 2) {
return ['success' => true, 'data' => []];
}
$db = Db::get();
$qb = $db->createQueryBuilder();
$qb->select([
'message',
'last_used_at',
'created_at',
])
->from('ews_user_message_suggestions')
->where('user_id = :userId')
->andWhere('message LIKE :search')
->orderBy('last_used_at', 'DESC')
->addOrderBy('created_at', 'DESC')
->setMaxResults($limit)
->setParameter('userId', $userId)
->setParameter('search', '%' . $q . '%');
$rows = $qb->execute()->fetchAllAssociative();
$data = [];
foreach ($rows as $row) {
$data[] = [
'message' => (string) $row['message'],
'last_used_at' => $row['last_used_at'] ?? null,
'created_at' => $row['created_at'] ?? null,
];
}
return ['success' => true, 'data' => $data];
}
/**
* Build Alert History rows for Twitter image from Update versions.
* Multiple alert actions on the same version are clubbed into one row.
*
* Mapping:
* Change Location → Update Alert Location
* Change Type → Lower Alert / Raise Alert
* Change Date or Time → Update Alert Period
* Change Status → Update Alert Status
*/
public function buildAlertHistoryImageEntries(
EwsNotification $notification,
bool $includeCurrentState = true,
$translator = null,
string $lang = 'en'
): array {
$lang = strtolower($lang) === 'ar' ? 'ar' : 'en';
$timezone = new \DateTimeZone(defined('TIMEZONE') ? TIMEZONE : 'Asia/Riyadh');
$snapshots = [];
$versions = $notification->getVersions() ?: [];
foreach ($versions as $version) {
if ($version->getNote() !== 'Update') {
continue;
}
$view = $version->loadData();
if (!$view instanceof EwsNotification) {
continue;
}
$actions = $this->extractRelevantHistoryActions($view, $translator, $lang);
if ($actions === []) {
continue;
}
$snapshots[] = [
'ts' => (int) ($version->getDate() ?: $view->getModificationDate() ?: $view->getCreationDate()),
'obj' => $view,
'actions' => $actions,
];
}
// Include the in-memory updated state (not yet saved as a version).
if ($includeCurrentState) {
$currentActions = $this->extractRelevantHistoryActions($notification, $translator, $lang);
if ($currentActions !== []) {
$snapshots[] = [
'ts' => time(),
'obj' => $notification,
'actions' => $currentActions,
];
}
}
usort($snapshots, static fn($a, $b) => ($a['ts'] ?? 0) <=> ($b['ts'] ?? 0));
$entries = [];
$hasNewAlert = false;
$titleSeparator = $lang === 'ar' ? ' | ' : ' | ';
foreach ($snapshots as $index => $snap) {
/** @var EwsNotification $obj */
$obj = $snap['obj'];
$previous = $index > 0 ? $snapshots[$index - 1]['obj'] : null;
$timestamp = $this->formatAlertHistoryTimestamp((int) $snap['ts'], $timezone);
$color = $this->resolveAlertHistoryDotColor($obj);
$clubbedActions = [];
$titleParts = [];
$detailParts = [];
$primaryKey = '';
foreach ($snap['actions'] as $actionMeta) {
$key = $actionMeta['key'];
$title = $actionMeta['title'];
if ($key === 'New Alert') {
$hasNewAlert = true;
}
if ($primaryKey === '') {
$primaryKey = $key;
}
$detail = $this->buildAlertHistoryActionDetail(
$key,
$obj,
$previous instanceof EwsNotification ? $previous : null,
$timezone,
$translator,
$lang
);
$titleParts[] = $title;
if ($detail !== '') {
$detailParts[] = $detail;
}
$clubbedActions[] = [
'title' => $title,
'detail' => $detail,
'key' => $key,
];
}
$entries[] = [
'timestamp' => $timestamp,
'title' => implode($titleSeparator, $titleParts),
'titleKey' => $primaryKey,
'detail' => implode("\n", $detailParts),
'actions' => $clubbedActions,
'color' => $color,
'ts' => (int) $snap['ts'],
];
}
if (!$hasNewAlert) {
$createdTs = (int) ($notification->getCreationDate() ?: time());
$newAlertTitle = $this->transAlertHistory('New Alert', $translator, $lang);
array_unshift($entries, [
'timestamp' => $this->formatAlertHistoryTimestamp($createdTs, $timezone),
'title' => $newAlertTitle,
'titleKey' => 'New Alert',
'detail' => '',
'actions' => [
[
'title' => $newAlertTitle,
'detail' => '',
'key' => 'New Alert',
],
],
'color' => $this->resolveAlertHistoryDotColor($notification),
'ts' => $createdTs,
]);
}
usort($entries, static fn($a, $b) => ($b['ts'] ?? 0) <=> ($a['ts'] ?? 0));
return array_values($entries);
}
/**
* Render Alert History HTML and convert to a PNG asset with wkhtmltoimage.
*/
public function generateAlertHistoryTwitterImage(
EwsNotification $notification,
$templating,
$logger = null,
bool $includeCurrentState = true,
$translator = null,
string $lang = 'en'
): ?\Pimcore\Model\Asset {
$lang = strtolower($lang) === 'ar' ? 'ar' : 'en';
$entries = $this->buildAlertHistoryImageEntries($notification, $includeCurrentState, $translator, $lang);
if ($entries === []) {
return null;
}
$timezone = new \DateTimeZone(defined('TIMEZONE') ? TIMEZONE : 'Asia/Riyadh');
$startedTs = (int) ($notification->getCreationDate() ?: time());
foreach ($entries as $entry) {
$isNewAlert = ($entry['titleKey'] ?? '') === 'New Alert';
if (!$isNewAlert && !empty($entry['actions']) && is_array($entry['actions'])) {
foreach ($entry['actions'] as $action) {
if (($action['key'] ?? '') === 'New Alert') {
$isNewAlert = true;
break;
}
}
}
if ($isNewAlert && !empty($entry['ts'])) {
$startedTs = min($startedTs, (int) $entry['ts']);
}
}
$html = $templating->render('image_templates/alert-history.html.twig', [
'lang' => $lang,
'isRtl' => $lang === 'ar',
'labels' => [
'alertHistory' => $this->transAlertHistory('Alert History', $translator, $lang),
],
'startedOn' => $this->formatAlertHistoryStartedOn($startedTs, $timezone, $translator, $lang),
'entries' => $entries,
]);
$container = \Pimcore::getContainer();
/** @var SnappyImage $snappyImage */
$snappyImage = $container->has('knp_snappy.image')
? $container->get('knp_snappy.image')
: $container->get(SnappyImage::class);
$snappyImage->setOption('enable-local-file-access', true);
$snappyImage->setOption('width', '776');
$snappyImage->setOption('format', 'png');
$snappyImage->setOption('quality', 100);
$imageBinary = $snappyImage->getOutputFromHtml($html);
if (!$imageBinary) {
if ($logger) {
$logger->error('wkhtmltoimage returned empty output for EWS alert history image');
}
return null;
}
return \App\Lib\Utility::createAsset(
$imageBinary,
uniqid('', true) . '_ews_alert_history_' . $lang . '.png',
'EWS Twitter Images'
);
}
/**
* @return array<int, array{key: string, title: string}>
*/
private function extractRelevantHistoryActions(EwsNotification $notification, $translator = null, string $lang = 'en'): array
{
$titles = [];
$seenKeys = [];
$actions = $notification->getAlertAction() ?: [];
foreach ($actions as $action) {
if (!$action) {
continue;
}
$key = $this->mapAlertActionToHistoryTitle((string) $action->getName('en'));
if ($key === null || isset($seenKeys[$key])) {
continue;
}
$seenKeys[$key] = true;
$localized = (string) ($action->getName($lang) ?: '');
$titles[] = [
'key' => $key,
'title' => $localized !== '' ? $localized : $this->transAlertHistory($key, $translator, $lang),
];
}
return $titles;
}
/**
* Map stored alert-action / change labels to canonical history keys.
*/
private function mapAlertActionToHistoryTitle(string $nameEn): ?string
{
$haystack = strtolower(trim($nameEn));
if ($haystack === '') {
return null;
}
if (str_contains($haystack, 'new alert')) {
return 'New Alert';
}
if (str_contains($haystack, 'raise')) {
return 'Raise Alert';
}
if (str_contains($haystack, 'lower')) {
return 'Lower Alert';
}
if (str_contains($haystack, 'location') || $haystack === 'change location') {
return 'Update Alert Location';
}
if (
str_contains($haystack, 'period')
|| str_contains($haystack, 'change date')
|| str_contains($haystack, 'change time')
|| str_contains($haystack, 'date or time')
|| (str_contains($haystack, 'time') && str_contains($haystack, 'update'))
) {
return 'Update Alert Period';
}
if (str_contains($haystack, 'status') || $haystack === 'change status') {
return 'Update Alert Status';
}
return null;
}
private function buildAlertHistoryActionDetail(
string $titleKey,
EwsNotification $current,
?EwsNotification $previous,
\DateTimeZone $timezone,
$translator = null,
string $lang = 'en'
): string {
if (!$previous) {
return '';
}
if ($titleKey === 'Raise Alert' || $titleKey === 'Lower Alert') {
$from = $this->resolveAlertSeverityLabel($previous, $lang, $translator);
$to = $this->resolveAlertSeverityLabel($current, $lang, $translator);
if ($from !== '' && $to !== '' && $from !== $to) {
return $this->transAlertHistory(
'Severity changed from %from% to %to%',
$translator,
$lang,
['%from%' => $from, '%to%' => $to]
);
}
}
if ($titleKey === 'Update Alert Location') {
$currentNames = $this->extractGovernorateNamesForHistory($current, $lang);
$previousNames = $this->extractGovernorateNamesForHistory($previous, $lang);
$added = array_values(array_diff($currentNames, $previousNames));
$removed = array_values(array_diff($previousNames, $currentNames));
$nameSeparator = $lang === 'ar' ? '، ' : ', ';
$parts = [];
if ($added) {
$parts[] = $this->transAlertHistory(
'Added governorates: %names%',
$translator,
$lang,
['%names%' => implode($nameSeparator, $added)]
);
}
if ($removed) {
$parts[] = $this->transAlertHistory(
'Removed governorates: %names%',
$translator,
$lang,
['%names%' => implode($nameSeparator, $removed)]
);
}
return implode('. ', $parts);
}
if ($titleKey === 'Update Alert Period') {
$from = $this->formatAlertHistoryPeriodRange($previous, $timezone);
$to = $this->formatAlertHistoryPeriodRange($current, $timezone);
if ($from !== $to) {
return $this->transAlertHistory(
'Time changed from %from% to %to%',
$translator,
$lang,
['%from%' => $from, '%to%' => $to]
);
}
}
if ($titleKey === 'Update Alert Status') {
$from = (string) ($previous->getAlertStatus()?->getName($lang) ?? $previous->getAlertStatus()?->getName('en') ?? '');
$to = (string) ($current->getAlertStatus()?->getName($lang) ?? $current->getAlertStatus()?->getName('en') ?? '');
if ($from !== '' && $to !== '' && $from !== $to) {
return $this->transAlertHistory(
'Status changed from %from% to %to%',
$translator,
$lang,
['%from%' => $from, '%to%' => $to]
);
}
}
return '';
}
/**
* @return string[]
*/
private function extractGovernorateNamesForHistory(EwsNotification $notification, string $lang = 'en'): array
{
$names = [];
$fallback = $lang === 'ar' ? 'en' : 'ar';
foreach ($notification->getGovernorate() ?: [] as $gov) {
if ($gov) {
$name = (string) ($gov->getName($lang) ?: $gov->getName($fallback));
if ($name !== '') {
$names[] = $name;
}
}
}
foreach ($notification->getMapGovernorate() ?: [] as $gov) {
if ($gov) {
$name = (string) ($gov->getName($lang) ?: $gov->getName($fallback));
if ($name !== '' && !in_array($name, $names, true)) {
$names[] = $name;
}
}
}
return $names;
}
private function formatAlertHistoryPeriodRange(EwsNotification $notification, \DateTimeZone $timezone): string
{
$startDate = $notification->getStartDate();
$endDate = $notification->getEndDate();
$start = $startDate
? ($startDate instanceof \Carbon\Carbon ? $startDate->copy()->setTimezone($timezone) : (clone $startDate)->setTimezone($timezone))
: null;
$end = $endDate
? ($endDate instanceof \Carbon\Carbon ? $endDate->copy()->setTimezone($timezone) : (clone $endDate)->setTimezone($timezone))
: null;
$startStr = $start ? $start->format('d/m/Y H:i') : '';
$endStr = $end ? $end->format('d/m/Y H:i') : '';
if ($startStr === '' && $endStr === '') {
return '';
}
return $startStr . '–' . $endStr;
}
private function formatAlertHistoryTimestamp(int $timestamp, \DateTimeZone $timezone): string
{
if ($timestamp <= 0) {
return '';
}
$dt = (new \DateTime('@' . $timestamp))->setTimezone($timezone);
return $dt->format('d/m/Y') . ' | ' . $dt->format('H:i');
}
private function formatAlertHistoryStartedOn(int $timestamp, \DateTimeZone $timezone, $translator = null, string $lang = 'en'): string
{
if ($timestamp <= 0) {
return '';
}
$dt = (new \DateTime('@' . $timestamp))->setTimezone($timezone);
return $this->transAlertHistory(
'Started on %date% at %time%',
$translator,
$lang,
[
'%date%' => $dt->format('d/m/Y'),
'%time%' => $dt->format('H:i'),
]
);
}
private function transAlertHistory(string $key, $translator = null, string $lang = 'en', array $params = []): string
{
$fallback = [
'en' => [
'Alert History' => 'Alert History',
'New Alert' => 'New Alert',
'Raise Alert' => 'Raise Alert',
'Lower Alert' => 'Lower Alert',
'Update Alert Location' => 'Update Alert Location',
'Update Alert Period' => 'Update Alert Period',
'Update Alert Status' => 'Update Alert Status',
'Started on %date% at %time%' => 'Started on %date% at %time%',
'Added governorates: %names%' => 'Added governorates: %names%',
'Removed governorates: %names%' => 'Removed governorates: %names%',
'Time changed from %from% to %to%' => 'Time changed from %from% to %to%',
'Status changed from %from% to %to%' => 'Status changed from %from% to %to%',
'Severity changed from %from% to %to%' => 'Severity changed from %from% to %to%',
'Red' => 'Red',
'Orange' => 'Orange',
'Yellow' => 'Yellow',
'Green' => 'Green',
],
'ar' => [
'Alert History' => 'سجل التنبيه',
'New Alert' => 'تنبيه جديد',
'Raise Alert' => 'رفع مستوى التنبيه',
'Lower Alert' => 'خفض مستوى التنبيه',
'Update Alert Location' => 'تحديث موقع التنبيه',
'Update Alert Period' => 'تحديث فترة التنبيه',
'Update Alert Status' => 'تحديث حالة التنبيه',
'Started on %date% at %time%' => 'بدأ في %date% الساعة %time%',
'Added governorates: %names%' => 'المحافظات المضافة: %names%',
'Removed governorates: %names%' => 'المحافظات المحذوفة: %names%',
'Time changed from %from% to %to%' => 'تم تغيير الوقت من %from% إلى %to%',
'Status changed from %from% to %to%' => 'تم تغيير الحالة من %from% إلى %to%',
'Severity changed from %from% to %to%' => 'تم تغيير الشدة من %from% إلى %to%',
'Red' => 'أحمر',
'Orange' => 'برتقالي',
'Yellow' => 'أصفر',
'Green' => 'أخضر',
],
];
$applyParams = static function (string $text) use ($params): string {
foreach ($params as $search => $replace) {
$text = str_replace((string) $search, (string) $replace, $text);
}
return $text;
};
if ($translator && method_exists($translator, 'trans')) {
$translated = $translator->trans($key, $params, null, $lang);
if (is_string($translated) && $translated !== '') {
// Symfony returns the message id when missing; prefer our bilingual fallback.
$untranslated = $applyParams($key);
if (!($lang !== 'en' && $translated === $untranslated && isset($fallback[$lang][$key]))) {
return $translated;
}
}
}
$text = $fallback[$lang][$key] ?? ($fallback['en'][$key] ?? $key);
return $applyParams($text);
}
private function resolveAlertHistoryDotColor(EwsNotification $notification): string
{
$color = strtolower((string) ($notification->getAlertType()?->getColor() ?? 'red'));
if (in_array($color, ['red', 'orange', 'yellow', 'green'], true)) {
return $color;
}
return 'red';
}
}