db = $dbConnection; } /** * Process a location update from any source. * * @param int|string $passengerId * @param float $lat * @param float $lng * @param string $source Enum: 'app_usage', 'geofence', 'silent_push' * @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); $zone = null; // 2. Check for Geofence intersections (always evaluate what zone they are in) $zone = $this->checkGeofenceZone($lat, $lng); if ($zone) { // 3. Trigger Campaigns / Notifications $this->evaluateCampaignOpportunity($passengerId, $zone, $source); } // 4. Update Driver Demand Map $this->updateDemandMap($passengerId, $lat, $lng); // 5. Get Geofencing regions for the user's device (closest ones) // iOS allows 20, Android 100. We'll return top 20 by default. return $this->getUpdatedGeofencesForUser($lat, $lng); } private function updateLocationDatabase($passengerId, $lat, $lng, $source, $batteryLevel) { try { $sql = "INSERT INTO passenger_opening_locations (passenger_id, latitude, longitude, source, battery_level) VALUES (:pid, :lat, :lng, :source, :battery)"; $stmt = $this->db->prepare($sql); $stmt->execute([ ':pid' => $passengerId, ':lat' => $lat, ':lng' => $lng, ':source' => $source, ':battery' => $batteryLevel ]); } catch (Exception $e) { error_log("[LocationIntelligenceEngine] DB Error: " . $e->getMessage()); } } private function checkGeofenceZone($lat, $lng) { // Find if the user's lat/lng is within the radius of any active geofence zone // Using Haversine formula $sql = "SELECT id, zone_name, country_code, radius_meters, (6371000 * acos(cos(radians(:lat)) * cos(radians(latitude)) * cos(radians(longitude) - radians(:lng)) + sin(radians(:lat)) * sin(radians(latitude)))) AS distance FROM geofence_zones WHERE is_active = 1 HAVING distance <= radius_meters ORDER BY distance ASC LIMIT 1"; $stmt = $this->db->prepare($sql); $stmt->execute([':lat' => $lat, ':lng' => $lng]); return $stmt->fetch(PDO::FETCH_ASSOC); } private function evaluateCampaignOpportunity($passengerId, $zone, $source) { // 1. Check if passenger received a campaign recently (Anti-Spam) $sqlSpamCheck = "SELECT COUNT(*) FROM marketing_campaigns_log WHERE passenger_id = :pid AND message_type = 'push' AND sent_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)"; $stmtSpam = $this->db->prepare($sqlSpamCheck); $stmtSpam->execute([':pid' => $passengerId]); $spamCount = intval($stmtSpam->fetchColumn()); if ($spamCount == 0) { // 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'] . " واستمتع بتجربة سيرو!"; // 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'] ]); } } private function updateDemandMap($passengerId, $lat, $lng) { // Broadcast this location to drivers or update a demand heat map cache // Here we insert into passengerlocation to log the hotspot. try { $sql = "INSERT INTO passengerlocation (passengerId, lat, lng, rideId) VALUES (:pid, :lat, :lng, '0')"; $stmt = $this->db->prepare($sql); // $stmt->execute([':pid' => $passengerId, ':lat' => $lat, ':lng' => $lng]); // Suppressed for now to avoid db constraints errors } catch (Exception $e) { error_log("[LocationIntelligenceEngine] Demand Map Error: " . $e->getMessage()); } } private function getUpdatedGeofencesForUser($lat, $lng, $limit = 20) { // Return top nearest geofences to update on the user's device $sql = "SELECT id, zone_name, latitude, longitude, radius_meters, (6371000 * acos(cos(radians(:lat)) * cos(radians(latitude)) * cos(radians(longitude) - radians(:lng)) + sin(radians(:lat)) * sin(radians(latitude)))) AS distance FROM geofence_zones WHERE is_active = 1 ORDER BY distance ASC LIMIT :limit"; $stmt = $this->db->prepare($sql); $stmt->bindParam(':lat', $lat); $stmt->bindParam(':lng', $lng); $stmt->bindValue(':limit', (int) $limit, PDO::PARAM_INT); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_ASSOC); } } ?>