first commit
This commit is contained in:
198
backend/ride/rides/acceptRide.php
Executable file
198
backend/ride/rides/acceptRide.php
Executable file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// driver/ride/accept_ride.php
|
||||
// PURPOSE : قبول رحلة — ride DB هو المرجع، primary DB يتزامن بعده
|
||||
// RACE : Optimistic lock عبر WHERE status IN ('waiting','wait')
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
include "../../connect.php";
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
} catch (Exception $e) {
|
||||
error_log("[accept_ride] Failed to connect to Ride Database: " . $e->getMessage());
|
||||
printFailure("Database connection failed");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 1. Input & Validation ──────────────────────────────────────
|
||||
$rideId = filterRequest("id");
|
||||
$driverId = filterRequest("driver_id");
|
||||
$status = filterRequest("status"); // القيمة التي يرسلها التطبيق: 'accepted'
|
||||
$passengerToken = filterRequest("passengerToken");
|
||||
|
||||
if (empty($rideId) || empty($driverId)) {
|
||||
printFailure("Missing required parameters");
|
||||
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 {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP A — القفل على ride DB (المرجع الأساسي)
|
||||
// Optimistic lock: نغير فقط إذا status لا يزال 'waiting' أو 'wait'
|
||||
// السائق الأول الذي يصل يربح — الباقي يجدون rowCount=0
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
$stmtLock = $con_ride->prepare("
|
||||
UPDATE `ride`
|
||||
SET `status` = ?,
|
||||
`driver_id` = ?,
|
||||
`rideTimeStart` = NOW()
|
||||
WHERE `id` = ?
|
||||
AND `status` IN ('waiting', 'wait')
|
||||
");
|
||||
$stmtLock->execute([$status, $driverId, $rideId]);
|
||||
|
||||
if ($stmtLock->rowCount() === 0) {
|
||||
// الرحلة غير متاحة — سائق آخر سبق أو الرحلة ألغيت
|
||||
error_log("[accept_ride] RideID=$rideId not available for DriverID=$driverId (rowCount=0)");
|
||||
printFailure("Ride not available");
|
||||
exit;
|
||||
}
|
||||
|
||||
error_log("[accept_ride] ride DB locked. RideID=$rideId → DriverID=$driverId");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP B — تزامن primary DB (بعد نجاح القفل)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
try {
|
||||
$con->prepare("
|
||||
UPDATE `ride`
|
||||
SET `driver_id` = ?,
|
||||
`status` = ?,
|
||||
`rideTimeStart` = NOW()
|
||||
WHERE `id` = ?
|
||||
")->execute([$driverId, $status, $rideId]);
|
||||
error_log("[accept_ride] primary DB synced. RideID=$rideId");
|
||||
} catch (PDOException $eSync) {
|
||||
// لا نوقف — ride DB هو المرجع
|
||||
error_log("[accept_ride] primary DB sync WARNING: " . $eSync->getMessage());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 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 C.1 — تحديث جدول waitingRides حتى لا تظهر للكباتن الآخرين
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
try {
|
||||
$con->prepare("UPDATE `waitingRides` SET `status` = 'Apply' WHERE `id` = ?")->execute([$rideId]);
|
||||
} catch (PDOException $eWaiting) {
|
||||
error_log("[accept_ride] waitingRides WARNING: " . $eWaiting->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,
|
||||
(SELECT ROUND(AVG(rating), 2) FROM ratingDriver WHERE driver_id = d.id) AS ratingDriver,
|
||||
dt.token
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration c ON c.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
WHERE d.id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtDriver->execute([$driverId]);
|
||||
$driverRaw = $stmtDriver->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($driverRaw) {
|
||||
$encryptedFields = ['first_name', 'last_name', 'gender', 'phone', 'car_plate', 'token'];
|
||||
foreach ($driverRaw as $key => $value) {
|
||||
$driverInfo[$key] = (in_array($key, $encryptedFields) && !empty($value))
|
||||
? $encryptionHelper->decryptData($value)
|
||||
: $value;
|
||||
}
|
||||
$driverInfo['driverName'] = trim(($driverInfo['first_name'] ?? '') . ' ' . ($driverInfo['last_name'] ?? ''));
|
||||
$driverInfo['ratingDriver'] = $driverInfo['ratingDriver'] ?: "5.0";
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP E — جلب passenger_id وإرسال الإشعارات
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
$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,
|
||||
"", // تفريغ العنوان للإرسال الصامت
|
||||
"", // تفريغ المحتوى للإرسال الصامت
|
||||
['ride_id' => (string) $rideId, 'driver_info' => $driverInfo, 'status' => 'accepted'],
|
||||
"Accepted Ride",
|
||||
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");
|
||||
}
|
||||
128
backend/ride/rides/add.php
Normal file
128
backend/ride/rides/add.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// 🚀 1. تسجيل بداية الطلب
|
||||
error_log("🚀 [add_ride.php] Request Started.");
|
||||
|
||||
// استلام البيانات
|
||||
$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");
|
||||
$status = filterRequest("status");
|
||||
$price_for_driver = filterRequest("price_for_driver");
|
||||
$price_for_passenger = filterRequest("price_for_passenger");
|
||||
$distance = filterRequest("distance");
|
||||
$carType = filterRequest("carType");
|
||||
|
||||
error_log("ℹ️ [add_ride.php] Data Received. Processing dates...");
|
||||
|
||||
// 🛠️ 2. معالجة التواريخ لتناسب MySQL (الحل الجذري للمشكلة)
|
||||
// تحويل "2025-12-18 09:48:26.805" إلى "2025-12-18" فقط
|
||||
$date_formatted = date("Y-m-d", strtotime($date_raw));
|
||||
|
||||
// تحويل "2025-12-18 09:48:26.810" إلى "09:48:26" فقط
|
||||
$time_formatted = date("H:i:s", strtotime($time_raw));
|
||||
|
||||
// معالجة وقت الانتهاء (قد يكون مدة أو وقت)
|
||||
// نحاول استخراج الوقت منه، إذا فشل نضعه 00:00:00
|
||||
$endtime_formatted = date("H:i:s", strtotime($endtime_raw));
|
||||
if (!$endtime_formatted) {
|
||||
$endtime_formatted = "00:00:00";
|
||||
}
|
||||
|
||||
// تجهيز مصفوفة البيانات
|
||||
$data = [
|
||||
":start_location" => $start_location,
|
||||
":end_location" => $end_location,
|
||||
":date" => $date_formatted, // نستخدم الصيغة المعالجة
|
||||
":time" => $time_formatted, // نستخدم الصيغة المعالجة
|
||||
":endtime" => $endtime_formatted,
|
||||
":price" => $price,
|
||||
":passenger_id" => $passenger_id,
|
||||
":driver_id" => $driver_id,
|
||||
":status" => $status,
|
||||
":carType" => $carType,
|
||||
":price_for_driver" => $price_for_driver,
|
||||
":price_for_passenger" => $price_for_passenger,
|
||||
":distance" => $distance,
|
||||
];
|
||||
|
||||
// تسجيل البيانات التي سيتم إدخالها للتأكد
|
||||
error_log("ℹ️ [add_ride.php] Prepared Data: " . json_encode($data));
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. الإضافة في السيرفر المحلي (Main DB)
|
||||
// ---------------------------------------------------------
|
||||
|
||||
$sql = "INSERT INTO `ride` (
|
||||
`start_location`, `end_location`, `date`, `time`, `endtime`,
|
||||
`price`, `passenger_id`, `driver_id`, `status`, `carType`,
|
||||
`price_for_driver`, `price_for_passenger`, `distance`
|
||||
) VALUES (
|
||||
:start_location, :end_location, :date, :time, :endtime,
|
||||
:price, :passenger_id, :driver_id, :status, :carType,
|
||||
:price_for_driver, :price_for_passenger, :distance
|
||||
)";
|
||||
|
||||
try {
|
||||
error_log("🔄 [add_ride.php] Inserting into LOCAL DB...");
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute($data);
|
||||
|
||||
$insertedId = $con->lastInsertId();
|
||||
$count = $stmt->rowCount();
|
||||
|
||||
error_log("✅ [add_ride.php] Local Insert Success. ID: $insertedId");
|
||||
|
||||
if ($count > 0) {
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. الإضافة في سيرفر التتبع (Tracking DB)
|
||||
// ---------------------------------------------------------
|
||||
|
||||
$sqlRemote = "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
|
||||
)";
|
||||
|
||||
// إضافة الـ ID للمصفوفة
|
||||
$data[':id'] = $insertedId;
|
||||
|
||||
try {
|
||||
error_log("🔄 [add_ride.php] Inserting into REMOTE DB...");
|
||||
|
||||
$stmtRemote = $con_ride->prepare($sqlRemote);
|
||||
$stmtRemote->execute($data);
|
||||
|
||||
error_log("✅ [add_ride.php] Remote Insert Success.");
|
||||
|
||||
} catch (PDOException $eRemote) {
|
||||
// نسجل خطأ الريموت لكن لا نوقف العملية لأن اللوكل تم بنجاح
|
||||
error_log("⚠️ [add_ride.php] Remote DB Error: " . $eRemote->getMessage());
|
||||
}
|
||||
|
||||
// طباعة النجاح (JSON صحيح)
|
||||
jsonSuccess($insertedId);
|
||||
|
||||
} else {
|
||||
error_log("❌ [add_ride.php] Failed to insert locally (Rows affected 0).");
|
||||
jsonError("Failed to save ride information locally");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// تسجيل الخطأ بدقة
|
||||
error_log("❌ [add_ride.php] SQL Error: " . $e->getMessage());
|
||||
jsonError("Database Error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
232
backend/ride/rides/add_ride.php
Executable file
232
backend/ride/rides/add_ride.php
Executable file
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// passenger/ride/add_ride.php
|
||||
// PURPOSE : إنشاء رحلة جديدة — ride DB أولاً، primary DB ثانياً
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
include "../../connect.php";
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
} catch (Exception $e) {
|
||||
error_log("[add_ride] Failed to connect to Ride Database: " . $e->getMessage());
|
||||
printFailure("Database connection failed");
|
||||
exit;
|
||||
}
|
||||
// =================================================================================
|
||||
// 🛠️ دالة مساعدة: إرسال الرحلة لسوق السائقين (Marketplace Broadcast)
|
||||
// =================================================================================
|
||||
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData) {
|
||||
$url = getenv('LOCATION_SOCKET_URL');
|
||||
$keyPath = getenv('INTERNAL_SOCKET_KEY_PATH');
|
||||
$INTERNAL_KEY = $keyPath && file_exists($keyPath) ? trim(file_get_contents($keyPath)) : '';
|
||||
|
||||
$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
|
||||
];
|
||||
|
||||
$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);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200);
|
||||
if ($INTERNAL_KEY) {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $INTERNAL_KEY"]);
|
||||
}
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
error_log("[add_ride] Request started. passenger_id=" . ($_POST['passenger_id'] ?? '?'));
|
||||
|
||||
// ── 1. Input ───────────────────────────────────────────────────
|
||||
$start_location = filterRequest("start_location");
|
||||
$end_location = filterRequest("end_location");
|
||||
$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");
|
||||
$distance = filterRequest("distance");
|
||||
$carType = filterRequest("carType");
|
||||
$passenger_name = filterRequest("passenger_name");
|
||||
$passenger_phone = filterRequest("passenger_phone");
|
||||
$passenger_token = filterRequest("passenger_token");
|
||||
$passenger_email = filterRequest("passenger_email");
|
||||
$passenger_wallet = filterRequest("passenger_wallet");
|
||||
$passenger_rating = filterRequest("passenger_rating");
|
||||
$start_name_loc = filterRequest("start_name");
|
||||
$end_name_loc = filterRequest("end_name");
|
||||
$duration_text = filterRequest("duration_text");
|
||||
$distance_text = filterRequest("distance_text");
|
||||
$is_wallet = filterRequest("is_wallet");
|
||||
$has_steps = filterRequest("has_steps");
|
||||
$step0 = filterRequest("step0");
|
||||
$step1 = filterRequest("step1");
|
||||
$step2 = filterRequest("step2");
|
||||
$step3 = filterRequest("step3");
|
||||
$step4 = filterRequest("step4");
|
||||
|
||||
// Validation
|
||||
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)) {
|
||||
[$startLat, $startLng] = array_map('trim', explode(',', $start_location, 2));
|
||||
}
|
||||
if (!empty($end_location)) {
|
||||
[$endLat, $endLng] = array_map('trim', explode(',', $end_location, 2));
|
||||
}
|
||||
|
||||
// ── 4. مصفوفة بيانات الإدخال ──────────────────────────────────
|
||||
$insertData = [
|
||||
':start_location' => $start_location,
|
||||
':end_location' => $end_location,
|
||||
':date' => $date_formatted,
|
||||
':time' => $time_formatted,
|
||||
':endtime' => $endtime_formatted,
|
||||
':price' => $price,
|
||||
':passenger_id' => $passenger_id,
|
||||
':driver_id' => $driver_id,
|
||||
':status' => $status,
|
||||
':carType' => $carType,
|
||||
':price_for_driver' => $price_for_driver,
|
||||
':price_for_passenger' => $price_for_passenger,
|
||||
':distance' => $distance,
|
||||
];
|
||||
|
||||
$sqlInsert = "INSERT INTO `ride`
|
||||
(`start_location`,`end_location`,`date`,`time`,`endtime`,
|
||||
`price`,`passenger_id`,`driver_id`,`status`,`carType`,
|
||||
`price_for_driver`,`price_for_passenger`,`distance`)
|
||||
VALUES
|
||||
(:start_location,:end_location,:date,:time,:endtime,
|
||||
:price,:passenger_id,:driver_id,:status,:carType,
|
||||
:price_for_driver,:price_for_passenger,:distance)";
|
||||
|
||||
try {
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP A — ride DB أولاً (هو المرجع الأساسي)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
$stmtRide = $con_ride->prepare($sqlInsert);
|
||||
$stmtRide->execute($insertData);
|
||||
$insertedId = $con_ride->lastInsertId();
|
||||
|
||||
if (!$insertedId) {
|
||||
error_log("[add_ride] ride DB insert returned no ID.");
|
||||
printFailure("Failed to create ride");
|
||||
exit;
|
||||
}
|
||||
|
||||
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 {
|
||||
$primaryData = $insertData;
|
||||
$primaryData[':id'] = $insertedId;
|
||||
$stmtPrimary = $con->prepare($sqlInsertWithId);
|
||||
$stmtPrimary->execute($primaryData);
|
||||
error_log("[add_ride] primary DB sync success. RideID=$insertedId");
|
||||
} catch (PDOException $ePrimary) {
|
||||
// لا نوقف العملية — ride DB هو المرجع
|
||||
error_log("[add_ride] primary DB sync WARNING: " . $ePrimary->getMessage());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 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,
|
||||
];
|
||||
|
||||
// Direct dispatch للسائقين القريبين
|
||||
$driversData = findBestDrivers($con, $startLat, $startLng, $carType);
|
||||
if (!empty($driversData)) {
|
||||
dispatchRideToDrivers($driversData, $insertedId, $payload, $start_name_loc, $encryptionHelper);
|
||||
error_log("[add_ride] Dispatched RideID=$insertedId to " . count($driversData) . " drivers.");
|
||||
} else {
|
||||
error_log("[add_ride] No direct drivers found for RideID=$insertedId — market only.");
|
||||
}
|
||||
|
||||
// Broadcast للـ marketplace دائماً
|
||||
broadcastRideToMarket($insertedId, $startLat, $startLng, $payload);
|
||||
|
||||
// رد النجاح للتطبيق
|
||||
printSuccess($insertedId);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("[add_ride] CRITICAL ride DB error: " . $e->getMessage());
|
||||
printFailure("Database error");
|
||||
}
|
||||
75
backend/ride/rides/arrive_ride.php
Executable file
75
backend/ride/rides/arrive_ride.php
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// arrive_ride.php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
} catch (Exception $e) {
|
||||
error_log("[arrive_ride] Failed to connect to Ride Database: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$rideId = filterRequest("ride_id");
|
||||
$driverId = filterRequest("driver_id");
|
||||
$passengerToken = filterRequest("passengerToken");
|
||||
|
||||
if (!$rideId || !$driverId) {
|
||||
jsonError("Missing required parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. تحديث الحالة في السيرفر البعيد (Remote DB - con_ride)
|
||||
$stmtRemote = $con_ride->prepare("UPDATE ride SET status = 'arrived', updated_at = NOW() WHERE id = ? AND driver_id = ? AND status = 'Apply'");
|
||||
$stmtRemote->execute([$rideId, $driverId]);
|
||||
|
||||
// 2. تحديث الحالة في السيرفر المحلي (Local DB - con)
|
||||
if (isset($con)) {
|
||||
$stmtLocal = $con->prepare("UPDATE ride SET status = 'arrived', updated_at = NOW() WHERE id = ? AND driver_id = ? AND status = 'Apply'");
|
||||
$stmtLocal->execute([$rideId, $driverId]);
|
||||
}
|
||||
|
||||
// 3. جلب بيانات الراكب للإرسال
|
||||
// نستخدم con_ride لضمان الدقة
|
||||
$stmtPas = $con_ride->prepare("SELECT passenger_id FROM ride WHERE id = ?");
|
||||
$stmtPas->execute([$rideId]);
|
||||
$passenger_id = $stmtPas->fetchColumn();
|
||||
|
||||
if ($passenger_id) {
|
||||
|
||||
// أ) إرسال Socket (الأسرع)
|
||||
$payload = [
|
||||
'status' => 'arrived',
|
||||
'ride_id' => $rideId,
|
||||
'msg' => 'السائق وصل إلى موقعك 🚖'
|
||||
];
|
||||
|
||||
if (function_exists('notifyPassengerOnRideServer')) {
|
||||
notifyPassengerOnRideServer($passenger_id, $payload);
|
||||
}
|
||||
|
||||
// ب) إرسال FCM (باستخدام الدالة الجديدة)
|
||||
if (!empty($passengerToken)) {
|
||||
$fcmData = [
|
||||
'category' => 'Arrive Ride', // نفس الاسم القديم لضمان عمل التطبيق
|
||||
'ride_id' => (string)$rideId,
|
||||
'status' => 'arrived'
|
||||
];
|
||||
|
||||
// 🔥 استخدام sendFCM_Internal كرسالة صامتة
|
||||
sendFCM_Internal(
|
||||
$passengerToken, // الهدف
|
||||
"", // تفريغ العنوان
|
||||
"", // تفريغ النص
|
||||
$fcmData, // البيانات
|
||||
"Arrive Ride", // التصنيف
|
||||
false // ليس Topic
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
jsonSuccess(null, "Arrival notified successfully");
|
||||
|
||||
} catch (Exception $e) {
|
||||
jsonError("Error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
108
backend/ride/rides/cancelRideFromDriver.php
Executable file
108
backend/ride/rides/cancelRideFromDriver.php
Executable file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
// cancelRideFromDriver.php
|
||||
|
||||
// تأكد أن هذا الملف يحتوي على دوال الإشعارات (notifyPassengerOnRideServer)
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// 🚀 تسجيل بداية العملية
|
||||
error_log("🚀 [cancelRide.php] Request Started to Cancel Ride From Driver.");
|
||||
|
||||
$id = filterRequest("id"); // Ride ID
|
||||
|
||||
if (!$id) {
|
||||
error_log("❌ [cancelRide.php] Missing Ride ID.");
|
||||
jsonError("Missing ID");
|
||||
exit;
|
||||
}
|
||||
|
||||
// الحالة الجديدة (إلغاء نهائي من طرف السائق)
|
||||
$newStatus = "cancelRideFromDriver";
|
||||
|
||||
// الحالات المسموح بإلغاء الرحلة فيها فقط
|
||||
// نسمح بالإلغاء إذا كانت في وضع الانتظار أو القبول المبدئي
|
||||
$allowedStatuses = "'wait', 'waiting', 'Apply', 'accepted', 'arrive'";
|
||||
|
||||
try {
|
||||
// ---------------------------------------------------------
|
||||
// 1. التحديث على سيرفر التتبع (Remote DB)
|
||||
// ---------------------------------------------------------
|
||||
// نستخدم شرط الحالة لضمان عدم إلغاء رحلة بدأت بالفعل (Start/Begin)
|
||||
$sql = "UPDATE `ride`
|
||||
SET `status` = ?, `updated_at` = CURRENT_TIMESTAMP
|
||||
WHERE `id` = ?
|
||||
AND `status` IN ($allowedStatuses)";
|
||||
|
||||
// استخدام Prepared Statements للأمان
|
||||
$stmtRemote = $con_ride->prepare($sql);
|
||||
$stmtRemote->execute([$newStatus, $id]);
|
||||
|
||||
$count = $stmtRemote->rowCount();
|
||||
error_log("ℹ️ [cancelRide.php] Remote DB Rows Affected: $count");
|
||||
|
||||
// التحقق: هل تم التحديث؟
|
||||
if ($count > 0) {
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. التحديث على السيرفر المحلي (Local DB)
|
||||
// ---------------------------------------------------------
|
||||
// نبدأ معاملة لضمان تكامل البيانات
|
||||
if (isset($con)) {
|
||||
$con->beginTransaction();
|
||||
try {
|
||||
$stmtLocal = $con->prepare($sql);
|
||||
$stmtLocal->execute([$newStatus, $id]);
|
||||
|
||||
// تحديث جدول driver_orders أيضاً لتوحيد الحالة (اختياري ولكنه مفضل)
|
||||
$stmtDriverOrder = $con->prepare("UPDATE driver_orders SET status = ? WHERE order_id = ?");
|
||||
$stmtDriverOrder->execute([$newStatus, $id]);
|
||||
|
||||
$con->commit();
|
||||
} catch (Exception $eLocal) {
|
||||
$con->rollBack();
|
||||
error_log("⚠️ Local DB Update Failed: " . $eLocal->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. 🔥 إشعار الراكب عبر السوكيت (القطعة المفقودة) 🔥
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// أ. جلب معرف الراكب لإرسال الإشعار له
|
||||
// نستخدم connection الرحلات لضمان وجود البيانات
|
||||
$stmtPas = $con_ride->prepare("SELECT passenger_id FROM ride WHERE id = ?");
|
||||
$stmtPas->execute([$id]);
|
||||
$passenger_id = $stmtPas->fetchColumn();
|
||||
|
||||
if ($passenger_id) {
|
||||
$payload = [
|
||||
'ride_id' => $id,
|
||||
'status' => 'cancelled', // هذه الحالة يستقبلها الفلاتر ويغلق الواجهة
|
||||
'msg' => 'للأسف، قام السائق بإلغاء الرحلة.'
|
||||
];
|
||||
|
||||
// استدعاء الدالة المعرفة في functions.php/connect.php
|
||||
if (function_exists('notifyPassengerOnRideServer')) {
|
||||
notifyPassengerOnRideServer($passenger_id, $payload);
|
||||
error_log("📡 [cancelRide.php] Notification sent to Passenger ID: $passenger_id");
|
||||
} else {
|
||||
error_log("⚠️ [cancelRide.php] Function notifyPassengerOnRideServer not found!");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. إنهاء العملية
|
||||
// ---------------------------------------------------------
|
||||
error_log("✅ [cancelRide.php] Ride cancelled successfully.");
|
||||
jsonSuccess(null, "Ride cancelled successfully");
|
||||
|
||||
} else {
|
||||
// الفشل يعني أن الرحلة غير موجودة أو حالتها لا تسمح بالإلغاء (مثلاً بدأت بالفعل)
|
||||
error_log("⚠️ [cancelRide.php] Failed. ID invalid OR Status not allowed (maybe started?).");
|
||||
jsonError("Cannot cancel ride. Status might be started or already completed.");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("❌ [cancelRide.php] Database Error: " . $e->getMessage());
|
||||
jsonError("Database Error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
148
backend/ride/rides/cancel_ride_by_driver.php
Executable file
148
backend/ride/rides/cancel_ride_by_driver.php
Executable file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
// cancel_ride_by_driver.php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
} catch (Exception $e) {
|
||||
error_log("[cancel_ride_by_driver] Failed to connect to Ride Database: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$rideId = filterRequest("ride_id");
|
||||
$driverId = filterRequest("driver_id");
|
||||
$reason = filterRequest("reason");
|
||||
$passengerToken = filterRequest("passenger_token");
|
||||
|
||||
// تثبيت الحالة
|
||||
$statusText = "CancelFromDriverAfterApply";
|
||||
|
||||
if (!$rideId || !$driverId) {
|
||||
jsonError("Missing parameters");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 1. معالجة driver_orders (Insert or Update)
|
||||
// ---------------------------------------------------------
|
||||
$checkStmt = $con->prepare("SELECT order_id FROM driver_orders WHERE order_id = ? AND driver_id = ?");
|
||||
$checkStmt->execute([$rideId, $driverId]);
|
||||
|
||||
if ($checkStmt->rowCount() > 0) {
|
||||
// موجود: تحديث
|
||||
$stmtLog = $con->prepare("UPDATE driver_orders SET status = ?, notes = ?, created_at = NOW() WHERE order_id = ? AND driver_id = ?");
|
||||
$stmtLog->execute([$statusText, $reason, $rideId, $driverId]);
|
||||
} else {
|
||||
// غير موجود: إدخال
|
||||
$stmtLog = $con->prepare("INSERT INTO driver_orders (driver_id, order_id, status, created_at, notes) VALUES (?, ?, ?, NOW(), ?)");
|
||||
$stmtLog->execute([$driverId, $rideId, $statusText, $reason]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. منطق الحظر (Business Logic)
|
||||
// ---------------------------------------------------------
|
||||
$stmtCount = $con->prepare("
|
||||
SELECT COUNT(*) FROM driver_orders
|
||||
WHERE driver_id = ?
|
||||
AND status = ?
|
||||
AND created_at >= NOW() - INTERVAL 1 DAY
|
||||
");
|
||||
$stmtCount->execute([$driverId, $statusText]);
|
||||
$cancelCount = $stmtCount->fetchColumn();
|
||||
|
||||
$isBlocked = false;
|
||||
$blockUntil = "";
|
||||
|
||||
if ($cancelCount >= 3) {
|
||||
$isBlocked = true;
|
||||
$blockUntil = date('Y-m-d H:i:s', strtotime('+4 hours'));
|
||||
// يمكنك هنا تحديث حالة السائق في جدول driver إذا لزم الأمر
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. تحديث حالة الرحلة في جدول ride
|
||||
// ---------------------------------------------------------
|
||||
$sqlRide = "UPDATE ride SET status = ?, driver_id = 0 WHERE id = ?";
|
||||
|
||||
// Local DB
|
||||
$con->prepare($sqlRide)->execute([$statusText, $rideId]);
|
||||
|
||||
// Remote DB (إن وجد)
|
||||
if (isset($con_ride)) {
|
||||
$con_ride->prepare($sqlRide)->execute([$statusText, $rideId]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. إشعار الراكب
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// أ) Socket (يحتاج Passenger ID)
|
||||
$stmtPas = $con->prepare("SELECT passenger_id FROM ride WHERE id = ?");
|
||||
$stmtPas->execute([$rideId]);
|
||||
$passenger_id = $stmtPas->fetchColumn();
|
||||
|
||||
if ($passenger_id) {
|
||||
$socketPayload = [
|
||||
'ride_id' => $rideId,
|
||||
'status' => 'cancelled_by_driver',
|
||||
'msg' => 'تم إلغاء الرحلة من قبل السائق'
|
||||
];
|
||||
if (function_exists('notifyPassengerOnRideServer')) {
|
||||
notifyPassengerOnRideServer($passenger_id, $socketPayload);
|
||||
}
|
||||
}
|
||||
|
||||
// ب) FCM (Internal)
|
||||
if (empty($passengerToken) && $passenger_id) {
|
||||
$stmtToken = $con->prepare("SELECT token FROM tokens WHERE passengerID = ? ORDER BY id DESC LIMIT 1");
|
||||
$stmtToken->execute([$passenger_id]);
|
||||
$rawToken = $stmtToken->fetchColumn();
|
||||
if ($rawToken) {
|
||||
$passengerToken = $rawToken;
|
||||
if (!empty($encryptionHelper)) {
|
||||
try {
|
||||
$decrypted = $encryptionHelper->decryptData($rawToken);
|
||||
if ($decrypted !== false && !empty($decrypted)) {
|
||||
$passengerToken = trim($decrypted);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($passengerToken)) {
|
||||
$fcmData = [
|
||||
'category' => 'Cancel Trip from driver',
|
||||
'ride_id' => (string)$rideId
|
||||
];
|
||||
|
||||
// 🔥 استخدام الدالة الجديدة
|
||||
sendFCM_Internal(
|
||||
$passengerToken, // الهدف
|
||||
"تم إلغاء الرحلة ❌", // العنوان
|
||||
"عذراً، قام السائق بإلغاء الرحلة.", // النص
|
||||
$fcmData, // البيانات
|
||||
"Cancel Trip from driver", // التصنيف (تأكد أنه يطابق ما في تطبيق الراكب)
|
||||
false // ليس Topic
|
||||
);
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
|
||||
// 5. الرد للفلاتر
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"cancel_count" => $cancelCount,
|
||||
"is_blocked" => $isBlocked,
|
||||
"block_until" => $blockUntil
|
||||
]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
if ($con->inTransaction()) $con->rollBack();
|
||||
jsonError("DB Error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
147
backend/ride/rides/cancel_ride_by_passenger.php
Executable file
147
backend/ride/rides/cancel_ride_by_passenger.php
Executable file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
} catch (Exception $e) {
|
||||
error_log("[cancel_ride_by_passenger] Failed to connect to Ride Database: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// cancel_ride_by_passenger.php
|
||||
|
||||
$rideId = filterRequest("ride_id");
|
||||
$reason = filterRequest("reason");
|
||||
|
||||
if (!$rideId) {
|
||||
jsonError("Missing Ride ID");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// جلب بيانات الرحلة للتحقق
|
||||
$stmt = $con->prepare("SELECT driver_id, status FROM ride WHERE id = ?");
|
||||
$stmt->execute([$rideId]);
|
||||
$ride = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$ride) {
|
||||
jsonError("Ride not found");
|
||||
exit;
|
||||
}
|
||||
|
||||
$driverId = $ride['driver_id'];
|
||||
$currentStatus = $ride['status'];
|
||||
|
||||
if ($currentStatus == 'Begin') {
|
||||
jsonError("Cannot cancel started ride");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 1. تحديث قواعد البيانات (Transaction)
|
||||
// =================================================================
|
||||
$con->beginTransaction();
|
||||
|
||||
// تحديث waitingRides
|
||||
$updateWaiting = $con->prepare("UPDATE waitingRides SET status = ? WHERE id = ?");
|
||||
$updateWaiting->execute(['cancelled_by_passenger', $rideId]);
|
||||
|
||||
// تحديث ride (محلي)
|
||||
$updateRide = $con->prepare("UPDATE ride SET status = ?, updated_at = NOW() WHERE id = ?");
|
||||
$updateRide->execute(['cancelled_by_passenger', $rideId]);
|
||||
|
||||
// تحديث driver_orders
|
||||
if ($driverId > 0) {
|
||||
$updateOrder = $con->prepare("UPDATE driver_orders SET status = 'cancelled_by_passenger', notes = ? WHERE order_id = ? AND driver_id = ?");
|
||||
$updateOrder->execute([$reason, $rideId, $driverId]);
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
|
||||
// تحديث السيرفر البعيد (Remote DB)
|
||||
if (isset($con_ride)) {
|
||||
try {
|
||||
$updateRide2 = $con_ride->prepare("UPDATE ride SET status = ?, updated_at = NOW() WHERE id = ?");
|
||||
$updateRide2->execute(['cancelled_by_passenger', $rideId]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Secondary DB update failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 2. إشعار السائق (Socket + FCM)
|
||||
// =================================================================
|
||||
if ($driverId > 0) {
|
||||
|
||||
// أ) Socket (إشعار السائق في التطبيق فوراً)
|
||||
$socketUrl = 'http://188.68.36.205:2021';
|
||||
$internalKeyPath = '/home/intaleq-api/.internal_socket_key';
|
||||
$internalKey = file_exists($internalKeyPath) ? trim(file_get_contents($internalKeyPath)) : '';
|
||||
|
||||
$ch = curl_init($socketUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
||||
'action' => 'cancel_ride',
|
||||
'driver_id' => $driverId,
|
||||
'ride_id' => $rideId,
|
||||
'reason' => $reason
|
||||
]));
|
||||
if (!empty($internalKey)) curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $internalKey"]);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 500);
|
||||
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
|
||||
@curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// ب) FCM (باستخدام الدالة الجديدة مع فك التشفير)
|
||||
$driverToken = filterRequest("driver_token");
|
||||
|
||||
if (empty($driverToken)) {
|
||||
$stmtToken = $con->prepare("SELECT token FROM driverToken WHERE captain_id = ?");
|
||||
$stmtToken->execute([$driverId]);
|
||||
$rawToken = $stmtToken->fetchColumn();
|
||||
|
||||
if ($rawToken) {
|
||||
$driverToken = $rawToken;
|
||||
|
||||
// 🔥 محاولة فك التشفير (لأن التوكنات غالباً مشفرة)
|
||||
if (!empty($encryptionHelper)) {
|
||||
try {
|
||||
$decrypted = $encryptionHelper->decryptData($rawToken);
|
||||
if ($decrypted !== false && !empty($decrypted)) {
|
||||
$driverToken = trim($decrypted);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// في حال الفشل نستخدم الخام
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($driverToken)) {
|
||||
// تجهيز البيانات
|
||||
$fcmData = [
|
||||
'category' => 'Cancel Trip',
|
||||
'ride_id' => (string)$rideId,
|
||||
'reason' => $reason
|
||||
];
|
||||
|
||||
// إرسال الإشعار
|
||||
sendFCM_Internal(
|
||||
$driverToken, // الهدف
|
||||
"إلغاء الرحلة 🚫", // العنوان
|
||||
"قام الراكب بإلغاء الرحلة: $reason", // النص
|
||||
$fcmData, // البيانات
|
||||
'Cancel Trip', // التصنيف
|
||||
false // ليس Topic
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
jsonSuccess(null, "Ride cancelled successfully");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
if ($con->inTransaction()) $con->rollBack();
|
||||
error_log("Cancel ride error: " . $e->getMessage());
|
||||
jsonError("Database error occurred");
|
||||
}
|
||||
?>
|
||||
64
backend/ride/rides/cron_ride_timeout.php
Executable file
64
backend/ride/rides/cron_ride_timeout.php
Executable file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
// cron_cleanup_waiting.php
|
||||
// مسار الاتصال بقاعدة البيانات
|
||||
require_once __DIR__ . '/../../get_connect.php';
|
||||
|
||||
// تسجيل في اللوج لبداية العملية
|
||||
error_log("⏰ [Cleanup Cron] Started cleaning old waiting rides...");
|
||||
|
||||
try {
|
||||
// المدة المسموحة بالدقائق
|
||||
$minutesLimit = 15;
|
||||
|
||||
// =========================================================
|
||||
// الخطوة 1: تحديث الحالة في الجدول الرئيسي (الأرشيف)
|
||||
// =========================================================
|
||||
// نقوم بتحديث حالة الرحلات التي ستُحذف ليعلم الراكب أنها انتهت (Time Out)
|
||||
// بدلاً من أن تبقى 'waiting' للأبد في سجلات الراكب
|
||||
|
||||
$sqlUpdate = "UPDATE ride
|
||||
SET status = 'timeout'
|
||||
WHERE id IN (
|
||||
SELECT id FROM waitingRides
|
||||
WHERE created_at < DATE_SUB(NOW(), INTERVAL $minutesLimit MINUTE)
|
||||
) AND status = 'waiting'"; // نتأكد أننا نحدث ما هو معلق فقط
|
||||
|
||||
$stmtUpdate = $con->prepare($sqlUpdate);
|
||||
$stmtUpdate->execute();
|
||||
$updatedCount = $stmtUpdate->rowCount();
|
||||
|
||||
// =========================================================
|
||||
// الخطوة 2: الحذف من جدول الانتظار (تنظيف Hot Data)
|
||||
// =========================================================
|
||||
|
||||
$sqlDelete = "DELETE FROM waitingRides
|
||||
WHERE created_at < DATE_SUB(NOW(), INTERVAL $minutesLimit MINUTE)";
|
||||
|
||||
$stmtDelete = $con->prepare($sqlDelete);
|
||||
$stmtDelete->execute();
|
||||
$deletedCount = $stmtDelete->rowCount();
|
||||
|
||||
// =========================================================
|
||||
// الخطوة 3: (اختياري) تنظيف الريدز
|
||||
// =========================================================
|
||||
// بما أنك تستخدم Redis، المفترض أن تحذفها منه أيضاً.
|
||||
// لكن بما أن الريدز يعتمد على TTL (Expire) أو سيتم تحديثه عند الطلب القادم،
|
||||
// فالحذف من الـ MySQL يكفي لأن getRideWaiting سيفحص MySQL ولن يجدها.
|
||||
|
||||
// تقرير العملية
|
||||
if ($deletedCount > 0) {
|
||||
$msg = "✅ [Cleanup Cron] Success: Timed out $updatedCount rides in Main DB, and Deleted $deletedCount rides from Waiting DB.";
|
||||
error_log($msg);
|
||||
echo json_encode(["status" => "success", "message" => $msg]);
|
||||
} else {
|
||||
$msg = "💤 [Cleanup Cron] No expired rides found.";
|
||||
error_log($msg);
|
||||
echo json_encode(["status" => "success", "message" => "Nothing to clean."]);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$errorMsg = "❌ [Cleanup Cron] Error: " . $e->getMessage();
|
||||
error_log($errorMsg);
|
||||
echo json_encode(["status" => "failure", "message" => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
19
backend/ride/rides/delete.php
Normal file
19
backend/ride/rides/delete.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// استلام قيمة ID بعد التصفية
|
||||
$id = filterRequest("id");
|
||||
|
||||
// استخدام استعلام محضّر لتفادي SQL Injection
|
||||
$sql = "DELETE FROM `ride` WHERE `id` = :id";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
// التحقق من نجاح العملية
|
||||
if ($stmt->rowCount() > 0) {
|
||||
jsonSuccess(null, "Ride deleted successfully");
|
||||
} else {
|
||||
jsonError("Failed to delete ride");
|
||||
}
|
||||
?>
|
||||
89
backend/ride/rides/emailToPassengerTripDetail.php
Normal file
89
backend/ride/rides/emailToPassengerTripDetail.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
// الاتصال بقاعدة البيانات والدوال المشتركة
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// تضمين autoload من Composer (مجلد vendor في الجذر)
|
||||
require_once __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
// استقبال البيانات من الطلب
|
||||
$passengerName = filterRequest("name");
|
||||
$passengerEmail = filterRequest("email");
|
||||
$passengerPhone = filterRequest("phone");
|
||||
$fee = floatval(filterRequest("fee"));
|
||||
$startLocation = filterRequest("startLocation");
|
||||
$endLocation = filterRequest("endLocation");
|
||||
$startNameLocation = filterRequest("startNameLocation");
|
||||
$endNameLocation = filterRequest("endNameLocation");
|
||||
$timeOfTrip = filterRequest("timeOfTrip");
|
||||
$duration = intval(filterRequest("duration"));
|
||||
$duration = floor($duration / 60); // تحويل للـ minutes
|
||||
|
||||
// حساب الخصم
|
||||
$discount = 0;
|
||||
if ($startNameLocation && $endNameLocation) {
|
||||
$discount = $fee * 0.12;
|
||||
$beforDiscount = $fee;
|
||||
$fee -= $discount;
|
||||
}
|
||||
|
||||
// بناء الإيميل
|
||||
$bodyEmail = "
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: 'Arial', sans-serif; background-color: #f8f9fa; color: #333; line-height: 1.6; }
|
||||
table { border-collapse: collapse; width: 100%; background-color: white; margin: 20px 0; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
|
||||
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
th { background-color: #007bff; color: white; }
|
||||
tr:nth-child(even) { background-color: #f2f2f2; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src='https://api.tripz-egypt.com/tripz/images/logo%20(1).png' alt='Tripz Logo' style='width: 150px; margin: 20px auto; display: block;'>
|
||||
<p>Hi $passengerName,</p>
|
||||
<p>Thank you for booking your ride with <strong>Tripz</strong>. Here are the details of your recent trip:</p>
|
||||
<table>
|
||||
<tr><th>Detail</th><th>Value</th></tr>
|
||||
<tr><td>Passenger</td><td>$passengerName</td></tr>
|
||||
<tr><td>Email</td><td>$passengerEmail</td></tr>
|
||||
<tr><td>Phone</td><td>$passengerPhone</td></tr>
|
||||
<tr><td>Fee</td><td>$$fee</td></tr>
|
||||
<tr><td>Start Location</td><td>$startLocation ($startNameLocation)</td></tr>
|
||||
<tr><td>End Location</td><td>$endLocation ($endNameLocation)</td></tr>
|
||||
<tr><td>Time of Trip</td><td>$timeOfTrip</td></tr>
|
||||
<tr><td>Duration</td><td>$duration minutes</td></tr>
|
||||
</table>";
|
||||
|
||||
if ($discount > 0) {
|
||||
$bodyEmail .= "<p>You have received a 12% discount on your trip from $startNameLocation to $endNameLocation. The original fee was $$beforDiscount. Your discounted fee is $$fee.</p>";
|
||||
}
|
||||
|
||||
$bodyEmail .= "<p>Thank you for using <strong>Tripz</strong>. We hope you have a great day!</p><p>Best regards,<br>Tripz Team</p></body></html>";
|
||||
|
||||
// إعداد البريد
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
$mail->Host = 'smtp.hostinger.com';
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = 'hamzaayed@tripz-egypt.com';
|
||||
$mail->Password = $TRIPZ_SMTP_PASSWORD;
|
||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
||||
$mail->Port = 587;
|
||||
|
||||
$mail->setFrom('hamzaayed@tripz-egypt.com', 'Tripz');
|
||||
$mail->addAddress($passengerEmail, $passengerName);
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = 'Your Tripz Trip Details';
|
||||
$mail->Body = $bodyEmail;
|
||||
|
||||
$mail->send();
|
||||
echo json_encode(["status" => "success", "message" => "Email sent successfully"]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["status" => "error", "message" => $mail->ErrorInfo]);
|
||||
}
|
||||
0
backend/ride/rides/error_log
Normal file
0
backend/ride/rides/error_log
Normal file
290
backend/ride/rides/finish_ride_updates.php
Executable file
290
backend/ride/rides/finish_ride_updates.php
Executable file
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
} catch (Exception $e) {
|
||||
error_log("[finish_ride_updates] Failed to connect to Ride Database: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// finish_ride_updates.php — Atomic Server-to-Server
|
||||
// ============================================================
|
||||
// Driver App calls this ONCE with raw ride data (NOT the price).
|
||||
// Server calculates price securely, processes payment via S2S,
|
||||
// and atomically updates all databases within a transaction.
|
||||
//
|
||||
// Flow:
|
||||
// 1. Receive raw params from driver app
|
||||
// 2. Calculate price server-side (from DB + actual distance)
|
||||
// 3. BEGIN TRANSACTION (local DB)
|
||||
// 4. Update ride on local DB + remote DB (con_ride)
|
||||
// 5. Update driver_orders
|
||||
// 6. S2S cURL → Wallet Payment Server (process_ride_payments.php)
|
||||
// 7. If payment OK → COMMIT, notify passenger (Socket + FCM)
|
||||
// 8. If payment FAIL → ROLLBACK, ride stays 'Begin', safe retry
|
||||
// ============================================================
|
||||
|
||||
// --- Secure S2S Configuration ---
|
||||
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY') );
|
||||
define('WALLET_PAYMENT_URL', 'https://walletintaleq.intaleq.xyz/v1/main/ride/payment/process_ride_payments.php');
|
||||
|
||||
// ============================================================
|
||||
// 1. Receive Raw Parameters (NO price from client)
|
||||
// ============================================================
|
||||
$rideId = filterRequest("rideId");
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$newStatus = filterRequest("status"); // Expected: "Finished"
|
||||
$actualDistance = filterRequest("actualDistance");
|
||||
$actualDuration = filterRequest("actualDuration");
|
||||
$passengerToken = filterRequest("passengerToken");
|
||||
$driver_token = filterRequest("driver_token");
|
||||
$walletChecked = filterRequest("walletChecked");
|
||||
$passengerWalletBurc = filterRequest("passengerWalletBurc");
|
||||
|
||||
if (empty($rideId) || empty($newStatus) || empty($driver_id) || empty($passengerId)) {
|
||||
jsonError("Missing required parameters: rideId, driver_id, passengerId, status");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($newStatus !== 'Finished') {
|
||||
jsonError("Invalid status. Expected: Finished");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 2. Server-Side Price Calculation (Secure — NOT from client)
|
||||
// ============================================================
|
||||
try {
|
||||
// Fetch ride data from remote/local DB for server-side calculation
|
||||
$stmtRideData = $con->prepare("
|
||||
SELECT id, price AS quoted_price, car_type,
|
||||
distance AS planned_distance, passenger_id, driver_id
|
||||
FROM ride WHERE id = ? AND driver_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtRideData->execute([$rideId, $driver_id]);
|
||||
$rideData = $stmtRideData->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$rideData) {
|
||||
jsonError("Ride not found or driver mismatch.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$quotedPrice = floatval($rideData['quoted_price'] ?? 0);
|
||||
$kazanPercent = 10;
|
||||
$carType = $rideData['car_type'] ?? 'Fixed Price';
|
||||
|
||||
// Fixed-price types: use quoted price as-is
|
||||
$fixedPriceTypes = ['Speed', 'Fixed Price', 'Awfar Car'];
|
||||
if (in_array($carType, $fixedPriceTypes)) {
|
||||
$finalPrice = $quotedPrice;
|
||||
} else {
|
||||
// Variable pricing: calculate from actual distance
|
||||
$cleanDist = preg_replace('/[^0-9.]/', '', $actualDistance);
|
||||
$distanceKm = floatval($cleanDist);
|
||||
|
||||
if ($distanceKm <= 0) {
|
||||
$finalPrice = $quotedPrice; // fallback
|
||||
} else {
|
||||
$perKmRate = getPerKmRate($carType);
|
||||
$perMinRate = getPerMinRate();
|
||||
$durationMin = intval(preg_replace('/[^0-9]/', '', $actualDuration));
|
||||
|
||||
$calculated = ($distanceKm * $perKmRate) + ($durationMin * $perMinRate);
|
||||
$calculated *= (1 + ($kazanPercent / 100));
|
||||
|
||||
$finalPrice = max($quotedPrice, round($calculated, 2));
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Error calculating price: " . $e->getMessage());
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3. Atomic Transaction: Update DBs + Process Payment
|
||||
// ============================================================
|
||||
try {
|
||||
// --- Update Remote DB (con_ride) FIRST ---
|
||||
// (Not in transaction — remote DB doesn't support cross-DB rollback,
|
||||
// but we keep it minimal as a "best-effort" update)
|
||||
if (isset($con_ride)) {
|
||||
$stmtRemote = $con_ride->prepare(
|
||||
"UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"
|
||||
);
|
||||
$stmtRemote->execute([$newStatus, $finalPrice, $rideId]);
|
||||
}
|
||||
|
||||
// --- BEGIN Local DB Transaction ---
|
||||
$con->beginTransaction();
|
||||
|
||||
// 3a. Update ride (local DB)
|
||||
$stmtLocal = $con->prepare(
|
||||
"UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"
|
||||
);
|
||||
$stmtLocal->execute([$newStatus, $finalPrice, $rideId]);
|
||||
|
||||
if ($stmtLocal->rowCount() == 0) {
|
||||
throw new Exception("Ride already finished or not found in local DB.");
|
||||
}
|
||||
|
||||
// 3b. 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]);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3c. Server-to-Server Payment Processing (S2S)
|
||||
// ============================================================
|
||||
$paymentPayload = [
|
||||
'rideId' => $rideId,
|
||||
'driverId' => $driver_id,
|
||||
'passengerId' => $passengerId,
|
||||
'paymentAmount' => $finalPrice,
|
||||
'paymentMethod' => ($walletChecked === 'true') ? 'wallet' : 'cash',
|
||||
'walletChecked' => $walletChecked,
|
||||
'passengerWalletBurc' => $passengerWalletBurc,
|
||||
'authToken' => $driver_token,
|
||||
];
|
||||
|
||||
$ch = curl_init(WALLET_PAYMENT_URL);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query($paymentPayload),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'X-S2S-Api-Key: ' . S2S_SHARED_KEY,
|
||||
],
|
||||
]);
|
||||
|
||||
$paymentResponse = curl_exec($ch);
|
||||
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// Validate payment response
|
||||
$paymentSuccess = false;
|
||||
$paymentError = '';
|
||||
|
||||
if ($curlError) {
|
||||
$paymentError = "S2S connection error: " . $curlError;
|
||||
} elseif ($httpStatusCode !== 200) {
|
||||
$paymentError = "Payment server returned HTTP $httpStatusCode";
|
||||
} else {
|
||||
$paymentResult = json_decode($paymentResponse, true);
|
||||
if ($paymentResult && isset($paymentResult['status']) && $paymentResult['status'] === 'success') {
|
||||
$paymentSuccess = true;
|
||||
} else {
|
||||
$paymentError = $paymentResult['error'] ?? 'Payment server returned failure';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$paymentSuccess) {
|
||||
// ❌ Payment failed — ROLLBACK everything
|
||||
$con->rollBack();
|
||||
error_log("[finish_ride_updates] Payment FAILED for ride $rideId: $paymentError");
|
||||
jsonError("Payment processing failed: $paymentError");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ Payment succeeded — COMMIT
|
||||
$con->commit();
|
||||
|
||||
// ============================================================
|
||||
// 4. Notifications (After successful commit)
|
||||
// ============================================================
|
||||
$passenger_id = $passengerId; // alias for legacy code
|
||||
|
||||
if (!empty($passenger_id)) {
|
||||
// Legacy list for backward compatibility
|
||||
$legacyList = [
|
||||
(string)$driver_id,
|
||||
(string)$rideId,
|
||||
(string)$driver_token,
|
||||
(string)$finalPrice
|
||||
];
|
||||
|
||||
// a) Socket notification
|
||||
$socketPayload = [
|
||||
'ride_id' => $rideId,
|
||||
'status' => 'finished',
|
||||
'price' => $finalPrice,
|
||||
'DriverList' => $legacyList
|
||||
];
|
||||
|
||||
if (function_exists('notifyPassengerOnRideServer')) {
|
||||
notifyPassengerOnRideServer($passenger_id, $socketPayload);
|
||||
}
|
||||
|
||||
// b) FCM notification
|
||||
if (!empty($passengerToken)) {
|
||||
$fcmData = [
|
||||
'ride_id' => (string)$rideId,
|
||||
'price' => (string)$finalPrice,
|
||||
'DriverList' => $legacyList
|
||||
];
|
||||
|
||||
sendFCM_Internal(
|
||||
$passengerToken,
|
||||
"تم إنهاء الرحلة 🏁",
|
||||
"المبلغ المطلوب: " . $finalPrice . " ل.س",
|
||||
$fcmData,
|
||||
'Driver Finish Trip',
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 5. Return Success with server-calculated price
|
||||
// ============================================================
|
||||
jsonSuccess([
|
||||
'price' => $finalPrice,
|
||||
'rideId' => $rideId
|
||||
], "Ride finished and payment processed successfully.");
|
||||
|
||||
} catch (Exception $e) {
|
||||
if (isset($con) && $con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[finish_ride_updates] Error for ride $rideId: " . $e->getMessage());
|
||||
jsonError("Transaction failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper Functions
|
||||
// ============================================================
|
||||
|
||||
function getPerKmRate(string $carType): float {
|
||||
$rates = [
|
||||
'Comfort' => 44,
|
||||
'Lady' => 44,
|
||||
'Mishwar Vip' => 50,
|
||||
'Electric' => 45,
|
||||
'Van' => 63,
|
||||
'Delivery' => 25,
|
||||
'Speed' => 36,
|
||||
'Fixed Price' => 36,
|
||||
'Awfar Car' => 36,
|
||||
];
|
||||
return $rates[$carType] ?? 36;
|
||||
}
|
||||
|
||||
function getPerMinRate(): float {
|
||||
$hour = (int)date('H');
|
||||
if ($hour >= 21 || $hour < 1) return 11; // Late
|
||||
if ($hour >= 14 && $hour <= 17) return 10; // Peak
|
||||
return 9; // Normal
|
||||
}
|
||||
?>
|
||||
48
backend/ride/rides/get.php
Executable file
48
backend/ride/rides/get.php
Executable file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
$driver_id = filterRequest("driver_id");
|
||||
|
||||
try {
|
||||
// Step 1: Count rides for passenger or driver
|
||||
$baseSql = "SELECT COUNT(*) AS total_rows FROM `ride`";
|
||||
$params = [];
|
||||
|
||||
if (!empty($passenger_id)) {
|
||||
$baseSql .= " WHERE passenger_id = :passenger_id";
|
||||
$params[':passenger_id'] = $passenger_id;
|
||||
} elseif (!empty($driver_id)) {
|
||||
$baseSql .= " WHERE driver_id = :driver_id";
|
||||
$params[':driver_id'] = $driver_id;
|
||||
}
|
||||
|
||||
$stmt = $con->prepare($baseSql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$total_rows = $row['total_rows'] ?? 0;
|
||||
|
||||
if ($total_rows > 0) {
|
||||
// Step 2: Fetch the latest 10 ride records
|
||||
$rideSql = "SELECT * FROM `ride`";
|
||||
if (!empty($passenger_id)) {
|
||||
$rideSql .= " WHERE passenger_id = :passenger_id ORDER BY created_at DESC LIMIT 10";
|
||||
} elseif (!empty($driver_id)) {
|
||||
$rideSql .= " WHERE driver_id = :driver_id ORDER BY created_at DESC LIMIT 10";
|
||||
}
|
||||
|
||||
$rideStmt = $con->prepare($rideSql);
|
||||
$rideStmt->execute($params);
|
||||
$rides = $rideStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"data" => $rides
|
||||
]);
|
||||
} else {
|
||||
jsonSuccess([], "No rides found");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
91
backend/ride/rides/getRealTimeHeatmap.php
Executable file
91
backend/ride/rides/getRealTimeHeatmap.php
Executable file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// getRealTimeHeatmap.php
|
||||
require_once __DIR__ . '/../../get_connect.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
$precision = 2; // دقة الشبكة (تقريباً 1 كم)
|
||||
|
||||
// الأوزان
|
||||
$WEIGHT_WAITING = 5; // طلب انتظار = 5 نقاط
|
||||
$WEIGHT_MISSED = 8; // طلب ضائع = 8 نقاط (أهمية قصوى)
|
||||
$WEIGHT_ACTIVE = 1; // طلب نشط = 1 نقطة
|
||||
|
||||
$grid = [];
|
||||
|
||||
// 1. طلبات الانتظار (Waiting)
|
||||
$stmtW = $con->prepare("SELECT start_lat, start_lng FROM waitingRides WHERE status IN ('wait', 'waiting')");
|
||||
$stmtW->execute();
|
||||
while ($row = $stmtW->fetch(PDO::FETCH_ASSOC)) {
|
||||
addToGrid($grid, $row['start_lat'], $row['start_lng'], $precision, $WEIGHT_WAITING);
|
||||
}
|
||||
|
||||
// 2. طلبات ضائعة (Timeout)
|
||||
$stmtM = $con->prepare("SELECT start_location FROM ride WHERE (status = 'timeout' OR status = 'cancelled_no_driver_found') AND created_at >= DATE_SUB(NOW(), INTERVAL 20 MINUTE)");
|
||||
$stmtM->execute();
|
||||
while ($row = $stmtM->fetch(PDO::FETCH_ASSOC)) {
|
||||
$parts = explode(',', $row['start_location']);
|
||||
if (count($parts) == 2) addToGrid($grid, $parts[0], $parts[1], $precision, $WEIGHT_MISSED);
|
||||
}
|
||||
|
||||
// 3. طلبات نشطة (Active)
|
||||
$stmtA = $con->prepare("SELECT start_location FROM ride WHERE created_at >= DATE_SUB(NOW(), INTERVAL 15 MINUTE) AND status NOT IN ('timeout', 'cancelled_no_driver_found')");
|
||||
$stmtA->execute();
|
||||
while ($row = $stmtA->fetch(PDO::FETCH_ASSOC)) {
|
||||
$parts = explode(',', $row['start_location']);
|
||||
if (count($parts) == 2) addToGrid($grid, $parts[0], $parts[1], $precision, $WEIGHT_ACTIVE);
|
||||
}
|
||||
|
||||
// تجهيز البيانات النهائية
|
||||
$finalData = [];
|
||||
foreach ($grid as $cell) {
|
||||
$score = $cell['score']; // مجموع النقاط (الوزن)
|
||||
$count = $cell['count']; // العدد الحقيقي للطلبات
|
||||
|
||||
// 🧠 المنطق المزدوج: نحدد اللون بناءً على النقاط أو العدد
|
||||
$intensity = "low";
|
||||
$surge = 1.0;
|
||||
|
||||
// المعادلة: منطقة حمراء إذا كان السكور عالي جداً (مشاكل) أو العدد كبير جداً (زحمة)
|
||||
if ($score >= 15 || $count >= 5) {
|
||||
$intensity = "high"; // أحمر (خطر/فرصة ذهبية)
|
||||
$surge = 1.5;
|
||||
} elseif ($score >= 8 || $count >= 3) {
|
||||
$intensity = "medium"; // برتقالي
|
||||
$surge = 1.2;
|
||||
} elseif ($score >= 3 || $count >= 1) {
|
||||
$intensity = "normal"; // أصفر
|
||||
}
|
||||
|
||||
if ($score > 0) {
|
||||
$finalData[] = [
|
||||
'lat' => $cell['lat'],
|
||||
'lng' => $cell['lng'],
|
||||
'count' => $count, // ✅ العدد الحقيقي (مهم للعرض)
|
||||
'intensity' => $intensity, // ✅ التصنيف الذكي
|
||||
'surge' => $surge
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents('heatmap_live.json', json_encode($finalData)); // الكاش
|
||||
|
||||
echo json_encode(["status" => "success", "data" => $finalData]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["status" => "failure", "message" => $e->getMessage()]);
|
||||
}
|
||||
|
||||
function addToGrid(&$grid, $lat, $lng, $precision, $weight) {
|
||||
if (empty($lat) || empty($lng)) return;
|
||||
$rLat = round(floatval($lat), $precision);
|
||||
$rLng = round(floatval($lng), $precision);
|
||||
$key = "$rLat,$rLng";
|
||||
|
||||
if (!isset($grid[$key])) {
|
||||
$grid[$key] = ['lat' => $rLat, 'lng' => $rLng, 'count' => 0, 'score' => 0];
|
||||
}
|
||||
$grid[$key]['count']++; // زيادة العدد (+1 دائماً)
|
||||
$grid[$key]['score'] += $weight; // زيادة الوزن (حسب نوع الطلب)
|
||||
}
|
||||
?>
|
||||
158
backend/ride/rides/getRideOrderID.php
Executable file
158
backend/ride/rides/getRideOrderID.php
Executable file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
// نقوم بتضمين ملف الاتصال المعدل الذي يحتوي على $con (الرئيسي)
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// تهيئة اتصال قاعدة بيانات الرحلات
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
} catch (Exception $e) {
|
||||
error_log("[getRideOrderID] Failed to connect to Ride Database: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(["status" => "failure", "message" => "Database connection failed"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// استلام البيانات (يمكن استلام ID الرحلة أو ID الراكب)
|
||||
$passengerID = filterRequest("passengerID");
|
||||
$rideID = filterRequest("id"); // إضافة استقبال متغير رقم الرحلة
|
||||
|
||||
try {
|
||||
// =================================================================
|
||||
// 1. الخطوة الأولى: تحديد استراتيجية البحث (بواسطة رقم الرحلة أو الراكب)
|
||||
// =================================================================
|
||||
|
||||
$sqlRide = "SELECT
|
||||
id,
|
||||
start_location,
|
||||
end_location,
|
||||
date,
|
||||
driver_id,
|
||||
passenger_id,
|
||||
price,
|
||||
status,
|
||||
created_at,
|
||||
DriverIsGoingToPassenger,
|
||||
rideTimeStart,
|
||||
rideTimeFinish,
|
||||
price_for_driver,
|
||||
distance
|
||||
FROM ride ";
|
||||
|
||||
// المنطق الجديد:
|
||||
// إذا تم إرسال rideID، نبحث عن الرحلة المحددة بدقة (تجنباً لأي تضارب)
|
||||
// إذا لم يتم إرساله، نبحث عن أحدث رحلة للراكب (للتتبع المباشر)
|
||||
if (!empty($rideID)) {
|
||||
$sqlRide .= "WHERE id = :rideID";
|
||||
} else {
|
||||
$sqlRide .= "WHERE passenger_id = :passengerID ORDER BY id DESC LIMIT 1";
|
||||
}
|
||||
|
||||
// نستخدم المتغير $con_ride (سيرفر الرحلات)
|
||||
$stmtRide = $con_ride->prepare($sqlRide);
|
||||
|
||||
// ربط المتغيرات حسب نوع البحث
|
||||
if (!empty($rideID)) {
|
||||
$stmtRide->bindParam(':rideID', $rideID);
|
||||
} else {
|
||||
$stmtRide->bindParam(':passengerID', $passengerID);
|
||||
}
|
||||
|
||||
$stmtRide->execute();
|
||||
|
||||
$rideData = $stmtRide->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// إذا لم يتم العثور على رحلة في سيرفر الرحلات، نوقف العملية
|
||||
if (!$rideData) {
|
||||
echo json_encode(["status" => "failure", "message" => "No ride found"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 2. الخطوة الثانية: جلب البيانات الثابتة (سائق، سيارة، تقييم) من السيرفر الرئيسي ($con)
|
||||
// نستخدم المعرفات التي حصلنا عليها من نتيجة الاستعلام الأول
|
||||
// =================================================================
|
||||
|
||||
$driverID = $rideData['driver_id'];
|
||||
$pID = $rideData['passenger_id']; // نأخذ معرف الراكب من الرحلة نفسها لضمان التطابق
|
||||
|
||||
// ملاحظة: استخدام :driverID_Sub في الاستعلام الفرعي لتجنب أخطاء PDO
|
||||
$sqlDetails = "SELECT
|
||||
passengers.first_name AS passengerName,
|
||||
passengers.last_name,
|
||||
|
||||
CarRegistration.make,
|
||||
CarRegistration.model,
|
||||
CarRegistration.car_plate,
|
||||
CarRegistration.year,
|
||||
CarRegistration.color,
|
||||
CarRegistration.color_hex,
|
||||
|
||||
driver.first_name AS driverName,
|
||||
driver.gender,
|
||||
driver.phone,
|
||||
|
||||
(
|
||||
SELECT ROUND(AVG(ratingDriver.rating), 2)
|
||||
FROM ratingDriver
|
||||
WHERE ratingDriver.driver_id = :driverID_Sub
|
||||
) AS ratingDriver,
|
||||
|
||||
driverToken.token AS token
|
||||
|
||||
FROM driver
|
||||
LEFT JOIN passengers ON passengers.id = :passengerID
|
||||
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
|
||||
LEFT JOIN driverToken ON driverToken.captain_id = driver.id
|
||||
WHERE driver.id = :driverID";
|
||||
|
||||
// نستخدم المتغير الأصلي $con للسيرفر الرئيسي
|
||||
$stmtDetails = $con->prepare($sqlDetails);
|
||||
|
||||
// نربط المتغيرات
|
||||
$stmtDetails->bindParam(':driverID', $driverID);
|
||||
$stmtDetails->bindParam(':driverID_Sub', $driverID);
|
||||
$stmtDetails->bindParam(':passengerID', $pID);
|
||||
|
||||
$stmtDetails->execute();
|
||||
|
||||
$detailsData = $stmtDetails->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// =================================================================
|
||||
// 3. الخطوة الثالثة: دمج البيانات وتجهيز الرد
|
||||
// =================================================================
|
||||
|
||||
$finalData = [];
|
||||
|
||||
if ($detailsData) {
|
||||
// دمج مصفوفة الرحلة (من سيرفر الرحلات) مع مصفوفة التفاصيل (من الرئيسي)
|
||||
$finalData = array_merge($rideData, $detailsData);
|
||||
} else {
|
||||
// في حال كانت الرحلة بدون سائق بعد، نكتفي ببيانات الرحلة
|
||||
$finalData = $rideData;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 4. فك التشفير (Decrypt)
|
||||
// =================================================================
|
||||
|
||||
if ($finalData) {
|
||||
$fieldsToDecrypt = ['driverName', 'gender', 'phone', 'car_plate', 'passengerName', 'last_name', 'token'];
|
||||
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (!empty($finalData[$field])) {
|
||||
$finalData[$field] = $encryptionHelper->decryptData($finalData[$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"data" => $finalData
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("API Error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(["status" => "failure", "message" => "Server Error: " . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
148
backend/ride/rides/getRideOrderIDNew.php
Executable file
148
backend/ride/rides/getRideOrderIDNew.php
Executable file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
// نقوم بتضمين ملف الاتصال المعدل الذي يحتوي على $con (الرئيسي) و $con_ride (الرحلات)
|
||||
require_once __DIR__ . '/../../get_connect.php';
|
||||
|
||||
// استلام البيانات (يمكن استلام ID الرحلة أو ID الراكب)
|
||||
$passengerID = filterRequest("passengerID");
|
||||
$rideID = filterRequest("id"); // إضافة استقبال متغير رقم الرحلة
|
||||
|
||||
try {
|
||||
// =================================================================
|
||||
// 1. الخطوة الأولى: تحديد استراتيجية البحث (بواسطة رقم الرحلة أو الراكب)
|
||||
// =================================================================
|
||||
|
||||
$sqlRide = "SELECT
|
||||
id,
|
||||
start_location,
|
||||
end_location,
|
||||
date,
|
||||
driver_id,
|
||||
passenger_id,
|
||||
price,
|
||||
status,
|
||||
created_at,
|
||||
DriverIsGoingToPassenger,
|
||||
rideTimeStart,
|
||||
rideTimeFinish,
|
||||
price_for_driver,
|
||||
distance
|
||||
FROM ride ";
|
||||
|
||||
// المنطق الجديد:
|
||||
// إذا تم إرسال rideID، نبحث عن الرحلة المحددة بدقة (تجنباً لأي تضارب)
|
||||
// إذا لم يتم إرساله، نبحث عن أحدث رحلة للراكب (للتتبع المباشر)
|
||||
if (!empty($rideID)) {
|
||||
$sqlRide .= "WHERE id = :rideID";
|
||||
} else {
|
||||
$sqlRide .= "WHERE passenger_id = :passengerID ORDER BY id DESC LIMIT 1";
|
||||
}
|
||||
|
||||
// نستخدم المتغير $con_ride (سيرفر الرحلات)
|
||||
$stmtRide = $con_ride->prepare($sqlRide);
|
||||
|
||||
// ربط المتغيرات حسب نوع البحث
|
||||
if (!empty($rideID)) {
|
||||
$stmtRide->bindParam(':rideID', $rideID);
|
||||
} else {
|
||||
$stmtRide->bindParam(':passengerID', $passengerID);
|
||||
}
|
||||
|
||||
$stmtRide->execute();
|
||||
|
||||
$rideData = $stmtRide->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// إذا لم يتم العثور على رحلة في سيرفر الرحلات، نوقف العملية
|
||||
if (!$rideData) {
|
||||
echo json_encode(["status" => "failure", "message" => "No ride found"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 2. الخطوة الثانية: جلب البيانات الثابتة (سائق، سيارة، تقييم) من السيرفر الرئيسي ($con)
|
||||
// نستخدم المعرفات التي حصلنا عليها من نتيجة الاستعلام الأول
|
||||
// =================================================================
|
||||
|
||||
$driverID = $rideData['driver_id'];
|
||||
$pID = $rideData['passenger_id']; // نأخذ معرف الراكب من الرحلة نفسها لضمان التطابق
|
||||
|
||||
// ملاحظة: استخدام :driverID_Sub في الاستعلام الفرعي لتجنب أخطاء PDO
|
||||
$sqlDetails = "SELECT
|
||||
passengers.first_name AS passengerName,
|
||||
passengers.last_name,
|
||||
|
||||
CarRegistration.make,
|
||||
CarRegistration.model,
|
||||
CarRegistration.car_plate,
|
||||
CarRegistration.year,
|
||||
CarRegistration.color,
|
||||
CarRegistration.color_hex,
|
||||
|
||||
driver.first_name AS driverName,
|
||||
driver.gender,
|
||||
driver.phone,
|
||||
|
||||
(
|
||||
SELECT ROUND(AVG(ratingDriver.rating), 2)
|
||||
FROM ratingDriver
|
||||
WHERE ratingDriver.driver_id = :driverID_Sub
|
||||
) AS ratingDriver,
|
||||
|
||||
driverToken.token AS token
|
||||
|
||||
FROM driver
|
||||
LEFT JOIN passengers ON passengers.id = :passengerID
|
||||
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
|
||||
LEFT JOIN driverToken ON driverToken.captain_id = driver.id
|
||||
WHERE driver.id = :driverID";
|
||||
|
||||
// نستخدم المتغير الأصلي $con للسيرفر الرئيسي
|
||||
$stmtDetails = $con->prepare($sqlDetails);
|
||||
|
||||
// نربط المتغيرات
|
||||
$stmtDetails->bindParam(':driverID', $driverID);
|
||||
$stmtDetails->bindParam(':driverID_Sub', $driverID);
|
||||
$stmtDetails->bindParam(':passengerID', $pID);
|
||||
|
||||
$stmtDetails->execute();
|
||||
|
||||
$detailsData = $stmtDetails->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// =================================================================
|
||||
// 3. الخطوة الثالثة: دمج البيانات وتجهيز الرد
|
||||
// =================================================================
|
||||
|
||||
$finalData = [];
|
||||
|
||||
if ($detailsData) {
|
||||
// دمج مصفوفة الرحلة (من سيرفر الرحلات) مع مصفوفة التفاصيل (من الرئيسي)
|
||||
$finalData = array_merge($rideData, $detailsData);
|
||||
} else {
|
||||
// في حال كانت الرحلة بدون سائق بعد، نكتفي ببيانات الرحلة
|
||||
$finalData = $rideData;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 4. فك التشفير (Decrypt)
|
||||
// =================================================================
|
||||
|
||||
if ($finalData) {
|
||||
$fieldsToDecrypt = ['driverName', 'gender', 'phone', 'car_plate', 'passengerName', 'last_name', 'token'];
|
||||
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (!empty($finalData[$field])) {
|
||||
$finalData[$field] = $encryptionHelper->decryptData($finalData[$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"data" => $finalData
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("API Error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(["status" => "failure", "message" => "Server Error: " . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
26
backend/ride/rides/getRideStatus.php
Normal file
26
backend/ride/rides/getRideStatus.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$id = filterRequest("id");
|
||||
|
||||
if (empty($id)) {
|
||||
jsonError("Missing ride ID.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "SELECT `status` FROM `ride` WHERE `id` = :id";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row && isset($row['status'])) {
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"data" => $row['status']
|
||||
]);
|
||||
} else {
|
||||
jsonError("Ride not found.");
|
||||
}
|
||||
?>
|
||||
27
backend/ride/rides/getRideStatusBegin.php
Normal file
27
backend/ride/rides/getRideStatusBegin.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
//getRideStatusBegin.php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$ride_id = filterRequest("ride_id");
|
||||
|
||||
if (empty($ride_id)) {
|
||||
jsonError("Ride ID is missing.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "SELECT `status`, `DriverIsGoingToPassenger`, `rideTimeStart` FROM `ride` WHERE `id` = :ride_id";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':ride_id', $ride_id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"data" => $row
|
||||
]);
|
||||
} else {
|
||||
jsonError("Ride not found.");
|
||||
}
|
||||
?>
|
||||
93
backend/ride/rides/getRideStatusFromStartApp.php
Normal file
93
backend/ride/rides/getRideStatusFromStartApp.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
// =========================================================
|
||||
// 1. سيرفر الرحلات: جلب بيانات الرحلة
|
||||
// =========================================================
|
||||
$stmt = $con_ride->prepare("
|
||||
SELECT
|
||||
id AS rideId,
|
||||
status,
|
||||
start_location,
|
||||
end_location,
|
||||
carType,
|
||||
driver_id,distance,
|
||||
price,
|
||||
created_at
|
||||
FROM ride
|
||||
WHERE passenger_id = ?
|
||||
AND (
|
||||
status IN ('Apply', 'Applied', 'accepted', 'arrived', 'Arrived', 'Begin') AND created_at >= NOW() - INTERVAL 24 HOUR
|
||||
OR (status = 'Finished' AND created_at >= NOW() - INTERVAL 24 HOUR)
|
||||
)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
$stmt->execute([$passenger_id]);
|
||||
$ride = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$ride) {
|
||||
echo json_encode(["status" => "failure", "message" => "No active ride found"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// 2. السيرفر الرئيسي: جلب اسم السائق + متوسط تقييمه العام
|
||||
// =========================================================
|
||||
|
||||
// ملاحظة: تم الحفاظ على الاستعلام كما هو
|
||||
// rateDriver: هو الاسم الذي سنستخدمه في PHP
|
||||
$stmt2 = $con->prepare("
|
||||
SELECT
|
||||
d.first_name AS driverName,
|
||||
(SELECT AVG(rating) FROM ratingDriver WHERE driver_id = d.id) AS rateDriver,
|
||||
(SELECT COUNT(*) FROM ratingDriver WHERE ride_id = ?) AS thisRideRated
|
||||
FROM driver d
|
||||
WHERE d.id = ?
|
||||
");
|
||||
|
||||
$stmt2->execute([$ride['rideId'], $ride['driver_id']]);
|
||||
$driverData = $stmt2->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// =========================================================
|
||||
// 3. المعالجة النهائية
|
||||
// =========================================================
|
||||
|
||||
if ($driverData) {
|
||||
// فك التشفير
|
||||
$ride['driverName'] = $encryptionHelper->decryptData($driverData['driverName']);
|
||||
|
||||
// --- تصحيح الخطأ هنا ---
|
||||
// كان يستدعي driverAvg وهو غير موجود، تم تغييره لـ rateDriver
|
||||
$ride['rateDriver'] = $driverData['rateDriver'] ? round($driverData['rateDriver'], 2) : 5;
|
||||
|
||||
// --- منطق هل تحتاج الرحلة لتقييم (needsReview) ---
|
||||
$isFinished = ($ride['status'] === 'Finished');
|
||||
$isRated = ($driverData['thisRideRated'] > 0);
|
||||
|
||||
$ride['needsReview'] = ($isFinished && !$isRated) ? 1 : 0;
|
||||
|
||||
} else {
|
||||
// حالة عدم وجود سائق (نادراً ما تحدث إذا كان driver_id موجوداً في جدول الرحلات)
|
||||
$ride['driverName'] = null;
|
||||
$ride['rateDriver'] = 5;
|
||||
$ride['needsReview'] = 0;
|
||||
}
|
||||
|
||||
// تنظيف البيانات
|
||||
unset($ride['created_at']);
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"data" => $ride
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["status" => "failure", "message" => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
23
backend/ride/rides/getTripCountByCaptain.php
Normal file
23
backend/ride/rides/getTripCountByCaptain.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$driver_id = filterRequest("driver_id");
|
||||
|
||||
$sql = "
|
||||
SELECT COUNT(*) as count
|
||||
FROM `ride`
|
||||
WHERE driver_id = :driver_id AND status = 'Finished'
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':driver_id', $driver_id, PDO::PARAM_INT); // أو PARAM_STR حسب نوع الـ ID
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
jsonSuccess($row);
|
||||
} else {
|
||||
jsonError($message = "No finished ride records found for this driver");
|
||||
}
|
||||
?>
|
||||
154
backend/ride/rides/get_driver_location.php
Executable file
154
backend/ride/rides/get_driver_location.php
Executable file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
// تضمين ملف الاتصال الذي يحتوي على تعريف السيرفرات الثلاثة ($con, $con_ride, $con_tracking)
|
||||
// وكائن التشفير $encryptionHelper
|
||||
require_once __DIR__ . '/../../get_connect.php';
|
||||
|
||||
// السماح بالوصول من أي دومين (لأن الرابط سيفتح في متصفح العميل)
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
$rideID = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
// تنظيف الـ Token: نتأكد من تعقيم الرموز الخاصة لمنع XSS
|
||||
$token = filter_input(INPUT_GET, 'token', FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
|
||||
// التحقق من وصول البيانات
|
||||
if (!$rideID || !$token) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["status" => "failure", "message" => "Missing Parameters"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// =================================================================
|
||||
// الخطوة 1: الاتصال بسيرفر الرحلات ($con_ride)
|
||||
// الهدف: جلب driver_id وحالة الرحلة للتحقق
|
||||
// =================================================================
|
||||
|
||||
$sqlRide = "SELECT driver_id, status FROM ride WHERE id = :rideID LIMIT 1";
|
||||
$stmtRide = $con_ride->prepare($sqlRide);
|
||||
$stmtRide->bindParam(':rideID', $rideID);
|
||||
$stmtRide->execute();
|
||||
$rideData = $stmtRide->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// إذا لم توجد الرحلة
|
||||
if (!$rideData) {
|
||||
echo json_encode(["status" => "failure", "message" => "Ride not found"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$driverID = $rideData['driver_id'];
|
||||
$status = $rideData['status'];
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: التحقق الأمني (Hashing Validation)
|
||||
// القاعدة: Token = MD5(rideID + driverID + SecretSalt)
|
||||
// هذا يضمن أن الرابط تم توليده بواسطة التطبيق ولم يتم تخمينه
|
||||
// =================================================================
|
||||
|
||||
// * هام: هذه الكلمة السرية يجب أن تكون مطابقة تماماً للموجودة في تطبيق Flutter
|
||||
$secretSalt = getenv("secretSaltParent");
|
||||
|
||||
// إعادة بناء الهاش للمقارنة
|
||||
$generatedToken = md5($rideID . $driverID . $secretSalt);
|
||||
|
||||
if ($token !== $generatedToken) {
|
||||
http_response_code(403);
|
||||
echo json_encode(["status" => "failure", "message" => "Invalid Security Token"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: التحقق من حالة الرحلة (Logic Check)
|
||||
// الشرط: التتبع يعمل فقط إذا كانت الرحلة قد بدأت
|
||||
// =================================================================
|
||||
|
||||
// يمكنك إضافة 'Applied' أو 'Arrived' إذا أردت التتبع قبل الركوب
|
||||
$allowedStatuses = ['Begin', 'inProgress'];
|
||||
|
||||
if (!in_array($status, $allowedStatuses)) {
|
||||
echo json_encode(["status" => "failure", "message" => "Ride is not active", "ride_status" => $status]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 4: الاتصال بسيرفر التتبع ($con_tracking)
|
||||
// الهدف: جلب أحدث إحداثيات للسائق
|
||||
// =================================================================
|
||||
|
||||
$sqlLoc = "SELECT latitude, longitude, heading, speed, updated_at
|
||||
FROM car_locations
|
||||
WHERE driver_id = :driverID
|
||||
ORDER BY updated_at DESC LIMIT 1";
|
||||
|
||||
$stmtLoc = $con_tracking->prepare($sqlLoc);
|
||||
$stmtLoc->bindParam(':driverID', $driverID);
|
||||
$stmtLoc->execute();
|
||||
$locData = $stmtLoc->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locData) {
|
||||
// السائق لم يرسل موقعه بعد
|
||||
echo json_encode(["status" => "failure", "message" => "Waiting for driver signal..."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 5: الاتصال بالسيرفر الرئيسي ($con)
|
||||
// الهدف: جلب اسم السائق وموديل السيارة للعرض (اختياري لجمالية الصفحة)
|
||||
// =================================================================
|
||||
|
||||
$sqlDriver = "SELECT
|
||||
d.first_name,
|
||||
d.last_name,
|
||||
c.model,
|
||||
c.color,
|
||||
c.car_plate
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration c ON d.id = c.driverID
|
||||
WHERE d.id = :driverID LIMIT 1";
|
||||
|
||||
$stmtDriver = $con->prepare($sqlDriver);
|
||||
$stmtDriver->bindParam(':driverID', $driverID);
|
||||
$stmtDriver->execute();
|
||||
$driverInfo = $stmtDriver->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// فك التشفير إذا لزم الأمر (أسماء السائقين واللوحات غالباً مشفرة)
|
||||
if ($driverInfo) {
|
||||
// فك تشفير الاسم
|
||||
if (!empty($driverInfo['first_name'])) {
|
||||
$driverInfo['first_name'] = $encryptionHelper->decryptData($driverInfo['first_name']);
|
||||
}
|
||||
// فك تشفير اللوحة
|
||||
if (!empty($driverInfo['car_plate'])) {
|
||||
$driverInfo['car_plate'] = $encryptionHelper->decryptData($driverInfo['car_plate']);
|
||||
}
|
||||
// يمكنك فك تشفير باقي الحقول حسب الحاجة
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 6: تجميع البيانات وإرسال الرد النهائي
|
||||
// =================================================================
|
||||
|
||||
$response = [
|
||||
"status" => "success",
|
||||
"data" => [
|
||||
"lat" => $locData['latitude'],
|
||||
"lng" => $locData['longitude'],
|
||||
"heading" => $locData['heading'],
|
||||
"speed" => $locData['speed'],
|
||||
"last_update" => $locData['updated_at'],
|
||||
"driver_name" => $driverInfo['first_name'] ?? "Captain",
|
||||
"car_model" => $driverInfo['model'] ?? "",
|
||||
"car_color" => $driverInfo['color'] ?? "",
|
||||
"plate" => $driverInfo['car_plate'] ?? ""
|
||||
]
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// تسجيل الخطأ دون إظهاره للمستخدم العام
|
||||
error_log("Tracking Error: " . $e->getMessage());
|
||||
echo json_encode(["status" => "failure", "message" => "Server Error"]);
|
||||
}
|
||||
?>
|
||||
32
backend/ride/rides/gterideForDriverManyTime.php
Normal file
32
backend/ride/rides/gterideForDriverManyTime.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// لا حاجة للمتغير id لأنه غير مستخدم فعليًا
|
||||
// $id = filterRequest("id");
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM `ride`
|
||||
WHERE `status` = 'Finished'
|
||||
AND `created_at` BETWEEN CURRENT_DATE() + INTERVAL 7 HOUR AND CURRENT_DATE() + INTERVAL 10 HOUR
|
||||
) AS morning_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM `ride`
|
||||
WHERE `status` = 'Finished'
|
||||
AND `created_at` BETWEEN CURRENT_DATE() + INTERVAL 15 HOUR AND CURRENT_DATE() + INTERVAL 18 HOUR
|
||||
) AS afternoon_count
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"data" => $row
|
||||
]);
|
||||
?>
|
||||
7
backend/ride/rides/heatmap_live.json
Normal file
7
backend/ride/rides/heatmap_live.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{"lat": 33.5100, "lng": 36.2700, "count": 5, "intensity": "high", "surge": 1.5},
|
||||
{"lat": 33.5200, "lng": 36.2800, "count": 3, "intensity": "medium", "surge": 1.2},
|
||||
{"lat": 33.5150, "lng": 36.2750, "count": 2, "intensity": "normal", "surge": 1.0},
|
||||
{"lat": 33.5050, "lng": 36.2650, "count": 6, "intensity": "high", "surge": 1.5},
|
||||
{"lat": 33.5250, "lng": 36.2850, "count": 1, "intensity": "low", "surge": 1.0}
|
||||
]
|
||||
99
backend/ride/rides/public_track_location.php
Executable file
99
backend/ride/rides/public_track_location.php
Executable file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// ابدأ التخزين المؤقت فوراً
|
||||
ob_start();
|
||||
|
||||
require_once __DIR__ . '/../../get_connect.php';
|
||||
|
||||
// تنظيف *جميع* مستويات التخزين المؤقت (Loop)
|
||||
// هذا يضمن التخلص من أي مسافات أو أخطاء ظهرت من ملفات الـ include
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// ابدأ مخزناً جديداً ونظيفاً لهذا الملف فقط
|
||||
ob_start();
|
||||
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Access-Control-Allow-Methods: GET");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
function sendError($message, $code = 400, $extra = []) {
|
||||
// تنظيف ما قبل الخطأ
|
||||
ob_clean();
|
||||
http_response_code($code);
|
||||
echo json_encode(array_merge(["status" => "failure", "message" => $message], $extra));
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$rideID = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
|
||||
$token = filter_input(INPUT_GET, 'token', FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
|
||||
if (!$rideID || !$token) {
|
||||
sendError("Missing parameters");
|
||||
}
|
||||
|
||||
$stmtRide = $con_ride->prepare("SELECT driver_id, status FROM ride WHERE id = ? LIMIT 1");
|
||||
$stmtRide->execute([$rideID]);
|
||||
$rideData = $stmtRide->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$rideData) sendError("Ride not found");
|
||||
|
||||
$driverID = $rideData['driver_id'];
|
||||
$status = $rideData['status'];
|
||||
$secretSalt = "Intaleq_Secure_Track_2025";
|
||||
$generatedToken = md5(trim(strval($rideID)) . trim(strval($driverID)) . $secretSalt);
|
||||
|
||||
if ($token !== $generatedToken) sendError("Invalid Token");
|
||||
|
||||
$allowedStatuses = ['Applied', 'Arrived', 'Begin', 'inProgress'];
|
||||
if (!in_array($status, $allowedStatuses)) {
|
||||
sendError("Ride not active", 200, ["current_status" => $status]);
|
||||
}
|
||||
|
||||
$stmtLoc = $con_tracking->prepare("SELECT latitude, longitude, heading, speed, updated_at FROM car_locations WHERE driver_id = ? ORDER BY updated_at DESC LIMIT 1");
|
||||
$stmtLoc->execute([$driverID]);
|
||||
$locData = $stmtLoc->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locData) sendError("Waiting for driver signal...", 200);
|
||||
|
||||
$stmtDriver = $con->prepare("SELECT d.first_name, c.model, c.color, c.car_plate FROM driver d LEFT JOIN CarRegistration c ON d.id = c.driverID WHERE d.id = ? LIMIT 1");
|
||||
$stmtDriver->execute([$driverID]);
|
||||
$driverInfo = $stmtDriver->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$driverName = "Captain";
|
||||
$carModel = "Car";
|
||||
$carColor = "";
|
||||
$plate = "";
|
||||
|
||||
if ($driverInfo) {
|
||||
if (!empty($driverInfo['first_name'])) $driverName = $encryptionHelper->decryptData($driverInfo['first_name']);
|
||||
if (!empty($driverInfo['model'])) $carModel = $driverInfo['model'];
|
||||
if (!empty($driverInfo['color'])) $carColor = $driverInfo['color'];
|
||||
if (!empty($driverInfo['car_plate'])) $plate = $encryptionHelper->decryptData($driverInfo['car_plate']);
|
||||
}
|
||||
|
||||
$response = [
|
||||
"status" => "success",
|
||||
"data" => [
|
||||
"lat" => $locData['latitude'],
|
||||
"lng" => $locData['longitude'],
|
||||
"heading" => $locData['heading'],
|
||||
"speed" => $locData['speed'],
|
||||
"last_update" => $locData['updated_at'],
|
||||
"driver_name" => $driverName,
|
||||
"car_model" => $carModel,
|
||||
"car_color" => $carColor,
|
||||
"plate" => $plate,
|
||||
"ride_status" => $status
|
||||
]
|
||||
];
|
||||
|
||||
// التنظيف النهائي قبل الطباعة
|
||||
ob_clean();
|
||||
echo json_encode($response);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Tracking API Error: " . $e->getMessage());
|
||||
sendError("Server Error");
|
||||
}
|
||||
107
backend/ride/rides/retry_search_drivers.php
Executable file
107
backend/ride/rides/retry_search_drivers.php
Executable file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
// retry_search_drivers.php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// 1. استقبال البيانات القادمة من الفلتر (لتوفير الاستعلامات)
|
||||
$rideId = filterRequest("ride_id");
|
||||
$passengerId = filterRequest("passenger_id");
|
||||
$passengerName = filterRequest("passenger_name");
|
||||
$passengerPhone = filterRequest("passenger_phone");
|
||||
$passengerEmail = filterRequest("passenger_email");
|
||||
$passengerToken = filterRequest("passenger_token");
|
||||
$passengerWallet = filterRequest("passenger_wallet"); // الرصيد
|
||||
$isWallet = filterRequest("is_wallet"); // هل الدفع بالمحفظة؟ (true/false)
|
||||
$passengerRating = filterRequest("passenger_rating");
|
||||
|
||||
// بيانات الموقع والرحلة (يفضل إرسالها أيضاً لضمان الدقة)
|
||||
$startLat = filterRequest("start_lat");
|
||||
$startLng = filterRequest("start_lng");
|
||||
$endLat = filterRequest("end_lat");
|
||||
$endLng = filterRequest("end_lng");
|
||||
$startName = filterRequest("start_name");
|
||||
$endName = filterRequest("end_name");
|
||||
$distance = filterRequest("distance");
|
||||
$distanceText = filterRequest("distance_text");
|
||||
$durationText = filterRequest("duration_text");
|
||||
$price = filterRequest("price");
|
||||
$priceForDriver = filterRequest("price_for_driver");
|
||||
$carType = filterRequest("car_type");
|
||||
|
||||
// بيانات الخطوات (إن وجدت)
|
||||
$hasSteps = filterRequest("has_steps");
|
||||
$step0 = filterRequest("step0");
|
||||
$step1 = filterRequest("step1");
|
||||
$step2 = filterRequest("step2");
|
||||
$step3 = filterRequest("step3");
|
||||
$step4 = filterRequest("step4");
|
||||
|
||||
if (!$rideId) {
|
||||
jsonError("Missing Ride ID");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. تحديث حالة الرحلة في قاعدة البيانات (Reset)
|
||||
$updateStmt = $con->prepare("UPDATE ride SET status = 'waiting', driver_id = 0, updated_at = NOW() WHERE id = ?");
|
||||
$updateStmt->execute([$rideId]);
|
||||
|
||||
// 3. حساب العمولة (Kazan)
|
||||
$kazan = (double)$price - (double)$priceForDriver;
|
||||
|
||||
// 4. بناء Payload مطابق لـ add_ride.php (0 - 33)
|
||||
$payloadTemplate = [];
|
||||
$payloadTemplate[0] = (string)$startLat;
|
||||
$payloadTemplate[1] = (string)$startLng;
|
||||
$payloadTemplate[2] = (string)number_format((float)$price, 2, '.', '');
|
||||
$payloadTemplate[3] = (string)$endLat;
|
||||
$payloadTemplate[4] = (string)$endLng;
|
||||
$payloadTemplate[5] = (string)$distanceText;
|
||||
$payloadTemplate[6] = ""; // Driver ID placeholder
|
||||
$payloadTemplate[7] = (string)$passengerId;
|
||||
$payloadTemplate[8] = (string)$passengerName;
|
||||
$payloadTemplate[9] = (string)$passengerToken;
|
||||
$payloadTemplate[10] = (string)$passengerPhone;
|
||||
$payloadTemplate[11] = (string)$distance;
|
||||
$payloadTemplate[12] = "1";
|
||||
$payloadTemplate[13] = (string)$isWallet;
|
||||
$payloadTemplate[14] = (string)$distance;
|
||||
$payloadTemplate[15] = (string)$durationText;
|
||||
$payloadTemplate[16] = (string)$rideId;
|
||||
$payloadTemplate[17] = "";
|
||||
$payloadTemplate[18] = ""; // Driver ID placeholder
|
||||
$payloadTemplate[19] = (string)$durationText;
|
||||
$payloadTemplate[20] = (string)$hasSteps;
|
||||
$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((float)$priceForDriver, 2, '.', '');
|
||||
$payloadTemplate[27] = (string)$passengerWallet;
|
||||
$payloadTemplate[28] = (string)$passengerEmail;
|
||||
$payloadTemplate[29] = (string)$startName;
|
||||
$payloadTemplate[30] = (string)$endName;
|
||||
$payloadTemplate[31] = (string)$carType;
|
||||
$payloadTemplate[32] = (string)number_format($kazan, 2, '.', '');
|
||||
$payloadTemplate[33] = (string)$passengerRating;
|
||||
|
||||
ksort($payloadTemplate);
|
||||
$payloadTemplate = array_values($payloadTemplate);
|
||||
|
||||
// 5. البحث عن السائقين وإرسال الطلب (Using Helper Function)
|
||||
$latVal = doubleval($startLat);
|
||||
$lngVal = doubleval($startLng);
|
||||
|
||||
$driversData = findBestDrivers($con, $con_tracking, $latVal, $lngVal, $carType);
|
||||
|
||||
if (!empty($driversData)) {
|
||||
// استدعاء دالة الإرسال الموحدة (الموجودة في functions.php)
|
||||
dispatchRideToDrivers($driversData, $rideId, $payloadTemplate, $startName);
|
||||
}
|
||||
|
||||
jsonSuccess(null, "Ride reset and resent to drivers");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("DB Error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
104
backend/ride/rides/start_ride.php
Executable file
104
backend/ride/rides/start_ride.php
Executable file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// start_ride.php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
} catch (Exception $e) {
|
||||
error_log("[start_ride] Failed to connect to Ride Database: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$ride_id = filterRequest("id");
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$status = filterRequest("status"); // 'Begin'
|
||||
$passengerToken = filterRequest("passengerToken");
|
||||
|
||||
if (!$ride_id || !$driver_id || !$status) {
|
||||
jsonError("Missing parameters");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. تحديث سيرفر الرحلات (Remote DB - con_ride)
|
||||
$stmtRemote = $con_ride->prepare("UPDATE ride SET status = ?, rideTimeStart = NOW() WHERE id = ?");
|
||||
$stmtRemote->execute([$status, $ride_id]);
|
||||
|
||||
if ($stmtRemote->rowCount() == 0) {
|
||||
// ملاحظة: أحياناً التحديث لا يؤثر بصفوف إذا كانت البيانات نفسها،
|
||||
// لكن هنا نفترض الفشل إذا لم يجد الرحلة.
|
||||
// يمكنك إكمال التنفيذ إذا كنت متأكداً أن الرحلة موجودة.
|
||||
}
|
||||
|
||||
// 2. تحديث السيرفر المحلي (Local DB) والمعاملات
|
||||
$con->beginTransaction();
|
||||
|
||||
// تحديث الرحلة محلياً
|
||||
$stmtMainRide = $con->prepare("UPDATE ride SET status = ?, rideTimeStart = NOW() WHERE id = ?");
|
||||
$stmtMainRide->execute([$status, $ride_id]);
|
||||
|
||||
// تحديث أو إدخال في جدول Driver Orders
|
||||
$checkSql = "SELECT `order_id` FROM `driver_orders` WHERE `order_id` = ?";
|
||||
$checkStmt = $con->prepare($checkSql);
|
||||
$checkStmt->execute([$ride_id]);
|
||||
|
||||
if ($checkStmt->rowCount() > 0) {
|
||||
$updateSql = "UPDATE `driver_orders` SET `driver_id` = ?, `status` = ?, `created_at` = NOW() WHERE `order_id` = ?";
|
||||
$con->prepare($updateSql)->execute([$driver_id, $status, $ride_id]);
|
||||
} else {
|
||||
$insertSql = "INSERT INTO `driver_orders` (`driver_id`, `order_id`, `created_at`, `status`) VALUES (?, ?, NOW(), ?)";
|
||||
$con->prepare($insertSql)->execute([$driver_id, $ride_id, $status]);
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 🔥 الخطوة 3: إشعار الراكب (Socket + FCM)
|
||||
// =================================================================
|
||||
|
||||
// جلب بيانات الراكب من قاعدة البيانات لضمان الدقة
|
||||
$stmtPas = $con_ride->prepare("SELECT passenger_id FROM ride WHERE id = ?");
|
||||
$stmtPas->execute([$ride_id]);
|
||||
$passenger_id = $stmtPas->fetchColumn();
|
||||
|
||||
if ($passenger_id) {
|
||||
|
||||
// أ) إرسال السوكيت (Socket)
|
||||
// تم إلغاء التعليق عنه ليكون السيرفر هو المسؤول
|
||||
$socketPayload = [
|
||||
'ride_id' => $ride_id,
|
||||
'status' => 'started', // أو 'Begin' حسب ما يتوقعه التطبيق
|
||||
'msg' => 'بدأت الرحلة، نتمنى لك سلامة الوصول 🚀'
|
||||
];
|
||||
|
||||
if (function_exists('notifyPassengerOnRideServer')) {
|
||||
notifyPassengerOnRideServer($passenger_id, $socketPayload);
|
||||
}
|
||||
|
||||
// ب) إرسال FCM (Internal)
|
||||
if (!empty($passengerToken)) {
|
||||
$fcmData = [
|
||||
'category' => 'Trip is Begin',
|
||||
'ride_id' => (string)$ride_id,
|
||||
'status' => 'started'
|
||||
];
|
||||
|
||||
// 🔥 استخدام sendFCM_Internal كرسالة صامتة
|
||||
sendFCM_Internal(
|
||||
$passengerToken, // الهدف
|
||||
"", // تفريغ العنوان
|
||||
"", // تفريغ النص
|
||||
$fcmData, // البيانات
|
||||
"Trip is Begin", // التصنيف
|
||||
false // ليس Topic
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
jsonSuccess(null, "Ride started successfully");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
jsonError("Exception: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
40
backend/ride/rides/test_notification.php
Executable file
40
backend/ride/rides/test_notification.php
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// test_socket_dispatch.php
|
||||
|
||||
$socketUrl = "http://188.68.36.205:2021";
|
||||
$INTERNAL_KEY = trim(file_get_contents('/home/intaleq-api/.internal_socket_key'));
|
||||
|
||||
// جرّب Driver ID موجود عندك
|
||||
$driverId = 691;
|
||||
$rideId = 99999;
|
||||
|
||||
// payload تجريبي (بنفس شكل اللي عم تبعته بالـ add_ride)
|
||||
$payload = ["32.11153","36.0668","173.00","32.12207","36.06351","1.8064","","849a9faf3e68c1aeb708",
|
||||
"حمزه عايد","TOKEN","963992952235","1.8064","1","false","1.8064","3","692","","","3","false",
|
||||
"32.11153499923237,36.06680665165186","","","","","173.00","28.00","963992952235@intaleqapp.com",
|
||||
"وادي أكيدر","وادي أكيدر","Fixed Price","0.00","5.0"];
|
||||
|
||||
$postData = [
|
||||
'action' => 'dispatch_order',
|
||||
'drivers_ids' => json_encode([$driverId]),
|
||||
'ride_id' => $rideId,
|
||||
'payload' => $payload
|
||||
];
|
||||
|
||||
$ch = curl_init($socketUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $INTERNAL_KEY"]);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
die("Curl error: " . curl_error($ch));
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
echo "HTTP Code: $httpCode\n";
|
||||
echo "Response: $response\n";
|
||||
89
backend/ride/rides/update.php
Executable file
89
backend/ride/rides/update.php
Executable file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// 🚀 تسجيل بداية العملية
|
||||
error_log("🚀 [update.php] Request Started to update Ride Dynamic Data.");
|
||||
|
||||
$id = filterRequest("id");
|
||||
|
||||
if (!$id) {
|
||||
error_log("❌ [update.php] Missing ID.");
|
||||
jsonError("Missing ID");
|
||||
exit;
|
||||
}
|
||||
|
||||
$columnValues = [];
|
||||
$params = [':id' => $id];
|
||||
|
||||
// قائمة الحقول القابلة للتحديث
|
||||
$fields = [
|
||||
"start_location", "end_location", "date", "time", "endtime", "price",
|
||||
"passenger_id", "driver_id", "status", "created_at", "updated_at",
|
||||
"rideTimeStart", "rideTimeFinish", "price_for_driver", "driverGoToPassengerTime",
|
||||
"price_for_passenger", "distance"
|
||||
];
|
||||
|
||||
// بناء الاستعلام ديناميكياً باستخدام filterRequest
|
||||
foreach ($fields as $field) {
|
||||
// نتحقق من وجود المفتاح في الـ POST
|
||||
if (isset($_POST[$field])) {
|
||||
// نستخدم دالة الفلترة الخاصة بك
|
||||
$value = filterRequest($field);
|
||||
|
||||
$columnValues[] = "`$field` = :$field";
|
||||
$params[":$field"] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// إذا لم يتم إرسال أي حقول للتحديث
|
||||
if (empty($columnValues)) {
|
||||
error_log("⚠️ [update.php] No data provided in request to update.");
|
||||
jsonError("No data provided for update.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// تجميع جملة SQL
|
||||
$setClause = implode(", ", $columnValues);
|
||||
$sql = "UPDATE `ride` SET $setClause WHERE `id` = :id";
|
||||
|
||||
try {
|
||||
// ---------------------------------------------------------
|
||||
// 1. التحديث على سيرفر التتبع (Remote DB) - هو الأساس
|
||||
// ---------------------------------------------------------
|
||||
error_log("🔄 [update.php] Attempting to update REMOTE Tracking DB for Ride ID: $id");
|
||||
|
||||
$stmtRemote = $con_ride->prepare($sql);
|
||||
$stmtRemote->execute($params);
|
||||
|
||||
$count = $stmtRemote->rowCount();
|
||||
error_log("ℹ️ [update.php] Remote DB Rows Affected: $count");
|
||||
|
||||
// التحقق: هل نجح التحديث هناك؟
|
||||
if ($count > 0) {
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. التحديث على السيرفر المحلي (Local DB) للمطابقة
|
||||
// ---------------------------------------------------------
|
||||
error_log("🔄 [update.php] Remote success. Updating LOCAL Main DB...");
|
||||
|
||||
$stmtLocal = $con->prepare($sql);
|
||||
$stmtLocal->execute($params);
|
||||
|
||||
error_log("✅ [update.php] Update successful on both servers.");
|
||||
|
||||
// استخدام دالة النجاح الخاصة بك
|
||||
jsonSuccess(null, "Ride data updated successfully");
|
||||
|
||||
} else {
|
||||
// لم يتم التحديث (إما البيانات نفسها لم تتغير، أو المعرف غير موجود في السيرفر البعيد)
|
||||
error_log("⚠️ [update.php] Remote Update returned 0 rows (Data same or ID not found).");
|
||||
|
||||
// استخدام دالة الفشل (يمكنك تغيير الرسالة لتكون success إذا كنت لا تعتبر عدم تغيير البيانات خطأ)
|
||||
jsonError("No changes made (Remote DB affected 0 rows). Check ID or Data.");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("❌ [update.php] Database Error: " . $e->getMessage());
|
||||
jsonError("Database Error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
47
backend/ride/rides/updateRideAndCheckIfApplied.php
Normal file
47
backend/ride/rides/updateRideAndCheckIfApplied.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$rideId = filterRequest("id");
|
||||
$rideTimeStart = filterRequest("rideTimeStart");
|
||||
$driverId = filterRequest("driver_id");
|
||||
|
||||
// Step 1: تأكد أن الرحلة غير محجوزة مسبقًا
|
||||
$sqlCheck = "SELECT `status` FROM `ride` WHERE `id` = :rideId LIMIT 1";
|
||||
$stmtCheck = $con->prepare($sqlCheck);
|
||||
$stmtCheck->bindParam(":rideId", $rideId);
|
||||
$stmtCheck->execute();
|
||||
|
||||
$ride = $stmtCheck->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$ride) {
|
||||
jsonError("Ride not found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($ride['status'] === 'Apply') {
|
||||
jsonError("This ride is already applied by another driver.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Step 2: تحديث حالة الرحلة وربط السائق بها
|
||||
$sqlUpdate = "UPDATE `ride`
|
||||
SET `driver_id` = :driverId,
|
||||
`status` = 'Apply',
|
||||
`rideTimeStart` = :rideTimeStart
|
||||
WHERE `id` = :rideId";
|
||||
|
||||
$stmtUpdate = $con->prepare($sqlUpdate);
|
||||
$stmtUpdate->bindParam(":driverId", $driverId);
|
||||
$stmtUpdate->bindParam(":rideTimeStart", $rideTimeStart);
|
||||
$stmtUpdate->bindParam(":rideId", $rideId);
|
||||
|
||||
$stmtUpdate->execute();
|
||||
|
||||
if ($stmtUpdate->rowCount() > 0) {
|
||||
jsonSuccess(null, "Ride data updated successfully");
|
||||
// يمكنك هنا إرسال إشعار للسائقين الآخرين إذا أردت
|
||||
// FirebaseMessagesController()->sendNotificationToOtherDrivers(...)
|
||||
} else {
|
||||
jsonError("Failed to update ride data.");
|
||||
}
|
||||
?>
|
||||
79
backend/ride/rides/updateStausFromSpeed.php
Executable file
79
backend/ride/rides/updateStausFromSpeed.php
Executable file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
//updateStausFromSpeed.php";
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$rideId = filterRequest("id");
|
||||
$status = filterRequest("status");
|
||||
$driverId = filterRequest("driver_id");
|
||||
// لم نعد نحتاج لمتغير $rideTimeStart لربطه بالـ SQL، لكن نتركه لاستلام القيمة من الطلب
|
||||
$rideTimeStart = filterRequest("rideTimeStart");
|
||||
|
||||
// 🚀 1. تسجيل بداية الطلب والبيانات المستلمة
|
||||
error_log("🚀 [accept_ride.php] Request Started. RideID: $rideId | DriverID: $driverId | Status: $status");
|
||||
|
||||
if (!$rideId || !$driverId || !$status) {
|
||||
error_log("❌ [accept_ride.php] Missing required parameters.");
|
||||
jsonError("Missing required parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// ---------------------------------------------------------
|
||||
// 1. التحديث على سيرفر التتبع (صاحب القرار)
|
||||
// ---------------------------------------------------------
|
||||
|
||||
error_log("🔄 [accept_ride.php] Attempting to update REMOTE Tracking DB for Ride ID: $rideId");
|
||||
|
||||
$stmtRideRemote = $con_ride->prepare("UPDATE `ride`
|
||||
SET `status` = :status,
|
||||
`driver_id` = :driverId,
|
||||
`rideTimeStart` = NOW()
|
||||
WHERE `id` = :id
|
||||
AND `status` IN ('waiting', 'wait')
|
||||
");
|
||||
|
||||
$stmtRideRemote->execute([
|
||||
':status' => $status,
|
||||
':driverId' => $driverId,
|
||||
':id' => $rideId
|
||||
]);
|
||||
|
||||
$count = $stmtRideRemote->rowCount();
|
||||
error_log("ℹ️ [accept_ride.php] Remote DB Rows Affected: $count");
|
||||
|
||||
// نتحقق: هل نجح التحديث في سيرفر التتبع؟
|
||||
if ($count > 0) {
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. التحديث على السيرفر الرئيسي (تثبيت السجل فقط)
|
||||
// ---------------------------------------------------------
|
||||
|
||||
error_log("🔄 [accept_ride.php] Remote success. Updating LOCAL Main DB...");
|
||||
|
||||
$sqlUpdate = "UPDATE `ride`
|
||||
SET `driver_id` = :driverId,
|
||||
`status` = :status,
|
||||
`rideTimeStart` = NOW()
|
||||
WHERE id = :rideId
|
||||
AND `status` IN ('waiting', 'wait') ";
|
||||
|
||||
$stmtUpdate = $con->prepare($sqlUpdate);
|
||||
$stmtUpdate->bindParam(":driverId", $driverId);
|
||||
$stmtUpdate->bindParam(":status", $status);
|
||||
$stmtUpdate->bindParam(":rideId", $rideId);
|
||||
$stmtUpdate->execute();
|
||||
|
||||
error_log("✅ [accept_ride.php] Ride accepted and started successfully for Driver: $driverId");
|
||||
jsonSuccess(null, "Ride accepted and started successfully at " . date('Y-m-d H:i:s'));
|
||||
|
||||
} else {
|
||||
error_log("⚠️ [accept_ride.php] Failed to accept ride. It might be already taken, canceled, or invalid status.");
|
||||
jsonError("Ride cannot be accepted (Already taken, Canceled, or Invalid Status).");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("❌ [accept_ride.php] Database Error: " . $e->getMessage());
|
||||
jsonError("Database Error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
30
backend/ride/rides/update_ride_cancel_wait.php
Executable file
30
backend/ride/rides/update_ride_cancel_wait.php
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// update_ride_cancel_wait.php
|
||||
// يوضع على السيرفر الرئيسي (Main Server)
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$rideId = filterRequest("ride_id");
|
||||
$driverId = filterRequest("driver_id");
|
||||
$status = "CancelAfterWait";
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// 1. تحديث جدول الرحلات
|
||||
$stmtRide = $con->prepare("UPDATE ride SET status = ?, rideTimeStart = NOW() WHERE id = ?");
|
||||
$stmtRide->execute([$status, $rideId]);
|
||||
|
||||
// 2. تحديث جدول طلبات السائقين
|
||||
// نستخدم Check لضمان عدم تكرار التحديث إذا كان محدثاً مسبقاً
|
||||
$stmtOrder = $con->prepare("UPDATE driver_orders SET status = ? WHERE order_id = ? AND driver_id = ?");
|
||||
$stmtOrder->execute([$status, $rideId, $driverId]);
|
||||
|
||||
$con->commit();
|
||||
jsonSuccess(null, "Ride status updated");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$con->rollBack();
|
||||
jsonError("DB Error");
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user