Files
intaleq/backend/ride/rides/getRideOrderID.php
T
Hamza-AyedandClaude Opus 5 92dc6b3641 chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل
نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق».
نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا
`cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules ·
.dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore.

هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً
مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ.

⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا
صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم
توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 05:10:29 +03:00

205 lines
8.9 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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");
}