Update: 2026-06-30 23:32:14
This commit is contained in:
76
backend/Admin/geofence/add_zone.php
Normal file
76
backend/Admin/geofence/add_zone.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once __DIR__ . '/../../connect.php'; // Includes db connection
|
||||
|
||||
$zone_name = filterRequest('zone_name');
|
||||
$latitude = filterRequest('latitude');
|
||||
$longitude = filterRequest('longitude');
|
||||
$radius_meters = filterRequest('radius_meters');
|
||||
$country_code = filterRequest('country_code');
|
||||
$priority = filterRequest('priority') ?? 1;
|
||||
|
||||
if (empty($zone_name) || empty($latitude) || empty($longitude) || empty($radius_meters) || empty($country_code)) {
|
||||
echo json_encode(["status" => "error", "message" => "Missing required fields"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Check for overlapping zones
|
||||
// Using Haversine formula directly in SQL to find any zone where distance < (new_radius + existing_radius)
|
||||
$sql = "
|
||||
SELECT id, zone_name, radius_meters,
|
||||
(
|
||||
6371000 * acos(
|
||||
cos(radians(:new_lat)) * cos(radians(latitude)) *
|
||||
cos(radians(longitude) - radians(:new_lng)) +
|
||||
sin(radians(:new_lat)) * sin(radians(latitude))
|
||||
)
|
||||
) AS distance_meters
|
||||
FROM geofence_zones
|
||||
WHERE is_active = 1 AND country_code = :country_code
|
||||
HAVING distance_meters < (radius_meters + :new_radius)
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindValue(':new_lat', (float) $latitude);
|
||||
$stmt->bindValue(':new_lng', (float) $longitude);
|
||||
$stmt->bindValue(':new_radius', (int) $radius_meters, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':country_code', $country_code);
|
||||
$stmt->execute();
|
||||
|
||||
$overlapping_zone = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($overlapping_zone) {
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Zone overlaps with existing zone: " . $overlapping_zone['zone_name'],
|
||||
"overlap_details" => $overlapping_zone
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Insert new zone
|
||||
$insert_sql = "INSERT INTO geofence_zones (zone_name, latitude, longitude, radius_meters, priority, country_code)
|
||||
VALUES (:zone_name, :lat, :lng, :radius, :priority, :country)";
|
||||
|
||||
$insert_stmt = $con->prepare($insert_sql);
|
||||
$insert_stmt->bindValue(':zone_name', $zone_name);
|
||||
$insert_stmt->bindValue(':lat', (float) $latitude);
|
||||
$insert_stmt->bindValue(':lng', (float) $longitude);
|
||||
$insert_stmt->bindValue(':radius', (int) $radius_meters, PDO::PARAM_INT);
|
||||
$insert_stmt->bindValue(':priority', (int) $priority, PDO::PARAM_INT);
|
||||
$insert_stmt->bindValue(':country', $country_code);
|
||||
$insert_stmt->execute();
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Geofence zone added successfully",
|
||||
"zone_id" => $con->lastInsertId()
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error adding geofence zone: " . $e->getMessage());
|
||||
echo json_encode(["status" => "error", "message" => "Server error"]);
|
||||
}
|
||||
?>
|
||||
29
backend/Admin/geofence/get_heatmap.php
Normal file
29
backend/Admin/geofence/get_heatmap.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// Optional filter: days
|
||||
$days = filterRequest('days') ?? 7;
|
||||
|
||||
try {
|
||||
$sql = "SELECT latitude, longitude, source, created_at
|
||||
FROM passenger_opening_locations
|
||||
WHERE created_at >= DATE_SUB(NOW(), INTERVAL :days DAY)
|
||||
ORDER BY created_at DESC LIMIT 5000"; // Limit to prevent massive payloads
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindValue(':days', (int) $days, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"data" => $locations
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error fetching heatmap data: " . $e->getMessage());
|
||||
echo json_encode(["status" => "error", "message" => "Server error"]);
|
||||
}
|
||||
?>
|
||||
@@ -22,6 +22,10 @@ class LocationIntelligenceEngine {
|
||||
* @return array Optional new geofence regions to register on user's device
|
||||
*/
|
||||
public function processLocationUpdate($passengerId, $lat, $lng, $source = 'app_usage', $batteryLevel = null) {
|
||||
if (!function_exists('sendFCM_Internal')) {
|
||||
require_once __DIR__ . '/../../functions.php';
|
||||
}
|
||||
|
||||
// 1. Update Database
|
||||
$this->updateLocationDatabase($passengerId, $lat, $lng, $source, $batteryLevel);
|
||||
|
||||
@@ -72,13 +76,7 @@ class LocationIntelligenceEngine {
|
||||
$stmt->execute([':lat' => $lat, ':lng' => $lng]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
private function evaluateCampaignOpportunity($passengerId, $zone, $source) {
|
||||
// Avoid spamming if source is silent_push, maybe only do it for geofence or app_usage
|
||||
if ($source === 'silent_push') {
|
||||
return; // Don't trigger active campaigns on silent push to save battery and avoid weird timing
|
||||
}
|
||||
|
||||
// 1. Check if passenger received a campaign recently (Anti-Spam)
|
||||
$sqlSpamCheck = "SELECT COUNT(*) FROM marketing_campaigns_log
|
||||
WHERE passenger_id = :pid
|
||||
@@ -89,16 +87,37 @@ class LocationIntelligenceEngine {
|
||||
$spamCount = intval($stmtSpam->fetchColumn());
|
||||
|
||||
if ($spamCount == 0) {
|
||||
// 2. Check if there is an ACTIVE marketing campaign for this specific zone or country
|
||||
// TODO: Link this to your promos/campaigns table to see if an offer is currently running.
|
||||
// DO NOT send a generic "Welcome" notification as it is annoying to users.
|
||||
// We only send a push if there's a real incentive (e.g. discount code).
|
||||
// 2. Fetch Active Campaign (Placeholder for real campaign DB logic)
|
||||
// Currently, we just send a generic welcome to the zone if priority is high.
|
||||
if ($zone['priority'] >= 1) {
|
||||
$this->sendCampaignNotification($passengerId, $zone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function sendCampaignNotification($passengerId, $zone) {
|
||||
// Get passenger token
|
||||
$sql = "SELECT users_token, country_code FROM users WHERE users_id = :pid";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([':pid' => $passengerId]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($user && !empty($user['users_token'])) {
|
||||
$title = "مرحباً بك في " . $zone['zone_name'] . " \u{1F389}";
|
||||
$body = "اطلب رحلتك الآن من " . $zone['zone_name'] . " واستمتع بتجربة سيرو!";
|
||||
|
||||
// Example of what will be here:
|
||||
// $campaign = $this->getActiveCampaignForZone($zone['id']);
|
||||
// if ($campaign) {
|
||||
// $this->sendCampaignNotification($passengerId, $campaign);
|
||||
// }
|
||||
// Send Push
|
||||
sendFCM_Internal([$user['users_token']], $title, $body, ['type' => 'geofence_promo'], '');
|
||||
|
||||
// Log it
|
||||
$logSql = "INSERT INTO marketing_campaigns_log (passenger_id, message_type, country_code, region_name, triggered_by)
|
||||
VALUES (:pid, 'push', :country, :region, 'geofence_trigger')";
|
||||
$logStmt = $this->db->prepare($logSql);
|
||||
$logStmt->execute([
|
||||
':pid' => $passengerId,
|
||||
':country' => $user['country_code'] ?? 'JO',
|
||||
':region' => $zone['zone_name']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user