Files
Hamza-AyedandClaude Opus 5 4d8414c96b feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة
ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ.

الخريطة:
  backend · payment_server · loction_server · ride_server ·
  passenger_server · docker · dashboard · stress_test  → الجذر
  siro_rider  → apps/rider          siro_driver  → apps/driver
  siro_admin  → dashboards/admin    siro_service → dashboards/service
  android_bot → apps/android_bot    socialBot    → apps/socialBot

نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب)
لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً:
كل ما يلي يصير فرقاً مقروءاً مقابل المصدر.

لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز،
سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh
(ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في
مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و
dashboards/transit-web).

⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة:
1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر):
   كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner.
2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist)
   يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً.
3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع →
   يجب ضمّ الحزم داخله أسوة بـ apps/rider.

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

205 lines
8.9 KiB
PHP
Raw Permalink 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");
}