first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

198
backend/ride/rides/acceptRide.php Executable file
View 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
View 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
View 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");
}

View 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());
}
?>

View 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());
}
?>

View 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());
}
?>

View 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");
}
?>

View 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()]);
}
?>

View 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");
}
?>

View 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]);
}

View File

View 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
View 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());
}
?>

View 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; // زيادة الوزن (حسب نوع الطلب)
}
?>

View 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()]);
}
?>

View 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()]);
}
?>

View 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.");
}
?>

View 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.");
}
?>

View 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()]);
}
?>

View 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");
}
?>

View 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"]);
}
?>

View 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
]);
?>

View 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}
]

View 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");
}

View 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
View 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());
}
?>

View 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
View 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());
}
?>

View 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.");
}
?>

View 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());
}
?>

View 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");
}
?>