Update: 2026-06-21 02:07:00
This commit is contained in:
@@ -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 الأساسية
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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'].
|
||||
|
||||
72
backend/ride/heatmap/heatmap_live.php
Normal file
72
backend/ride/heatmap/heatmap_live.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
// If Main Redis is not available, return empty array
|
||||
if (!isset($redis) || $redis === null) {
|
||||
echo json_encode([]);
|
||||
exit();
|
||||
}
|
||||
|
||||
$grid_size = 0.0135;
|
||||
$keys = [];
|
||||
|
||||
try {
|
||||
// Prefix 'siro:' is automatically applied by $redis
|
||||
$keys = $redis->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);
|
||||
?>
|
||||
43
backend/ride/heatmap/log_demand.php
Normal file
43
backend/ride/heatmap/log_demand.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
$lat = filterRequest("lat");
|
||||
$lng = filterRequest("lng");
|
||||
|
||||
if (!$lat || !$lng) {
|
||||
jsonError("Missing coordinates");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($redis) || $redis === null) {
|
||||
// If Redis is not available, we fail gracefully.
|
||||
jsonSuccess(null, "Demand logged (fallback)");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Create a 1.5 km grid cell
|
||||
// 1 degree latitude is approximately 111 km.
|
||||
// 1.5 km / 111 km ≈ 0.0135 degrees.
|
||||
$grid_size = 0.0135;
|
||||
|
||||
$grid_lat = round((float)$lat / $grid_size) * $grid_size;
|
||||
$grid_lng = round((float)$lng / $grid_size) * $grid_size;
|
||||
|
||||
$grid_id = $grid_lat . "_" . $grid_lng;
|
||||
$redisKey = "demand:grid:" . $grid_id;
|
||||
|
||||
try {
|
||||
// Increment the demand count for this grid
|
||||
$currentCount = $redis->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");
|
||||
}
|
||||
?>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
|
||||
//getSpeed.php
|
||||
require_once __DIR__ . '/../../connect.php'; // Provides $con, $redisLocation, $encryptionHelper, jsonSuccess/jsonError
|
||||
|
||||
// getSpeed.php (Redis-Optimized Version)
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
@@ -13,89 +14,107 @@ try {
|
||||
exit;
|
||||
}
|
||||
|
||||
$freshSeconds = 180; // 3 دقائق
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
|
||||
// الخطوة 1: البحث في Redis باستخدام تقنية GeoRadius (أسرع 100 مرة من MySQL)
|
||||
// =================================================================
|
||||
$boundingBoxWKT = sprintf(
|
||||
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
|
||||
$southwestLon, $southwestLat,
|
||||
$northeastLon, $southwestLat,
|
||||
$northeastLon, $northeastLat,
|
||||
$southwestLon, $northeastLat,
|
||||
$southwestLon, $southwestLat
|
||||
);
|
||||
$centerLat = ($southwestLat + $northeastLat) / 2.0;
|
||||
$centerLon = ($southwestLon + $northeastLon) / 2.0;
|
||||
|
||||
// نجلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
|
||||
$sql_locations = "
|
||||
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
|
||||
FROM car_locations
|
||||
WHERE
|
||||
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
|
||||
AND status = 'off'
|
||||
AND updated_at >= 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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 على خريطة الراكب
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user