الموضعان كانا يقرآن مفتاحاً لا يُكتب في أي مكان في المشروع:
$redisLocation->hGetAll("driver:location:$driverId")
الكاتب الفعلي هو معالج الدفعات في loction_server/driver_socket.php، وهو
يكتب hmset على driver:profile:{id} و driver:public:{id} معاً بنفس الحقول
(lat/lng/heading/speed/status/updated_at). و driver:public له TTL 86400
بينما driver:profile له 900 فقط، فالعام هو الأنسب للقراءة.
الأثر: كانت حمولة القبول تصل الراكب بلا إحداثيات أولية للسائق، فلا يظهر
الماركر إلا بعد أول تحديث موقع من السوكيت أو الـ polling.
أحد الموضعين ملف getRideOrderID.php الذي أضفته في f66db7db — نسخت النمط
من acceptRide.php فنقلت الخطأ معه.
مثبَّت على الإنتاج: redis-cli --scan --pattern 'driver:*' أرجع
driver:public:<id> فقط، ولا شيء باسم driver:location.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
205 lines
8.9 KiB
PHP
205 lines
8.9 KiB
PHP
<?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 {
|
||
// المفتاح driver:public هو ما يكتبه driver_socket.php فعلاً
|
||
// (driver:location لا وجود له — كان خطأً منسوخاً من acceptRide.php).
|
||
$driverLoc = $redisLocation->hGetAll("driver:public:$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");
|
||
}
|