11
This commit is contained in:
@@ -1,185 +1,181 @@
|
|||||||
<?php
|
<?php
|
||||||
// acceptRide.php
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// driver/ride/accept_ride.php
|
||||||
|
// PURPOSE : قبول رحلة — ride DB هو المرجع، primary DB يتزامن بعده
|
||||||
|
// RACE : Optimistic lock عبر WHERE status IN ('waiting','wait')
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
// 1. Include Database Connection
|
include "../../connect.php";
|
||||||
// This file connects to both the Main DB ($con) and the Ride DB ($con_ride).
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
// 2. Input Validation & Filtering
|
// ── 1. Input & Validation ──────────────────────────────────────
|
||||||
$rideId = filterRequest("id");
|
$rideId = filterRequest("id");
|
||||||
$driverId = filterRequest("driver_id");
|
$driverId = filterRequest("driver_id");
|
||||||
$status = filterRequest("status"); // Expected: 'Apply' or 'accepted'
|
$status = filterRequest("status"); // القيمة التي يرسلها التطبيق: 'accepted'
|
||||||
$passengerToken = filterRequest("passengerToken");
|
$passengerToken = filterRequest("passengerToken");
|
||||||
|
|
||||||
// Log incoming data for debugging
|
if (empty($rideId) || empty($driverId)) {
|
||||||
error_log("ℹ️ [ACCEPT_RIDE_TRY] RideID: '$rideId' | DriverID: '$driverId' | Status: '$status' | PToken: '$passengerToken'");
|
printFailure("Missing required parameters");
|
||||||
|
|
||||||
// Check if critical data is missing
|
|
||||||
if (!$rideId || !$driverId) {
|
|
||||||
error_log("⛔ [ACCEPT_RIDE_FAIL] Missing parameters.");
|
|
||||||
jsonError("Missing required parameters.");
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// status whitelist — لا نقبل قيمة عشوائية من التطبيق
|
||||||
|
$allowedStatuses = ['accepted', 'Apply'];
|
||||||
|
if (!in_array($status, $allowedStatuses, true)) {
|
||||||
|
$status = 'accepted'; // fallback آمن
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("[accept_ride] DriverID=$driverId attempting RideID=$rideId");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// =================================================================================
|
// ═══════════════════════════════════════════════════════════
|
||||||
// 3. 🔒 ATOMIC UPDATE (The Race Condition Solver)
|
// STEP A — القفل على ride DB (المرجع الأساسي)
|
||||||
// We attempt to update the ride status ONLY if it is currently 'waiting'.
|
// Optimistic lock: نغير فقط إذا status لا يزال 'waiting' أو 'wait'
|
||||||
// This prevents two drivers from accepting the same ride simultaneously.
|
// السائق الأول الذي يصل يربح — الباقي يجدون rowCount=0
|
||||||
// We execute this on the Remote/Ride Database first ($con_ride).
|
// ═══════════════════════════════════════════════════════════
|
||||||
// =================================================================================
|
$stmtLock = $con_ride->prepare("
|
||||||
$stmtRemote = $con_ride->prepare("
|
UPDATE `ride`
|
||||||
UPDATE `ride`
|
SET `status` = ?,
|
||||||
SET `status` = ?, `driver_id` = ?, `rideTimeStart` = NOW()
|
`driver_id` = ?,
|
||||||
WHERE `id` = ? AND `status` IN ('waiting', 'wait')
|
`rideTimeStart` = NOW()
|
||||||
|
WHERE `id` = ?
|
||||||
|
AND `status` IN ('waiting', 'wait')
|
||||||
");
|
");
|
||||||
$stmtRemote->execute([$status, $driverId, $rideId]);
|
$stmtLock->execute([$status, $driverId, $rideId]);
|
||||||
|
|
||||||
// Check if the update actually changed a row.
|
|
||||||
// If rowCount > 0, IT MEANS SUCCESS! This driver won the ride.
|
|
||||||
if ($stmtRemote->rowCount() > 0) {
|
|
||||||
|
|
||||||
// 4. Synchronization: Update Local Database
|
|
||||||
// Now that we secured the ride, we update the main server's DB ($con) to match.
|
|
||||||
if (isset($con)) {
|
|
||||||
$stmtLocal = $con->prepare("UPDATE `ride` SET `driver_id` = ?, `status` = ?, `rideTimeStart` = NOW() WHERE id = ?");
|
|
||||||
$stmtLocal->execute([$driverId, $status, $rideId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Update/Insert Driver Orders Table
|
if ($stmtLock->rowCount() === 0) {
|
||||||
// This tracks the driver's history or active orders.
|
// الرحلة غير متاحة — سائق آخر سبق أو الرحلة ألغيت
|
||||||
$checkSql = "SELECT `order_id` FROM `driver_orders` WHERE `order_id` = ?";
|
error_log("[accept_ride] RideID=$rideId not available for DriverID=$driverId (rowCount=0)");
|
||||||
$checkStmt = $con->prepare($checkSql);
|
printFailure("Ride not available");
|
||||||
$checkStmt->execute([$rideId]);
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($checkStmt->rowCount() > 0) {
|
error_log("[accept_ride] ride DB locked. RideID=$rideId → DriverID=$driverId");
|
||||||
// If entry exists, update it
|
|
||||||
$updateSql = "UPDATE `driver_orders` SET `driver_id` = ?, `status` = ?, `created_at` = NOW() WHERE `order_id` = ?";
|
|
||||||
$con->prepare($updateSql)->execute([$driverId, $status, $rideId]);
|
|
||||||
} else {
|
|
||||||
// If not, insert new record
|
|
||||||
$insertSql = "INSERT INTO `driver_orders` (`driver_id`, `order_id`, `created_at`, `status`) VALUES (?, ?, NOW(), ?)";
|
|
||||||
$con->prepare($insertSql)->execute([$driverId, $rideId, $status]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================================================================
|
// ═══════════════════════════════════════════════════════════
|
||||||
// 6. 👤 GET DRIVER INFO (For the Passenger)
|
// STEP B — تزامن primary DB (بعد نجاح القفل)
|
||||||
// We need to fetch driver details (Car, Name, Rating) to show to the passenger.
|
// ═══════════════════════════════════════════════════════════
|
||||||
// =================================================================
|
try {
|
||||||
|
$con->prepare("
|
||||||
$driverInfo = [];
|
UPDATE `ride`
|
||||||
|
SET `driver_id` = ?,
|
||||||
$sqlDetails = "SELECT
|
`status` = ?,
|
||||||
d.id as driver_id,
|
`rideTimeStart` = NOW()
|
||||||
d.first_name,
|
WHERE `id` = ?
|
||||||
d.last_name,
|
")->execute([$driverId, $status, $rideId]);
|
||||||
d.gender,
|
error_log("[accept_ride] primary DB synced. RideID=$rideId");
|
||||||
d.phone,
|
} catch (PDOException $eSync) {
|
||||||
c.make,
|
// لا نوقف — ride DB هو المرجع
|
||||||
c.model,
|
error_log("[accept_ride] primary DB sync WARNING: " . $eSync->getMessage());
|
||||||
c.car_plate,
|
}
|
||||||
c.year,
|
|
||||||
c.color,
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// STEP C — driver_orders (INSERT أو UPDATE بسطر واحد آمن)
|
||||||
|
// ON DUPLICATE KEY يمنع race condition ثانية على هذا الجدول
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
try {
|
||||||
|
$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()
|
||||||
|
")->execute([$driverId, $rideId, $status]);
|
||||||
|
} catch (PDOException $eOrders) {
|
||||||
|
error_log("[accept_ride] driver_orders WARNING: " . $eOrders->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// STEP D — جلب بيانات السائق للراكب
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
$driverInfo = [];
|
||||||
|
|
||||||
|
$stmtDriver = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
d.id AS driver_id,
|
||||||
|
d.first_name,
|
||||||
|
d.last_name,
|
||||||
|
d.gender,
|
||||||
|
d.phone,
|
||||||
|
c.make,
|
||||||
|
c.model,
|
||||||
|
c.car_plate,
|
||||||
|
c.year,
|
||||||
|
c.color,
|
||||||
c.color_hex,
|
c.color_hex,
|
||||||
(SELECT ROUND(AVG(rating), 2) FROM ratingDriver WHERE driver_id = d.id) AS ratingDriver,
|
(SELECT ROUND(AVG(rating), 2) FROM ratingDriver WHERE driver_id = d.id) AS ratingDriver,
|
||||||
dt.token
|
dt.token
|
||||||
FROM driver d
|
FROM driver d
|
||||||
LEFT JOIN CarRegistration c ON c.driverID = d.id
|
LEFT JOIN CarRegistration c ON c.driverID = d.id
|
||||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||||
WHERE d.id = ?";
|
WHERE d.id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmtDriver->execute([$driverId]);
|
||||||
|
$driverRaw = $stmtDriver->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
$stmtDetails = $con->prepare($sqlDetails);
|
if ($driverRaw) {
|
||||||
$stmtDetails->execute([$driverId]);
|
$encryptedFields = ['first_name', 'last_name', 'gender', 'phone', 'car_plate', 'token'];
|
||||||
$driverRawData = $stmtDetails->fetch(PDO::FETCH_ASSOC);
|
foreach ($driverRaw as $key => $value) {
|
||||||
|
$driverInfo[$key] = (in_array($key, $encryptedFields) && !empty($value))
|
||||||
if ($driverRawData) {
|
? $encryptionHelper->decryptData($value)
|
||||||
// List of encrypted fields that need decryption
|
: $value;
|
||||||
$fieldsToDecrypt = ['first_name', 'last_name', 'gender', 'phone', 'car_plate', 'token'];
|
|
||||||
|
|
||||||
foreach ($driverRawData as $key => $value) {
|
|
||||||
if (in_array($key, $fieldsToDecrypt) && !empty($value)) {
|
|
||||||
// Decrypt sensitive data
|
|
||||||
$driverInfo[$key] = $encryptionHelper->decryptData($value);
|
|
||||||
} else {
|
|
||||||
$driverInfo[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format Full Name
|
|
||||||
$driverInfo['driverName'] = trim(($driverInfo['first_name'] ?? '') . ' ' . ($driverInfo['last_name'] ?? ''));
|
|
||||||
|
|
||||||
// Default rating if null
|
|
||||||
if (empty($driverInfo['ratingDriver'])) {
|
|
||||||
$driverInfo['ratingDriver'] = "5.0";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
$driverInfo['driverName'] = trim(($driverInfo['first_name'] ?? '') . ' ' . ($driverInfo['last_name'] ?? ''));
|
||||||
// =================================================================
|
$driverInfo['ratingDriver'] = $driverInfo['ratingDriver'] ?: "5.0";
|
||||||
// 7. 🔔 NOTIFY PASSENGER (Socket + FCM)
|
|
||||||
// Inform the passenger that a driver has been found.
|
|
||||||
// =================================================================
|
|
||||||
|
|
||||||
// Fetch Passenger ID based on Ride ID
|
|
||||||
$stmtPas = $con->prepare("SELECT passenger_id FROM ride WHERE id = ?");
|
|
||||||
$stmtPas->execute([$rideId]);
|
|
||||||
$passenger_id = $stmtPas->fetchColumn();
|
|
||||||
|
|
||||||
if ($passenger_id) {
|
|
||||||
// A. Send Socket Notification (Real-time update on map)
|
|
||||||
if (function_exists('notifyPassengerOnRideServer')) {
|
|
||||||
notifyPassengerOnRideServer($passenger_id, [
|
|
||||||
'status' => 'accepted',
|
|
||||||
'ride_id' => $rideId,
|
|
||||||
'driver_id' => $driverId,
|
|
||||||
'driver_info' => $driverInfo
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// B. Send FCM Notification (Push Notification)
|
|
||||||
if (!empty($passengerToken)) {
|
|
||||||
// Using the standardized FCM function
|
|
||||||
sendFCM_Internal(
|
|
||||||
$passengerToken,
|
|
||||||
"Ride Accepted 🚖", // Title
|
|
||||||
"Captain " . ($driverInfo['driverName'] ?? 'Driver') . " is coming to you.", // Body
|
|
||||||
['ride_id' => (string)$rideId, 'driver_info' => $driverInfo], // Data Payload
|
|
||||||
"Accepted Ride", // Category
|
|
||||||
false // Not a topic
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================================================================
|
|
||||||
// 8. 🧹 MARKETPLACE CLEANUP (Notify Location Server)
|
|
||||||
// Crucial Step: We tell the Location Server that this ride is taken.
|
|
||||||
// The Location Server will:
|
|
||||||
// 1. Remove the ride from Redis (geo:rides:waiting).
|
|
||||||
// 2. Broadcast 'ride_taken' to other drivers to remove it from their screens.
|
|
||||||
// =================================================================
|
|
||||||
sendToLocationServer('ride_taken_event', [
|
|
||||||
'ride_id' => $rideId,
|
|
||||||
'taken_by_driver_id' => $driverId
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 9. Final Response to the Driver App
|
|
||||||
echo json_encode([
|
|
||||||
"status" => "success",
|
|
||||||
"message" => "Ride Accepted",
|
|
||||||
"data" => $driverInfo
|
|
||||||
]);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Failure: This means rowCount was 0.
|
|
||||||
// Reason: The ride status was NOT 'waiting' (another driver took it milliseconds ago).
|
|
||||||
error_log("⛔ [ACCEPT_RIDE_FAIL] Row count 0 for RideID: '$rideId'. Status wasn't 'waiting'/'wait' or ID is wrong.");
|
|
||||||
jsonError("Ride not available (Already taken)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception $e) {
|
// ═══════════════════════════════════════════════════════════
|
||||||
// Handle unexpected errors
|
// STEP E — جلب passenger_id وإرسال الإشعارات
|
||||||
error_log("⛔ [ACCEPT_RIDE_EXCEPTION] " . $e->getMessage());
|
// ═══════════════════════════════════════════════════════════
|
||||||
jsonError("Error: " . $e->getMessage());
|
$passengerId = $con->prepare("SELECT passenger_id FROM ride WHERE id = ? LIMIT 1");
|
||||||
}
|
$passengerId->execute([$rideId]);
|
||||||
?>
|
$passengerIdValue = $passengerId->fetchColumn();
|
||||||
|
|
||||||
|
if ($passengerIdValue) {
|
||||||
|
// Socket — real-time update على خريطة الراكب
|
||||||
|
if (function_exists('notifyPassengerOnRideServer')) {
|
||||||
|
notifyPassengerOnRideServer($passengerIdValue, [
|
||||||
|
'status' => 'accepted',
|
||||||
|
'ride_id' => $rideId,
|
||||||
|
'driver_id' => $driverId,
|
||||||
|
'driver_info' => $driverInfo,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FCM — push notification
|
||||||
|
if (!empty($passengerToken)) {
|
||||||
|
sendFCM_Internal(
|
||||||
|
$passengerToken,
|
||||||
|
"تم قبول رحلتك",
|
||||||
|
"الكابتن " . ($driverInfo['driverName'] ?? '') . " في طريقه إليك",
|
||||||
|
['ride_id' => (string) $rideId, 'driver_info' => $driverInfo],
|
||||||
|
"ride_accepted",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// STEP F — تنظيف السوق (أبلغ location server إن الرحلة محجوزة)
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
sendToLocationServer('ride_taken_event', [
|
||||||
|
'ride_id' => $rideId,
|
||||||
|
'taken_by_driver_id' => $driverId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
error_log("[accept_ride] SUCCESS. RideID=$rideId accepted by DriverID=$driverId");
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// STEP G — رد النجاح للسائق (نفس بنية الرد القديمة)
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
echo json_encode([
|
||||||
|
"status" => "success",
|
||||||
|
"message" => "Ride Accepted",
|
||||||
|
"data" => $driverInfo,
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("[accept_ride] CRITICAL: " . $e->getMessage());
|
||||||
|
printFailure("Server error");
|
||||||
|
}
|
||||||
@@ -1,270 +1,187 @@
|
|||||||
<?php
|
<?php
|
||||||
// add_ride.php
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// passenger/ride/add_ride.php
|
||||||
|
// PURPOSE : إنشاء رحلة جديدة — ride DB أولاً، primary DB ثانياً
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
// 1. تضمين ملف الاتصال بقاعدة البيانات
|
include "../../connect.php";
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
$con_ride = Database::get('ride');
|
|
||||||
// include "functions.php"; // (تأكد من وجود دوال المساعدة مثل notifyPassengerSocket هنا أو ضمنها)
|
|
||||||
|
|
||||||
// إعدادات تتبع الأخطاء (للسيرفر)
|
error_log("[add_ride] Request started. passenger_id=" . ($_POST['passenger_id'] ?? '?'));
|
||||||
ini_set('log_errors', 1);
|
|
||||||
ini_set('error_log', '/home/intaleq-api/add_ride_error.log');
|
|
||||||
|
|
||||||
// =================================================================================
|
// ── 1. Input ───────────────────────────────────────────────────
|
||||||
// 🛠️ دالة مساعدة: إرسال الرحلة لسوق السائقين (Marketplace Broadcast)
|
$start_location = filterRequest("start_location");
|
||||||
// الوظيفة: ترسل بيانات الرحلة لسيرفر اللوكيشن ليقوم بحفظها في Redis وبثها للسائقين
|
$end_location = filterRequest("end_location");
|
||||||
// =================================================================================
|
$price = filterRequest("price");
|
||||||
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData) {
|
$passenger_id = filterRequest("passenger_id");
|
||||||
// رابط سيرفر اللوكيشن (تأكد من صحة الرابط والبورت)
|
$driver_id = filterRequest("driver_id") ?: 0;
|
||||||
$url = getenv('LOCATION_SOCKET_URL');
|
$status = filterRequest("status");
|
||||||
|
$price_for_driver = filterRequest("price_for_driver");
|
||||||
// قراءة مفتاح الأمان الداخلي للمصادقة
|
|
||||||
$INTERNAL_KEY = trim(file_get_contents(getenv('INTERNAL_SOCKET_KEY_PATH')));
|
|
||||||
|
|
||||||
// تجهيز البيانات بشكل (Key-Value Map) لسهولة التعامل في التطبيق والسوكيت
|
|
||||||
// ملاحظة: نستخدم القيم من $payloadData التي هي عبارة عن List حالياً
|
|
||||||
$marketPayload = [
|
|
||||||
'id' => (string)$rideId,
|
|
||||||
'start_lat' => $lat,
|
|
||||||
'start_lng' => $lng,
|
|
||||||
'price' => $payloadData[2], // السعر
|
|
||||||
'carType' => $payloadData[31], // نوع السيارة
|
|
||||||
'startName' => $payloadData[29], // اسم موقع البدء
|
|
||||||
'endName' => $payloadData[30], // اسم موقع الوصول
|
|
||||||
'distance' => $payloadData[11], // المسافة
|
|
||||||
'duration' => $payloadData[15], // الوقت
|
|
||||||
'passengerRate' => $payloadData[33], // تقييم الراكب
|
|
||||||
// يمكنك إضافة المزيد هنا حسب الحاجة
|
|
||||||
];
|
|
||||||
|
|
||||||
$postData = [
|
|
||||||
'action' => 'market_new_ride', // اسم الحدث في السوكيت
|
|
||||||
'payload' => $marketPayload
|
|
||||||
];
|
|
||||||
|
|
||||||
// إرسال الطلب (cURL)
|
|
||||||
$ch = curl_init();
|
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
|
||||||
curl_setopt($ch, CURLOPT_POST, 1);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
// وقت انتظار قصير جداً (200ms) لأننا لا نريد تأخير استجابة الراكب
|
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $INTERNAL_KEY"]);
|
|
||||||
curl_exec($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================================================================================
|
|
||||||
// 2. استقبال وتصفية البيانات من التطبيق
|
|
||||||
// =================================================================================
|
|
||||||
$start_location = filterRequest("start_location");
|
|
||||||
$end_location = filterRequest("end_location");
|
|
||||||
$date_raw = filterRequest("date");
|
|
||||||
$time_raw = filterRequest("time");
|
|
||||||
$endtime_raw = filterRequest("endtime");
|
|
||||||
$price = filterRequest("price");
|
|
||||||
$passenger_id = filterRequest("passenger_id");
|
|
||||||
$driver_id = filterRequest("driver_id") ?: 0;
|
|
||||||
$status = filterRequest("status");
|
|
||||||
$price_for_driver = filterRequest("price_for_driver");
|
|
||||||
$price_for_passenger = filterRequest("price_for_passenger");
|
$price_for_passenger = filterRequest("price_for_passenger");
|
||||||
$distance = filterRequest("distance");
|
$distance = filterRequest("distance");
|
||||||
$carType = filterRequest("carType");
|
$carType = filterRequest("carType");
|
||||||
|
$passenger_name = filterRequest("passenger_name");
|
||||||
// بيانات الراكب الإضافية
|
$passenger_phone = filterRequest("passenger_phone");
|
||||||
$passenger_name = filterRequest("passenger_name");
|
$passenger_token = filterRequest("passenger_token");
|
||||||
$passenger_phone = filterRequest("passenger_phone");
|
$passenger_email = filterRequest("passenger_email");
|
||||||
$passenger_token = filterRequest("passenger_token");
|
$passenger_wallet = filterRequest("passenger_wallet");
|
||||||
$passenger_email = filterRequest("passenger_email");
|
$passenger_rating = filterRequest("passenger_rating");
|
||||||
$passenger_wallet = filterRequest("passenger_wallet");
|
$start_name_loc = filterRequest("start_name");
|
||||||
$passenger_rating = filterRequest("passenger_rating");
|
$end_name_loc = filterRequest("end_name");
|
||||||
|
$duration_text = filterRequest("duration_text");
|
||||||
// تفاصيل الرحلة والنصوص
|
$distance_text = filterRequest("distance_text");
|
||||||
$start_name_loc = filterRequest("start_name");
|
$is_wallet = filterRequest("is_wallet");
|
||||||
$end_name_loc = filterRequest("end_name");
|
$has_steps = filterRequest("has_steps");
|
||||||
$duration_text = filterRequest("duration_text");
|
$step0 = filterRequest("step0");
|
||||||
$distance_text = filterRequest("distance_text");
|
$step1 = filterRequest("step1");
|
||||||
$is_wallet = filterRequest("is_wallet");
|
$step2 = filterRequest("step2");
|
||||||
$has_steps = filterRequest("has_steps");
|
$step3 = filterRequest("step3");
|
||||||
|
|
||||||
$step0 = filterRequest("step0"); $step1 = filterRequest("step1");
|
|
||||||
$step2 = filterRequest("step2"); $step3 = filterRequest("step3");
|
|
||||||
$step4 = filterRequest("step4");
|
$step4 = filterRequest("step4");
|
||||||
|
|
||||||
// معالجة الإحداثيات (فصل النص إلى Lat/Lng)
|
// Validation
|
||||||
$startLat = ""; $startLng = "";
|
if (empty($passenger_id) || empty($start_location) || empty($end_location) || empty($price)) {
|
||||||
|
error_log("[add_ride] Validation failed — missing required fields.");
|
||||||
|
printFailure("Missing required fields");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. تنسيق التواريخ ─────────────────────────────────────────
|
||||||
|
$date_formatted = date("Y-m-d");
|
||||||
|
$time_formatted = date("H:i:s");
|
||||||
|
$endtime_formatted = filterRequest("endtime")
|
||||||
|
? date("H:i:s", strtotime(filterRequest("endtime")))
|
||||||
|
: "00:00:00";
|
||||||
|
|
||||||
|
// ── 3. إحداثيات البداية والنهاية ──────────────────────────────
|
||||||
|
$startLat = $startLng = $endLat = $endLng = "";
|
||||||
if (!empty($start_location)) {
|
if (!empty($start_location)) {
|
||||||
$parts = explode(',', $start_location);
|
[$startLat, $startLng] = array_map('trim', explode(',', $start_location, 2));
|
||||||
$startLat = trim($parts[0] ?? ""); $startLng = trim($parts[1] ?? "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$endLat = ""; $endLng = "";
|
|
||||||
if (!empty($end_location)) {
|
if (!empty($end_location)) {
|
||||||
$parts = explode(',', $end_location);
|
[$endLat, $endLng] = array_map('trim', explode(',', $end_location, 2));
|
||||||
$endLat = trim($parts[0] ?? ""); $endLng = trim($parts[1] ?? "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// تنسيق التواريخ
|
// ── 4. مصفوفة بيانات الإدخال ──────────────────────────────────
|
||||||
$date_formatted = date("Y-m-d", strtotime($date_raw));
|
$insertData = [
|
||||||
$time_formatted = date("H:i:s", strtotime($time_raw));
|
':start_location' => $start_location,
|
||||||
$endtime_formatted = $endtime_raw ? date("H:i:s", strtotime($endtime_raw)) : "00:00:00";
|
':end_location' => $end_location,
|
||||||
|
':date' => $date_formatted,
|
||||||
// مصفوفة البيانات للإدخال
|
':time' => $time_formatted,
|
||||||
$data = [
|
':endtime' => $endtime_formatted,
|
||||||
":start_location" => $start_location,
|
':price' => $price,
|
||||||
":end_location" => $end_location,
|
':passenger_id' => $passenger_id,
|
||||||
":date" => $date_formatted,
|
':driver_id' => $driver_id,
|
||||||
":time" => $time_formatted,
|
':status' => $status,
|
||||||
":endtime" => $endtime_formatted,
|
':carType' => $carType,
|
||||||
":price" => $price,
|
':price_for_driver' => $price_for_driver,
|
||||||
":passenger_id" => $passenger_id,
|
':price_for_passenger' => $price_for_passenger,
|
||||||
":driver_id" => $driver_id,
|
':distance' => $distance,
|
||||||
":status" => $status,
|
|
||||||
":carType" => $carType,
|
|
||||||
":price_for_driver" => $price_for_driver,
|
|
||||||
":price_for_passenger" => $price_for_passenger,
|
|
||||||
":distance" => $distance,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// جملة SQL للإدخال
|
$sqlInsert = "INSERT INTO `ride`
|
||||||
$sql = "INSERT INTO `ride` (
|
(`start_location`,`end_location`,`date`,`time`,`endtime`,
|
||||||
`start_location`, `end_location`, `date`, `time`, `endtime`,
|
`price`,`passenger_id`,`driver_id`,`status`,`carType`,
|
||||||
`price`, `passenger_id`, `driver_id`, `status`, `carType`,
|
`price_for_driver`,`price_for_passenger`,`distance`)
|
||||||
`price_for_driver`, `price_for_passenger`, `distance`
|
VALUES
|
||||||
) VALUES (
|
(:start_location,:end_location,:date,:time,:endtime,
|
||||||
:start_location, :end_location, :date, :time, :endtime,
|
:price,:passenger_id,:driver_id,:status,:carType,
|
||||||
:price, :passenger_id, :driver_id, :status, :carType,
|
:price_for_driver,:price_for_passenger,:distance)";
|
||||||
:price_for_driver, :price_for_passenger, :distance
|
|
||||||
)";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 3. الإدخال في قاعدة بيانات الرحلات أولاً (Ride DB - المرجع الأساسي)
|
// ═══════════════════════════════════════════════════════════
|
||||||
$stmtRide = $con_ride->prepare($sql);
|
// STEP A — ride DB أولاً (هو المرجع الأساسي)
|
||||||
$stmtRide->execute($data);
|
// ═══════════════════════════════════════════════════════════
|
||||||
$insertedId = $con_ride->lastInsertId(); // ID الرحلة الجديد
|
$stmtRide = $con_ride->prepare($sqlInsert);
|
||||||
|
$stmtRide->execute($insertData);
|
||||||
|
$insertedId = $con_ride->lastInsertId();
|
||||||
|
|
||||||
if (!$insertedId) {
|
if (!$insertedId) {
|
||||||
jsonError("Failed to create ride");
|
error_log("[add_ride] ride DB insert returned no ID.");
|
||||||
|
printFailure("Failed to create ride");
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. الإدخال في قاعدة البيانات الرئيسية ثانياً (Main DB) بنفس الـ ID
|
error_log("[add_ride] ride DB insert success. RideID=$insertedId");
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
// STEP B — primary DB ثانياً (نسخة أرشيفية بنفس الـ ID)
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
$sqlInsertWithId = "INSERT INTO `ride`
|
||||||
|
(`id`,`start_location`,`end_location`,`date`,`time`,`endtime`,
|
||||||
|
`price`,`passenger_id`,`driver_id`,`status`,`carType`,
|
||||||
|
`price_for_driver`,`price_for_passenger`,`distance`)
|
||||||
|
VALUES
|
||||||
|
(:id,:start_location,:end_location,:date,:time,:endtime,
|
||||||
|
:price,:passenger_id,:driver_id,:status,:carType,
|
||||||
|
:price_for_driver,:price_for_passenger,:distance)";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$sqlMain = "INSERT INTO `ride` (
|
$primaryData = $insertData;
|
||||||
`id`, `start_location`, `end_location`, `date`, `time`, `endtime`,
|
$primaryData[':id'] = $insertedId;
|
||||||
`price`, `passenger_id`, `driver_id`, `status`, `carType`,
|
$stmtPrimary = $con->prepare($sqlInsertWithId);
|
||||||
`price_for_driver`, `price_for_passenger`, `distance`
|
$stmtPrimary->execute($primaryData);
|
||||||
) VALUES (
|
error_log("[add_ride] primary DB sync success. RideID=$insertedId");
|
||||||
:id, :start_location, :end_location, :date, :time, :endtime,
|
} catch (PDOException $ePrimary) {
|
||||||
:price, :passenger_id, :driver_id, :status, :carType,
|
// لا نوقف العملية — ride DB هو المرجع
|
||||||
:price_for_driver, :price_for_passenger, :distance
|
error_log("[add_ride] primary DB sync WARNING: " . $ePrimary->getMessage());
|
||||||
)";
|
|
||||||
$stmtMain = $con->prepare($sqlMain);
|
|
||||||
$data[':id'] = $insertedId;
|
|
||||||
$stmtMain->execute($data);
|
|
||||||
} catch (Exception $eMain) {
|
|
||||||
error_log("⚠️ Main DB Sync Warning: " . $eMain->getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($insertedId) {
|
// ═══════════════════════════════════════════════════════════
|
||||||
error_log("📝 Ride #$insertedId added successfully.");
|
// STEP C — بناء الـ payload وإرسال الرحلة للسائقين
|
||||||
|
// ═══════════════════════════════════════════════════════════
|
||||||
|
$kazan = (float) $price - (float) $price_for_driver;
|
||||||
|
$payload = [
|
||||||
|
(string) $startLat,
|
||||||
|
(string) $startLng,
|
||||||
|
number_format((float) $price, 2, '.', ''),
|
||||||
|
(string) $endLat,
|
||||||
|
(string) $endLng,
|
||||||
|
(string) $distance_text,
|
||||||
|
"",
|
||||||
|
(string) $passenger_id,
|
||||||
|
(string) $passenger_name,
|
||||||
|
(string) $passenger_token,
|
||||||
|
(string) $passenger_phone,
|
||||||
|
(string) $distance,
|
||||||
|
"1",
|
||||||
|
(string) $is_wallet,
|
||||||
|
(string) $distance,
|
||||||
|
(string) $duration_text,
|
||||||
|
(string) $insertedId,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
(string) $duration_text,
|
||||||
|
$has_steps ?: 'false',
|
||||||
|
(string) $step0,
|
||||||
|
(string) $step1,
|
||||||
|
(string) $step2,
|
||||||
|
(string) $step3,
|
||||||
|
(string) $step4,
|
||||||
|
number_format((float) $price_for_driver, 2, '.', ''),
|
||||||
|
(string) $passenger_wallet,
|
||||||
|
(string) $passenger_email,
|
||||||
|
(string) $start_name_loc,
|
||||||
|
(string) $end_name_loc,
|
||||||
|
(string) $carType,
|
||||||
|
number_format($kazan, 2, '.', ''),
|
||||||
|
(string) $passenger_rating,
|
||||||
|
];
|
||||||
|
|
||||||
// 5. تجهيز الـ Payload (قائمة البيانات للتطبيق)
|
// Direct dispatch للسائقين القريبين
|
||||||
$kazan = (double)$price - (double)$price_for_driver;
|
$driversData = findBestDrivers($con, $startLat, $startLng, $carType);
|
||||||
$payloadTemplate = [];
|
if (!empty($driversData)) {
|
||||||
// تعبئة البيانات بالترتيب الذي يتوقعه التطبيق (Indices 0-33)
|
dispatchRideToDrivers($driversData, $insertedId, $payload, $start_name_loc, $encryptionHelper);
|
||||||
$payloadTemplate[0] = (string)$startLat;
|
error_log("[add_ride] Dispatched RideID=$insertedId to " . count($driversData) . " drivers.");
|
||||||
$payloadTemplate[1] = (string)$startLng;
|
|
||||||
$payloadTemplate[2] = (string)number_format($price, 2, '.', '');
|
|
||||||
$payloadTemplate[3] = (string)$endLat;
|
|
||||||
$payloadTemplate[4] = (string)$endLng;
|
|
||||||
$payloadTemplate[5] = (string)$distance_text;
|
|
||||||
$payloadTemplate[6] = "";
|
|
||||||
$payloadTemplate[7] = (string)$passenger_id;
|
|
||||||
$payloadTemplate[8] = (string)$passenger_name;
|
|
||||||
$payloadTemplate[9] = (string)$passenger_token;
|
|
||||||
$payloadTemplate[10] = (string)$passenger_phone;
|
|
||||||
$payloadTemplate[11] = (string)$distance;
|
|
||||||
$payloadTemplate[12] = "1";
|
|
||||||
$payloadTemplate[13] = (string)$is_wallet;
|
|
||||||
$payloadTemplate[14] = (string)$distance;
|
|
||||||
$payloadTemplate[15] = (string)$duration_text;
|
|
||||||
$payloadTemplate[16] = (string)$insertedId;
|
|
||||||
$payloadTemplate[17] = "";
|
|
||||||
$payloadTemplate[18] = "";
|
|
||||||
$payloadTemplate[19] = (string)$duration_text;
|
|
||||||
$payloadTemplate[20] = $has_steps ?: 'false';
|
|
||||||
$payloadTemplate[21] = (string)$step0;
|
|
||||||
$payloadTemplate[22] = (string)$step1;
|
|
||||||
$payloadTemplate[23] = (string)$step2;
|
|
||||||
$payloadTemplate[24] = (string)$step3;
|
|
||||||
$payloadTemplate[25] = (string)$step4;
|
|
||||||
$payloadTemplate[26] = (string)number_format($price_for_driver, 2, '.', '');
|
|
||||||
$payloadTemplate[27] = (string)$passenger_wallet;
|
|
||||||
$payloadTemplate[28] = (string)$passenger_email;
|
|
||||||
$payloadTemplate[29] = (string)$start_name_loc;
|
|
||||||
$payloadTemplate[30] = (string)$end_name_loc;
|
|
||||||
$payloadTemplate[31] = (string)$carType;
|
|
||||||
$payloadTemplate[32] = (string)number_format($kazan, 2, '.', '');
|
|
||||||
$payloadTemplate[33] = (string)$passenger_rating;
|
|
||||||
|
|
||||||
ksort($payloadTemplate);
|
|
||||||
$payloadTemplate = array_values($payloadTemplate);
|
|
||||||
|
|
||||||
// 6. البحث عن السائقين للتوزيع المباشر (Direct Dispatch)
|
|
||||||
$driversData = findBestDrivers($con, $startLat, $startLng, $carType);
|
|
||||||
|
|
||||||
// متغير لنعرف هل وجدنا سائقين للتوجيه المباشر أم لا
|
|
||||||
$foundDirectDrivers = false;
|
|
||||||
|
|
||||||
if (!empty($driversData)) {
|
|
||||||
// أ. إرسال إشعار مباشر للسائقين المختارين
|
|
||||||
dispatchRideToDrivers($driversData, $insertedId, $payloadTemplate, $start_name_loc, $encryptionHelper);
|
|
||||||
error_log("📨 Dispatched Ride #$insertedId to " . count($driversData) . " drivers.");
|
|
||||||
$foundDirectDrivers = true;
|
|
||||||
} else {
|
|
||||||
error_log("⚠️ No specific drivers found for Direct Dispatch for Ride #$insertedId. Moved to Market only.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ب. 🔥 نشر الرحلة في السوق المفتوح (Marketplace) دائماً 🔥
|
|
||||||
// هذا هو طوق النجاة: حتى لو لم نجد سائقين أعلاه، نضعها في السوق
|
|
||||||
broadcastRideToMarket($insertedId, $startLat, $startLng, $payloadTemplate);
|
|
||||||
|
|
||||||
// ج. ✅ إرجاع نجاح للتطبيق دائماً (ليبقى الراكب في شاشة البحث)
|
|
||||||
// يمكنك إرسال معلومة إضافية للتطبيق أن البحث "عام" وليس "مباشر" إذا أردت
|
|
||||||
jsonSuccess($insertedId);
|
|
||||||
|
|
||||||
// ملاحظة: قمنا بإزالة كود الإلغاء (UPDATE ride SET status = 'cancelled...')
|
|
||||||
// لأننا نريد منح الفرصة للسائقين البعيدين قليلاً أو الذين فتحوا التطبيق للتو
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
else {
|
|
||||||
// 🛑 حالة عدم العثور على سائقين
|
|
||||||
error_log("⚠️ No drivers found for Ride #$insertedId.");
|
|
||||||
|
|
||||||
// أ. إلغاء الرحلة فوراً في قواعد البيانات
|
|
||||||
// ملاحظة: غيرنا الحالة إلى رسالة واضحة
|
|
||||||
$con->prepare("UPDATE ride SET status = 'cancelled_no_driver_found' WHERE id = ?")->execute([$insertedId]);
|
|
||||||
$con_ride->prepare("UPDATE ride SET status = 'cancelled_no_driver_found' WHERE id = ?")->execute([$insertedId]);
|
|
||||||
|
|
||||||
// ب. إشعار الراكب عبر السوكيت (لإظهار Popup)
|
|
||||||
if (function_exists('notifyPassengerSocket')) {
|
|
||||||
notifyPassengerSocket($passenger_id, 'no_drivers_found', [
|
|
||||||
'ride_id' => $insertedId,
|
|
||||||
'message' => 'No drivers available nearby'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ج. إرجاع فشل للتطبيق
|
|
||||||
jsonError("no_drivers_found");
|
|
||||||
*/
|
|
||||||
} else {
|
} else {
|
||||||
jsonError("Failed to add ride");
|
error_log("[add_ride] No direct drivers found for RideID=$insertedId — market only.");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception $e) {
|
// Broadcast للـ marketplace دائماً
|
||||||
error_log("AddRide Critical Error: " . $e->getMessage());
|
broadcastRideToMarket($insertedId, $startLat, $startLng, $payload);
|
||||||
jsonError("Database Error");
|
|
||||||
}
|
// رد النجاح للتطبيق
|
||||||
?>
|
printSuccess($insertedId);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("[add_ride] CRITICAL ride DB error: " . $e->getMessage());
|
||||||
|
printFailure("Database error");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user