إضافة getRideOrderID.php المفقود — المسار الاحتياطي لبيانات السائق
تطبيق الراكب يطلب /ride/rides/getRideOrderID.php منذ البداية، والموجود على
السيرفر getRideOrderIDNew.php فقط ⇒ 404 صامت في getUpdatedRideForDriverApply.
هذا يفسّر عرضين ظنّاهما منفصلين:
- الـ polling يسبق الـ FCM فيضبط _isAcceptanceProcessed=true ثم يأخذ 404،
فيُرفض بعدها payload الـ FCM الذي يحمل driver_info كاملاً
("Already processed") ⇒ الرحلة مقبولة بلا معلومات سائق.
- ولأن driverToken يبقى فارغاً، ترد send_fcm.php بـ 400 Missing: target
⇒ رسائل الراكب للسائق لا تُرسَل إطلاقاً. (الاتجاه المعاكس كان يعمل بعد
إصلاح getRideWaiting.php — نفس خطأ التشفير معكوساً.)
getRideOrderIDNew.php لا يصلح بديلاً: داخلي عبر get_connect.php →
validateInternalKey فلا يستطيع التطبيق مناداته، ولا يرجع
ratingCount/completedRides/driverTier.
النقطة الجديدة:
- connect.php (JWT)، والراكب من الـ JWT فقط لا من الطلب (حماية IDOR).
- نفس استعلام acceptRide.php وشكل رده حرفياً حتى يقرأه
_fillDriverDataLocally بنفس المفاتيح.
- فك تشفير driverToken.token — بلا ذلك يصل التطبيق blob يستخدمه كـ FCM
target فيرفضه FCM بـ 400.
- تفادي تصادم المفاتيح: getUpdatedRideForDriverApply يقرأ
passengerName + last_name كاسم الراكب، فلقب السائق نُقل إلى
driver_last_name واسم الراكب الحقيقي يُرجَّع في مكانه.
- ride DB هو المرجع مع fallback على primary، ورحلة بلا سائق ترد success
لا failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
143146c1b4
commit
f66db7db42
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// ride/rides/getRideOrderID.php
|
||||
// PURPOSE : بيانات السائق/السيارة للرحلة الحالية — المسار الاحتياطي للراكب
|
||||
// حين لا يوصل السوكيت driver_info (ride_lifecycle_controller.dart
|
||||
// → getUpdatedRideForDriverApply).
|
||||
//
|
||||
// لماذا هذا الملف: التطبيق يطلب getRideOrderID.php منذ البداية، والموجود
|
||||
// كان getRideOrderIDNew.php فقط ⇒ 404 صامت. ونتيجته أن الـ polling يسبق
|
||||
// الـ FCM فيضبط _isAcceptanceProcessed=true بلا بيانات، فيُرفض لاحقاً
|
||||
// payload الـ FCM الذي يحمل driver_info كاملاً ("Already processed")،
|
||||
// فتظهر الرحلة مقبولة بلا معلومات سائق، ويبقى driverToken فارغاً فلا
|
||||
// تُرسَل رسائل الراكب للسائق.
|
||||
//
|
||||
// getRideOrderIDNew.php لا يصلح بديلاً مباشراً: هو داخلي (get_connect.php →
|
||||
// validateInternalKey) ولا يستطيع التطبيق مناداته، ولا يرجع
|
||||
// ratingCount/completedRides/driverTier.
|
||||
//
|
||||
// الرد يطابق شكل driverInfo في acceptRide.php حرفياً حتى يقرأه
|
||||
// _fillDriverDataLocally و getUpdatedRideForDriverApply بنفس المفاتيح.
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
include "../../connect.php";
|
||||
|
||||
// الراكب من الـ JWT فقط — لا نثق بأي passengerID قادم من الطلب (حماية IDOR)
|
||||
$passengerId = $user_id;
|
||||
$rideIdParam = filterRequest("id");
|
||||
|
||||
if (empty($passengerId)) {
|
||||
printFailure("Unauthorized");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// ── 1. الرحلة: ride DB هو المرجع، مع fallback على primary ──────
|
||||
$rideRow = null;
|
||||
|
||||
$sqlRide = "SELECT id, driver_id, passenger_id, status
|
||||
FROM `ride`
|
||||
WHERE passenger_id = ? ";
|
||||
$params = [$passengerId];
|
||||
if (!empty($rideIdParam)) {
|
||||
$sqlRide .= "AND id = ? ";
|
||||
$params[] = $rideIdParam;
|
||||
}
|
||||
$sqlRide .= "ORDER BY id DESC LIMIT 1";
|
||||
|
||||
try {
|
||||
$con_ride = Database::get('ride');
|
||||
$stmtRide = $con_ride->prepare($sqlRide);
|
||||
$stmtRide->execute($params);
|
||||
$rideRow = $stmtRide->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (Exception $eRideDb) {
|
||||
error_log("[getRideOrderID] ride DB unavailable: " . $eRideDb->getMessage());
|
||||
}
|
||||
|
||||
if (!$rideRow) {
|
||||
$stmtRide = $con->prepare($sqlRide);
|
||||
$stmtRide->execute($params);
|
||||
$rideRow = $stmtRide->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
if (!$rideRow) {
|
||||
printFailure("No ride found");
|
||||
exit;
|
||||
}
|
||||
|
||||
$driverId = $rideRow['driver_id'] ?? '';
|
||||
if (empty($driverId)) {
|
||||
// رحلة لم يقبلها سائق بعد — ليست حالة خطأ
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "No driver assigned yet",
|
||||
"data" => ["ride_id" => (string)$rideRow['id'], "status" => $rideRow['status']],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 2. بيانات السائق — نفس استعلام acceptRide.php ──────────────
|
||||
$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,
|
||||
(SELECT COUNT(*) FROM ratingDriver WHERE driver_id = d.id) AS ratingCount,
|
||||
(SELECT COUNT(*) FROM ride WHERE driver_id = d.id AND status IN ('Finished', 'finished')) AS completedRides,
|
||||
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) {
|
||||
printFailure("Driver not found");
|
||||
exit;
|
||||
}
|
||||
|
||||
// جدول driverToken يخزّن التوكن مشفّراً — بلا فك تشفير يصل التطبيق
|
||||
// blob يستخدمه كـ FCM target فيرفضه FCM بـ 400 ولا تصل رسائل الراكب.
|
||||
$driverInfo = [];
|
||||
$encryptedFields = ['first_name', 'last_name', 'gender', 'phone', 'car_plate', 'token'];
|
||||
foreach ($driverRaw as $key => $value) {
|
||||
if (in_array($key, $encryptedFields, true) && !empty($value)) {
|
||||
$decrypted = false;
|
||||
try {
|
||||
$decrypted = $encryptionHelper->decryptData($value);
|
||||
} catch (\Throwable $e) {
|
||||
$decrypted = false;
|
||||
}
|
||||
$driverInfo[$key] = ($decrypted !== false && $decrypted !== null && $decrypted !== '')
|
||||
? $decrypted
|
||||
: $value;
|
||||
} else {
|
||||
$driverInfo[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$driverInfo['driverName'] = trim(($driverInfo['first_name'] ?? '') . ' ' . ($driverInfo['last_name'] ?? ''));
|
||||
if (empty($driverInfo['driverName']) && !empty($driverInfo['phone'])) {
|
||||
$driverInfo['driverName'] = $driverInfo['phone'];
|
||||
}
|
||||
|
||||
$driverInfo['ratingDriver'] = !empty($driverInfo['ratingDriver']) ? (string)$driverInfo['ratingDriver'] : "5.0";
|
||||
$ratingValue = (float) $driverInfo['ratingDriver'];
|
||||
$ratingCount = (int) ($driverInfo['ratingCount'] ?? 0);
|
||||
$completedRides = (int) ($driverInfo['completedRides'] ?? 0);
|
||||
if ($ratingValue >= 4.8 && $ratingCount >= 50 && $completedRides >= 100) {
|
||||
$driverInfo['driverTier'] = 'Professional driver';
|
||||
} elseif ($ratingValue >= 4.5 && $ratingCount >= 15 && $completedRides >= 30) {
|
||||
$driverInfo['driverTier'] = 'Trusted driver';
|
||||
} else {
|
||||
$driverInfo['driverTier'] = 'Verified driver';
|
||||
}
|
||||
|
||||
// ── 3. آخر موقع للسائق من Redis (اختياري) ──────────────────────
|
||||
if (isset($redisLocation) && $redisLocation) {
|
||||
try {
|
||||
$driverLoc = $redisLocation->hGetAll("driver:location:$driverId");
|
||||
if (!empty($driverLoc)) {
|
||||
$driverInfo['lat'] = (float)($driverLoc['lat'] ?? 0);
|
||||
$driverInfo['lng'] = (float)($driverLoc['lng'] ?? 0);
|
||||
$driverInfo['heading'] = (float)($driverLoc['heading'] ?? 0);
|
||||
}
|
||||
} catch (Exception $eLoc) {
|
||||
// تجاهل — الموقع يصل بالسوكيت أصلاً
|
||||
}
|
||||
}
|
||||
|
||||
// ⚠️ تصادم مفاتيح: getUpdatedRideForDriverApply يقرأ passengerName + last_name
|
||||
// كاسم **الراكب**. لو تركنا last_name للسائق يظهر لقب السائق كاسم الراكب.
|
||||
// ننقل لقب السائق إلى driver_last_name (وهو مفتاح يفهمه
|
||||
// _fillDriverDataLocally أصلاً) ونرجّع اسم الراكب الحقيقي في مكانه.
|
||||
$driverInfo['driver_last_name'] = $driverInfo['last_name'] ?? '';
|
||||
unset($driverInfo['last_name']);
|
||||
|
||||
try {
|
||||
$stmtPas = $con->prepare("SELECT first_name, last_name FROM passengers WHERE id = ? LIMIT 1");
|
||||
$stmtPas->execute([$passengerId]);
|
||||
$pasRow = $stmtPas->fetch(PDO::FETCH_ASSOC);
|
||||
if ($pasRow) {
|
||||
foreach (['first_name' => 'passengerName', 'last_name' => 'last_name'] as $src => $dst) {
|
||||
$val = $pasRow[$src] ?? '';
|
||||
if ($val === '' || $val === null) { $driverInfo[$dst] = ''; continue; }
|
||||
$dec = false;
|
||||
try {
|
||||
$dec = $encryptionHelper->decryptData($val);
|
||||
} catch (\Throwable $e) {
|
||||
$dec = false;
|
||||
}
|
||||
$driverInfo[$dst] = ($dec !== false && $dec !== null && $dec !== '') ? $dec : $val;
|
||||
}
|
||||
}
|
||||
} catch (PDOException $ePas) {
|
||||
error_log("[getRideOrderID] passenger name lookup WARNING: " . $ePas->getMessage());
|
||||
}
|
||||
|
||||
$driverInfo['ride_id'] = (string)$rideRow['id'];
|
||||
$driverInfo['status'] = $rideRow['status'];
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"message" => "Driver info",
|
||||
"data" => $driverInfo,
|
||||
]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("[getRideOrderID] CRITICAL: " . $e->getMessage());
|
||||
printFailure("Server error");
|
||||
}
|
||||
Reference in New Issue
Block a user