diff --git a/backend/core/bootstrap.php b/backend/core/bootstrap.php
index b048ba5..2829246 100644
--- a/backend/core/bootstrap.php
+++ b/backend/core/bootstrap.php
@@ -60,14 +60,16 @@ require_once __DIR__ . '/helpers.php';
$envFile = getenv('ENV_FILE_PATH') ?: (__DIR__ . '/../.env');
loadEnvironment($envFile);
-// 4. Redis Connection (Singleton)
+// 4. Redis Connections (Dual Architecture)
$redis = null;
+$redisLocation = null;
try {
if (extension_loaded('redis')) {
+ // --- Main Server Redis ---
$redis = new Redis();
- $redisHost = getenv('REDIS_HOST') ?: '127.0.0.1';
- $redisPort = (int)(getenv('REDIS_PORT') ?: 6379);
- $redisPass = getenv('REDIS_PASSWORD');
+ $redisHost = getenv('REDIS_MAIN_HOST') ?: getenv('REDIS_HOST') ?: '127.0.0.1';
+ $redisPort = (int)(getenv('REDIS_MAIN_PORT') ?: getenv('REDIS_PORT') ?: 6379);
+ $redisPass = getenv('REDIS_MAIN_PASSWORD') ?: getenv('REDIS_MAIN_AUTH') ?: getenv('REDIS_PASSWORD') ?: getenv('REDIS_AUTH');
if ($redis->connect($redisHost, $redisPort, 1.5)) {
if ($redisPass) $redis->auth($redisPass);
@@ -75,10 +77,24 @@ try {
} else {
$redis = null;
}
+
+ // --- Location Server Redis ---
+ $redisLocation = new Redis();
+ $locHost = getenv('REDIS_LOCATION_HOST') ?: $redisHost;
+ $locPort = (int)(getenv('REDIS_LOCATION_PORT') ?: $redisPort);
+ $locPass = getenv('REDIS_LOCATION_PASSWORD') ?: $redisPass;
+
+ if ($redisLocation->connect($locHost, $locPort, 1.5)) {
+ if ($locPass) $redisLocation->auth($locPass);
+ // No prefix for location server
+ } else {
+ $redisLocation = null;
+ }
}
} catch (Exception $e) {
error_log("[REDIS] Connection failed: " . $e->getMessage());
$redis = null;
+ $redisLocation = null;
}
// 5. تحميل الـ Services الأساسية
diff --git a/backend/ride/driver_scam/add.php b/backend/ride/driver_scam/add.php
index 4080508..04e5e21 100644
--- a/backend/ride/driver_scam/add.php
+++ b/backend/ride/driver_scam/add.php
@@ -13,6 +13,11 @@ if ($isDriverCallPassenger === null || $isDriverCallPassenger === "") {
$isDriverCallPassenger = "0";
}
+if (!$driverID || !$passengerID || !$rideID) {
+ jsonError("Missing required fields");
+ exit();
+}
+
// استخدام التاريخ الحالي
$dateCreated = date("Y-m-d H:i:s");
@@ -42,6 +47,16 @@ $stmt->bindParam(":dateCreated", $dateCreated);
$stmt->execute();
if ($stmt->rowCount() > 0) {
+ // Invalidate Redis cache key for this driver
+ if (isset($redis) && $redis !== null && $driverID) {
+ try {
+ $today = date("Y-m-d");
+ $redisKey = "driver:scam_count:" . $driverID . ":" . $today;
+ $redis->del($redisKey);
+ } catch (Exception $e) {
+ error_log("[add.php] Redis cache invalidation failed: " . $e->getMessage());
+ }
+ }
jsonSuccess(null, "Driver ride scam data saved successfully");
} else {
jsonError("Failed to save driver ride scam data");
diff --git a/backend/ride/driver_scam/get.php b/backend/ride/driver_scam/get.php
index e1d4559..b7bf9bd 100644
--- a/backend/ride/driver_scam/get.php
+++ b/backend/ride/driver_scam/get.php
@@ -9,22 +9,44 @@ if (!$driverID) {
exit();
}
+$today = date("Y-m-d");
+$redisKey = "driver:scam_count:" . $driverID . ":" . $today;
+$cachedData = null;
+
+// 1. Try to read from Redis
+if (isset($redis) && $redis !== null) {
+ try {
+ $cachedData = $redis->get($redisKey);
+ if ($cachedData !== false && $cachedData !== null) {
+ $rows = json_decode($cachedData, true);
+ if (!empty($rows)) {
+ echo json_encode(array("status" => "success", "message" => $rows));
+ exit();
+ } else {
+ jsonError("No ride scam record found");
+ exit();
+ }
+ }
+ } catch (Exception $e) {
+ error_log("[get.php] Redis read failed: " . $e->getMessage());
+ }
+}
+
+// 2. Fallback to SQL Database
$sql = "SELECT
DATE(driver_ride_scam.dateCreated) AS date,
CAST(COUNT(driver_ride_scam.id) AS CHAR) AS count
FROM
driver_ride_scam
-LEFT JOIN
+INNER JOIN
ride ON ride.id = driver_ride_scam.rideID
- AND ride.status = 'Cancel'
+ AND (ride.status LIKE 'Cancel%' OR ride.status LIKE 'cancel%' OR ride.status = 'cancelled_no_driver_found')
WHERE
driver_ride_scam.driverID = :driverID
AND driver_ride_scam.dateCreated >= CURDATE()
AND driver_ride_scam.dateCreated < DATE_ADD(CURDATE(), INTERVAL 1 DAY)
GROUP BY
- DATE(driver_ride_scam.dateCreated)
-ORDER BY
- date DESC";
+ DATE(driver_ride_scam.dateCreated)";
try {
$stmt = $con->prepare($sql);
@@ -33,6 +55,15 @@ try {
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ // 3. Cache the results in Redis (TTL of 60 seconds)
+ if (isset($redis) && $redis !== null) {
+ try {
+ $redis->set($redisKey, json_encode($rows), 60);
+ } catch (Exception $e) {
+ error_log("[get.php] Redis write failed: " . $e->getMessage());
+ }
+ }
+
if (!empty($rows)) {
// --- FIX IS HERE ---
// Your Flutter app looks for d['message'].
diff --git a/backend/ride/heatmap/heatmap_live.php b/backend/ride/heatmap/heatmap_live.php
new file mode 100644
index 0000000..088fb9f
--- /dev/null
+++ b/backend/ride/heatmap/heatmap_live.php
@@ -0,0 +1,72 @@
+keys("demand:grid:*");
+} catch (Exception $e) {
+ error_log("[heatmap_live.php] Redis keys error: " . $e->getMessage());
+ echo json_encode([]);
+ exit();
+}
+
+$heatmap_data = [];
+
+foreach ($keys as $key) {
+ // The keys returned by $redis->keys() will actually contain the 'siro:' prefix
+ // e.g. siro:demand:grid:33.5135_36.2735
+ $parts = explode(":", $key);
+ $coords = explode("_", end($parts));
+
+ if (count($coords) == 2) {
+ $lat = (float)$coords[0];
+ $lng = (float)$coords[1];
+
+ // We must strip 'siro:' to use $redis->get() because $redis auto-prefixes everything!
+ // Actually, $redis->keys() returns the physical key "siro:demand:grid:X"
+ // But $redis->get("demand:grid:X") automatically prepends "siro:".
+ // So we must strip the "siro:" part before passing to get()
+ $clean_key = str_replace("siro:", "", $key);
+ $count = (int)$redis->get($clean_key);
+
+ // Fetch active drivers using Location Redis
+ $available_drivers = 0;
+ try {
+ global $redisLocation;
+ if (isset($redisLocation) && $redisLocation !== null) {
+ $drivers = $redisLocation->georadius('geo:drivers:available', $lng, $lat, 0.75, 'km');
+ $availableDrivers = count($drivers);
+ }
+ } catch (Exception $e) {}
+
+ $intensity = 'low';
+ $surge_ratio = ($available_drivers > 0) ? ($count / $available_drivers) : $count;
+
+ if ($surge_ratio > 2.0 || $count >= 5) {
+ $intensity = 'high';
+ } else if ($surge_ratio > 1.2 || $count >= 3) {
+ $intensity = 'medium';
+ }
+
+ $heatmap_data[] = [
+ "lat" => $lat,
+ "lng" => $lng,
+ "count" => $count,
+ "intensity" => $intensity
+ ];
+ }
+}
+
+// Output the JSON array as expected by home_captain_controller.dart
+header('Content-Type: application/json');
+echo json_encode($heatmap_data);
+?>
diff --git a/backend/ride/heatmap/log_demand.php b/backend/ride/heatmap/log_demand.php
new file mode 100644
index 0000000..94e9357
--- /dev/null
+++ b/backend/ride/heatmap/log_demand.php
@@ -0,0 +1,43 @@
+incr($redisKey);
+
+ // If this is the first request, set the expiry to 60 seconds
+ if ($currentCount == 1) {
+ $redis->expire($redisKey, 60);
+ }
+
+ jsonSuccess(["grid_id" => $grid_id, "count" => $currentCount], "Demand logged successfully");
+} catch (Exception $e) {
+ error_log("[log_demand.php] Redis error: " . $e->getMessage());
+ jsonError("Error logging demand");
+}
+?>
diff --git a/backend/ride/location/getSpeed.php b/backend/ride/location/getSpeed.php
index 6345fa6..cd880d4 100644
--- a/backend/ride/location/getSpeed.php
+++ b/backend/ride/location/getSpeed.php
@@ -1,6 +1,7 @@
= NOW() - INTERVAL :freshSeconds SECOND
- ORDER BY updated_at DESC
- LIMIT 100; -- نجلب 100 مرشح محتمل
- ";
+ // حساب تقريبي لنصف القطر بالكيلومترات بناءً على الصندوق (Bounding Box)
+ $earth_radius = 6371;
+ $dLat = deg2rad($southwestLat - $centerLat);
+ $dLon = deg2rad($southwestLon - $centerLon);
+ $a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($centerLat)) * cos(deg2rad($southwestLat)) * sin($dLon/2) * sin($dLon/2);
+ $c = 2 * asin(sqrt($a));
+ $radiusKm = max(1, ($earth_radius * $c) + 1);
- $stmt_locations = $con_tracking->prepare($sql_locations);
- $stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
- $stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
- $stmt_locations->execute();
- $locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
+ // سحب معرفات السائقين المتاحين حول الراكب من سيرفر المواقع (Dual Redis)
+ $driver_ids = [];
+ if (isset($redisLocation)) {
+ $redisResults = $redisLocation->geoRadius('geo:drivers:available', $centerLon, $centerLat, $radiusKm, 'km');
+ if ($redisResults) {
+ foreach ($redisResults as $res) {
+ // قد يرجع Redis مصفوفة داخلية إذا تم تمرير خيارات، ولكن بالوضع الافتراضي يرجع سلاسل نصية
+ $driver_ids[] = is_array($res) ? $res[0] : $res;
+ }
+ }
+ }
- if (!$locations) {
+ if (empty($driver_ids)) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
- // الخطوة 2: تجميع معرفات السائقين (driver_id)
+ // الخطوة 2: جلب تفاصيل الموقع الدقيقة والسرعة لكل سائق من Redis Pipeline
// =================================================================
- $driver_ids = array_column($locations, 'driver_id');
+ $pipe = $redisLocation->pipeline();
+ foreach ($driver_ids as $id) {
+ $pipe->hGetAll("driver:profile:$id");
+ }
+ $profiles = $pipe->exec();
- // =================================================================
- // الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
- // =================================================================
- $drivers_info = [];
- if (!empty($driver_ids)) {
- $placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
+ $locations = [];
+ foreach ($driver_ids as $index => $id) {
+ $profile = $profiles[$index];
+ if (!$profile || empty($profile['lat'])) continue;
- // هنا نطبق الشروط الخاصة بهذا السكريبت (موديل السيارة > 2000)
- $sql_drivers_info = "
- SELECT
- d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
- cr.make, cr.model, cr.color, cr.color_hex, cr.year,
- dt.token,
- COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
- COALESCE(rdAvg.ratingCount, 0) AS ratingCount
- FROM driver d
- LEFT JOIN CarRegistration cr ON cr.driverID = d.id
- LEFT JOIN driverToken dt ON dt.captain_id = d.id
- LEFT JOIN (
- SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
- FROM ratingDriver
- GROUP BY driver_id
- ) rdAvg ON rdAvg.driver_id = d.id
- WHERE d.id IN ($placeholders)
- -- AND COALESCE(cr.year, 0) > 2000 -- ⭐ الشرط الخاص بهذا السكريبت
- -- AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
- AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
- ";
+ // تجاهل المواقع القديمة (أكثر من 3 دقائق)
+ $updatedAt = $profile['updated_at'] ?? 0;
+ if (time() - $updatedAt > 180) continue;
- $stmt_drivers_info = $con->prepare($sql_drivers_info);
- $stmt_drivers_info->execute($driver_ids);
- $drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
+ $locations[] = [
+ 'driver_id' => $id,
+ 'latitude' => $profile['lat'],
+ 'longitude' => $profile['lng'],
+ 'heading' => $profile['heading'] ?? 0,
+ 'speed' => $profile['speed'] ?? 0,
+ 'status' => 'off', // متواجدون في geo:drivers:available
+ 'updated_at' => date('Y-m-d H:i:s', $updatedAt)
+ ];
+ }
- // تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
- foreach ($drivers_info_raw as $driver) {
- $drivers_info[$driver['driver_id']] = $driver;
- }
+ if (empty($locations)) {
+ jsonError("No fresh car locations found in the specified area.");
+ exit;
}
// =================================================================
- // الخطوة 4: دمج النتائج في PHP
+ // الخطوة 3: جلب البيانات الثابتة (السيارة، الموديل، التقييم) من MySQL
+ // =================================================================
+ $drivers_info = [];
+ $valid_driver_ids = array_column($locations, 'driver_id');
+ $placeholders = implode(',', array_fill(0, count($valid_driver_ids), '?'));
+
+ $sql_drivers_info = "
+ SELECT
+ d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
+ cr.make, cr.model, cr.color, cr.color_hex, cr.year,
+ dt.token,
+ COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
+ COALESCE(rdAvg.ratingCount, 0) AS ratingCount
+ FROM driver d
+ LEFT JOIN CarRegistration cr ON cr.driverID = d.id
+ LEFT JOIN driverToken dt ON dt.captain_id = d.id
+ LEFT JOIN (
+ SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
+ FROM ratingDriver
+ GROUP BY driver_id
+ ) rdAvg ON rdAvg.driver_id = d.id
+ WHERE d.id IN ($placeholders)
+ AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
+ ";
+
+ $stmt_drivers_info = $con->prepare($sql_drivers_info);
+ $stmt_drivers_info->execute($valid_driver_ids);
+ $drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
+
+ foreach ($drivers_info_raw as $driver) {
+ $drivers_info[$driver['driver_id']] = $driver;
+ }
+
+ // =================================================================
+ // الخطوة 4: دمج النتائج والترتيب
// =================================================================
$final_results = [];
foreach ($locations as $location) {
@@ -105,9 +124,6 @@ try {
}
}
- // =================================================================
- // الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
- // =================================================================
usort($final_results, function ($a, $b) {
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
@@ -121,14 +137,14 @@ try {
$limited_results = array_slice($final_results, 0, 10);
if (empty($limited_results)) {
- jsonError("No cars matching the specific criteria (year > 2000) found.");
+ jsonError("No cars matching the specific criteria found.");
exit;
}
// =================================================================
- // الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
+ // الخطوة 5: فك التشفير وحساب العمر
// =================================================================
- $fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
+ $fieldsToDecrypt = ['phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin'];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
@@ -151,7 +167,7 @@ try {
jsonSuccess($limited_results);
} catch (PDOException $e) {
- error_log("[getSpeed.php] " . $e->getMessage());
+ error_log("[getSpeed.php PDO] " . $e->getMessage());
jsonError("An internal error occurred. Please try again later.");
} catch (Throwable $e) {
error_log("[getSpeed.php] " . $e->getMessage());
diff --git a/backend/ride/pricing/get.php b/backend/ride/pricing/get.php
index b599a63..043005f 100644
--- a/backend/ride/pricing/get.php
+++ b/backend/ride/pricing/get.php
@@ -109,6 +109,38 @@ function getPerKmRate($carType, $kazanRow) {
}
function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng, $carType = 'Speed') {
+ global $redis, $redisLocation;
+
+ $surgeMultiplier = 1.0;
+ if (isset($redis) && $redis !== null) {
+ try {
+ $grid_size = 0.0135;
+ $grid_lat = round((float)$passengerLat / $grid_size) * $grid_size;
+ $grid_lng = round((float)$passengerLng / $grid_size) * $grid_size;
+ $grid_id = $grid_lat . "_" . $grid_lng;
+
+ // Demand is handled by Main Redis (prefix automatically applied)
+ $demandCount = (int)$redis->get("demand:grid:" . $grid_id);
+ $availableDrivers = 0;
+
+ // Driver locations are handled by Location Redis (no prefix)
+ try {
+ if (isset($redisLocation) && $redisLocation !== null) {
+ $drivers = $redisLocation->georadius('geo:drivers:available', $grid_lng, $grid_lat, 0.75, 'km');
+ $availableDrivers = count($drivers);
+ }
+ } catch (Exception $e) {}
+
+ if ($demandCount > 0) {
+ $surgeRatio = ($availableDrivers > 0) ? ($demandCount / $availableDrivers) : $demandCount;
+ if ($surgeRatio > 1.2) {
+ $surgeMultiplier = 1.0 + ($surgeRatio - 1.2) * 0.5;
+ $surgeMultiplier = min(3.0, $surgeMultiplier); // Cap at 3.0
+ }
+ }
+ } catch (Exception $e) {}
+ }
+
$naturePrice = (float) ($kazanRow['naturePrice'] ?? 0);
$heavyPrice = (float) ($kazanRow['heavyPrice'] ?? 0);
$latePrice = (float) ($kazanRow['latePrice'] ?? 0);
@@ -200,6 +232,9 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
$fare = $billableDistance * $perKmSpeed;
$fare += $billableMinutes * $effectivePerMin;
+
+ // Apply Redis Geohash Surge Multiplier
+ $fare *= $surgeMultiplier;
if ($airportCtx) $fare += $airportAddon;
if ($damascusAirportBoundCtx || $isInDamascusAirportBoundCtx) {
$fare += $damascusAirportBoundAddon;
@@ -244,13 +279,27 @@ if (!empty($promo_code)) {
$negativeBalance = 0;
if (!empty($passenger_id)) {
try {
- $redis = new Redis();
- $redis->connect('127.0.0.1', 6379);
- $redisKey = "passenger_debt_" . $passenger_id;
- $redisDebt = $redis->get($redisKey);
-
- if ($redisDebt !== false) {
- $negativeBalance = (float) $redisDebt;
+ $redisInstance = null;
+ if (isset($redis) && $redis !== null) {
+ $redisInstance = $redis;
+ } else if (extension_loaded('redis')) {
+ $localRedis = new Redis();
+ $redisHost = getenv('REDIS_MAIN_HOST') ?: getenv('REDIS_HOST') ?: '127.0.0.1';
+ $redisPort = (int)(getenv('REDIS_MAIN_PORT') ?: getenv('REDIS_PORT') ?: 6379);
+ $redisPass = getenv('REDIS_MAIN_PASSWORD') ?: getenv('REDIS_MAIN_AUTH') ?: getenv('REDIS_PASSWORD') ?: getenv('REDIS_AUTH');
+ if ($localRedis->connect($redisHost, $redisPort, 1.5)) {
+ if ($redisPass) $localRedis->auth($redisPass);
+ $localRedis->setOption(Redis::OPT_PREFIX, 'siro:');
+ $redisInstance = $localRedis;
+ }
+ }
+
+ if ($redisInstance !== null) {
+ $redisKey = "passenger_debt_" . $passenger_id;
+ $redisDebt = $redisInstance->get($redisKey);
+ if ($redisDebt !== false) {
+ $negativeBalance = (float) $redisDebt;
+ }
}
} catch (Exception $e) {
$negativeBalance = 0;
diff --git a/backend/ride/rides/acceptRide.php b/backend/ride/rides/acceptRide.php
index 7cd9c3f..c82b619 100644
--- a/backend/ride/rides/acceptRide.php
+++ b/backend/ride/rides/acceptRide.php
@@ -21,12 +21,22 @@ $rideId = filterRequest("id");
$driverId = $user_id;
$status = filterRequest("status"); // القيمة التي يرسلها التطبيق: 'accepted'
$passengerToken = filterRequest("passengerToken");
+$passengerFingerprint = filterRequest("passengerFingerprint");
+$passengerIdValue = filterRequest("passenger_id");
if (empty($rideId) || empty($driverId)) {
printFailure("Missing required parameters");
exit;
}
+// Self-ride validation
+$driverFingerprint = isset($_SERVER['HTTP_X_DEVICE_FP']) ? $_SERVER['HTTP_X_DEVICE_FP'] : '';
+if (!empty($driverFingerprint) && $driverFingerprint === $passengerFingerprint) {
+ error_log("[accept_ride] Self-ride attempt blocked. DriverID=$driverId, Fingerprint=$driverFingerprint");
+ printFailure("Self-matching is not allowed");
+ exit;
+}
+
// status whitelist — لا نقبل قيمة عشوائية من التطبيق
$allowedStatuses = ['accepted', 'Apply'];
if (!in_array($status, $allowedStatuses, true)) {
@@ -158,9 +168,11 @@ try {
// ═══════════════════════════════════════════════════════════
// STEP E — جلب passenger_id وإرسال الإشعارات
// ═══════════════════════════════════════════════════════════
- $passengerId = $con->prepare("SELECT passenger_id FROM ride WHERE id = ? LIMIT 1");
- $passengerId->execute([$rideId]);
- $passengerIdValue = $passengerId->fetchColumn();
+ if (empty($passengerIdValue)) {
+ $passengerId = $con->prepare("SELECT passenger_id FROM ride WHERE id = ? LIMIT 1");
+ $passengerId->execute([$rideId]);
+ $passengerIdValue = $passengerId->fetchColumn();
+ }
if ($passengerIdValue) {
// Socket — real-time update على خريطة الراكب
diff --git a/backend/ride/rides/add_ride.php b/backend/ride/rides/add_ride.php
index 2db87cc..279eb14 100644
--- a/backend/ride/rides/add_ride.php
+++ b/backend/ride/rides/add_ride.php
@@ -242,6 +242,7 @@ try {
// STEP C — بناء الـ payload وإرسال الرحلة للسائقين
// ═══════════════════════════════════════════════════════════
$kazan = (float) $price - (float) $price_for_driver;
+ $passengerFp = isset($_SERVER['HTTP_X_DEVICE_FP']) ? $_SERVER['HTTP_X_DEVICE_FP'] : '';
$payload = [
(string) $startLat,
(string) $startLng,
@@ -249,7 +250,7 @@ try {
(string) $endLat,
(string) $endLng,
(string) $distance_text,
- "",
+ (string) $passengerFp,
(string) $passenger_id,
(string) $passenger_name,
(string) $passenger_token,
diff --git a/backend/ride/rides/cancel_ride_by_driver.php b/backend/ride/rides/cancel_ride_by_driver.php
index 2de5cba..966cf1a 100644
--- a/backend/ride/rides/cancel_ride_by_driver.php
+++ b/backend/ride/rides/cancel_ride_by_driver.php
@@ -140,18 +140,30 @@ try {
// تخزين الدين في الـ Redis لمدة 6 شهور (15552000 ثانية)
try {
- $redis = new Redis();
- $redis->connect('127.0.0.1', 6379);
- $redisPass = getenv('REDIS_PASSWORD');
- if ($redisPass) $redis->auth($redisPass);
- $redis->setOption(Redis::OPT_PREFIX, 'siro:');
- $redisKey = "passenger_debt_" . $passenger_id;
- // إضافة الدين الجديد إلى الدين السابق إن وجد
- $currentDebt = (float) $redis->get($redisKey);
- $newDebt = $currentDebt + $negativeDebt;
- $redis->setex($redisKey, 15552000, $newDebt);
+ $redisInstance = null;
+ if (isset($redis) && $redis !== null) {
+ $redisInstance = $redis;
+ } else if (extension_loaded('redis')) {
+ $localRedis = new Redis();
+ $redisHost = getenv('REDIS_MAIN_HOST') ?: getenv('REDIS_HOST') ?: '127.0.0.1';
+ $redisPort = (int)(getenv('REDIS_MAIN_PORT') ?: getenv('REDIS_PORT') ?: 6379);
+ $redisPass = getenv('REDIS_MAIN_PASSWORD') ?: getenv('REDIS_MAIN_AUTH') ?: getenv('REDIS_PASSWORD') ?: getenv('REDIS_AUTH');
+ if ($localRedis->connect($redisHost, $redisPort, 1.5)) {
+ if ($redisPass) $localRedis->auth($redisPass);
+ $localRedis->setOption(Redis::OPT_PREFIX, 'siro:');
+ $redisInstance = $localRedis;
+ }
+ }
+
+ if ($redisInstance !== null) {
+ $redisKey = "passenger_debt_" . $passenger_id;
+ // إضافة الدين الجديد إلى الدين السابق إن وجد
+ $currentDebt = (float) $redisInstance->get($redisKey);
+ $newDebt = $currentDebt + $negativeDebt;
+ $redisInstance->setex($redisKey, 15552000, $newDebt);
+ }
} catch (Exception $e) {
- error_log("Redis Error: " . $e->getMessage());
+ error_log("Redis Error in cancel_ride_by_driver: " . $e->getMessage());
}
}
}
diff --git a/backend/ride/rides/finish_ride_updates.php b/backend/ride/rides/finish_ride_updates.php
index 4b3bc30..9c97466 100644
--- a/backend/ride/rides/finish_ride_updates.php
+++ b/backend/ride/rides/finish_ride_updates.php
@@ -173,17 +173,16 @@ try {
throw new Exception("Ride already finished or not found in local DB.");
}
- // 4b. Update driver_orders
- $checkStmt = $con->prepare("SELECT order_id FROM driver_orders WHERE order_id = ?");
- $checkStmt->execute([$rideId]);
-
- if ($checkStmt->rowCount() > 0) {
- $con->prepare("UPDATE driver_orders SET driver_id = ?, status = ?, created_at = NOW() WHERE order_id = ?")
- ->execute([$driver_id, $newStatus, $rideId]);
- } else {
- $con->prepare("INSERT INTO driver_orders (driver_id, order_id, created_at, status) VALUES (?, ?, NOW(), ?)")
- ->execute([$driver_id, $rideId, $newStatus]);
- }
+ // 4b. Update driver_orders (Optimized atomic query)
+ $stmtOrders = $con->prepare("
+ INSERT INTO `driver_orders` (`driver_id`, `order_id`, `status`, `created_at`)
+ VALUES (?, ?, ?, NOW())
+ ON DUPLICATE KEY UPDATE
+ `driver_id` = VALUES(`driver_id`),
+ `status` = VALUES(`status`),
+ `created_at` = NOW()
+ ");
+ $stmtOrders->execute([$driver_id, $rideId, $newStatus]);
// ============================================================
// 4c. Server-to-Server Payment Processing (S2S)
diff --git a/backend/ride/rides/retry_search_drivers.php b/backend/ride/rides/retry_search_drivers.php
index 35ce9c7..beefebe 100644
--- a/backend/ride/rides/retry_search_drivers.php
+++ b/backend/ride/rides/retry_search_drivers.php
@@ -48,6 +48,7 @@ try {
// 3. حساب العمولة (Kazan)
$kazan = (double)$price - (double)$priceForDriver;
+ $passengerFp = isset($_SERVER['HTTP_X_DEVICE_FP']) ? $_SERVER['HTTP_X_DEVICE_FP'] : '';
// 4. بناء Payload مطابق لـ add_ride.php (0 - 33)
$payloadTemplate = [];
$payloadTemplate[0] = (string)$startLat;
@@ -56,7 +57,7 @@ try {
$payloadTemplate[3] = (string)$endLat;
$payloadTemplate[4] = (string)$endLng;
$payloadTemplate[5] = (string)$distanceText;
- $payloadTemplate[6] = ""; // Driver ID placeholder
+ $payloadTemplate[6] = (string)$passengerFp;
$payloadTemplate[7] = (string)$passengerId;
$payloadTemplate[8] = (string)$passengerName;
$payloadTemplate[9] = (string)$passengerToken;
diff --git a/knowledge/siro_admin_simulation.html b/knowledge/siro_admin_simulation.html
new file mode 100644
index 0000000..2b418af
--- /dev/null
+++ b/knowledge/siro_admin_simulation.html
@@ -0,0 +1,751 @@
+
+
+
+
+
+ Siro Admin – محاكاة لوحة التحكم والعمليات
+
+
+
+
+
+
+
+
+
+
S
+
Siro Admin — محاكاة المشرف والعمليات
+
+
+
+
+ اتصال WebSocket نشط
+
+
+
+
+
+
+
+
+
+
+
إدارة لوحة التحكم
+
+
+
+
+
التشغيل المالي والأسعار
+
+
+
+
+
التوثيق والجودة
+
+
+
+
+
الأمن والخصوصية
+
+
+
+
+
+
+
+
+
+
+
+ إجمالي الركاب
+ ▲ 12%
+
+
18,240
+
+
+
+
+ إجمالي الكباتن
+ ▲ 8%
+
+
4,912
+
+
+
+
+ رحلات الشهر الحالي
+ ▲ 24%
+
+
32,490
+
+
+
+
+ محفظة النظام (عمولات)
+ ▼ 2%
+
+
145,200 SP
+
+
+
+
+
+
+
+
+
📡 مراقبة الرحلات المباشرة والعمليات
+
+
+
+
+
+
+
+
+
+
📝 سجل الأحداث والعمليات الفورية
+
+
[النظام]: تم تشغيل محاكاة Siro Admin بنجاح.
+
[العمليات]: تم الاتصال بخادم الـ Websocket (rides.intaleq.xyz).
+
+
+
+
+
+
💰 تعديل عمولة Kazan ومعدلات التعرفة
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📑 وثائق الكباتن بانتظار التدقيق والتحقق
+
+
+
+
+ الكابتن: محمد أحمد الحموي
+ رقم السيارة: دمشق - 482920 • نوع المستند: رخصة القيادة
+ [تحليل الذكاء الاصطناعي Azure OCR]: الاسم والتواريخ متطابقة بنسبة 98%
+
+
+
+
+
+
+
+
+
+ الكابتن: رامي طارق المصري
+ رقم السيارة: ريف دمشق - 729221 • نوع المستند: تأمين المركبة
+ [تحليل الذكاء الاصطناعي Azure OCR]: المستند ينتهي خلال 3 أيام
+
+
+
+
+
+
+
+
+
+
+
+
🛡️ رادار كشف الاحتيال وتكرار بصمات الأجهزة
+
+ تنبيه أمني هام: تم اكتشاف بصمة جهاز مكررة مرتبطة بـ 3 كباتن مختلفين!
+ Device FP: SHA256:d89ef239fbc87a1d...
+
+
+
+
+ كابتن 1: علي سليم (نشط)
+ رقم الهاتف: 963992019283
+