Update codebase
This commit is contained in:
+326
-32
@@ -16,22 +16,20 @@ try {
|
||||
// =================================================================================
|
||||
// 🛠️ دالة مساعدة: إرسال الرحلة لسوق السائقين (Marketplace Broadcast)
|
||||
// =================================================================================
|
||||
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData, $extraMarketData = []) {
|
||||
$url = getenv('LOCATION_SOCKET_URL') ?: 'http://socket_driver:2021';
|
||||
if (strpos($url, 'localhost') !== false || strpos($url, '127.0.0.1') !== false) {
|
||||
if (file_exists('/.dockerenv')) {
|
||||
$url = str_replace(['localhost', '127.0.0.1'], 'socket_driver', $url);
|
||||
}
|
||||
}
|
||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
// ⚠️ هذه الحمولة تُبَثّ لكل سائق قريب (وليس للفائز فقط) — لا نضع فيها أي
|
||||
// بيانات راكب حسّاسة (هاتف/إيميل/FCM token). ما ينقص السائق من تفاصيل
|
||||
// الراكب يجلبه بعد نجاح القبول، والسيرفر يحلّ التوكن بنفسه في acceptRide.php.
|
||||
//
|
||||
// أسماء الحقول تطابق getRideWaiting.php لأن available_rides_page.dart يحشر
|
||||
// رحلات الـ socket في نفس القائمة ويقرأ منها بنفس المفاتيح — أي حقل ناقص
|
||||
// يصل للسيرفر لاحقاً كنص "null".
|
||||
$marketPayload = array_merge([
|
||||
/**
|
||||
* حمولة الرحلة كما يراها السائق قبل القبول.
|
||||
*
|
||||
* مشتركة بين البثّ الحر والإسناد بالدفعات عمداً: أي حقل يظهر في أحدهما
|
||||
* ولا يظهر في الآخر يصل للسيرفر لاحقاً كنص "null".
|
||||
*
|
||||
* ⚠️ لا تضع هنا أي بيانات راكب حسّاسة (هاتف/إيميل/FCM token) — هذه الحمولة
|
||||
* تصل سائقين لن يقودوا الرحلة. التفاصيل تُجلب بعد نجاح القبول.
|
||||
*
|
||||
* أسماء الحقول تطابق getRideWaiting.php لأن available_rides_page.dart يحشر
|
||||
* رحلات الـ socket في نفس القائمة ويقرأ منها بنفس المفاتيح.
|
||||
*/
|
||||
function buildMarketPayload($rideId, $lat, $lng, $payloadData, $extraMarketData = []) {
|
||||
return array_merge([
|
||||
'id' => (string)$rideId,
|
||||
'start_lat' => $lat,
|
||||
'start_lng' => $lng,
|
||||
@@ -47,7 +45,60 @@ function broadcastRideToMarket($rideId, $lat, $lng, $payloadData, $extraMarketDa
|
||||
'duration' => $payloadData[15],
|
||||
'passengerRate' => $payloadData[33],
|
||||
'passengerId' => $payloadData[7],
|
||||
// رحلة محجوزة: يقرأها available_rides_page و order_request_page
|
||||
// ونافذة الأوفرلي. غيابها = رحلة لحظية عادية.
|
||||
'is_scheduled' => $payloadData[39] ?? '',
|
||||
'scheduled_at' => $payloadData[40] ?? '',
|
||||
// راكب بلا إنترنت: طلب عبر رسالة نصية. لن يصله شات ولا إشعار،
|
||||
// والاتصال الهاتفي هو وسيلة التواصل الوحيدة معه.
|
||||
'is_sms' => $payloadData[41] ?? '',
|
||||
], $extraMarketData);
|
||||
}
|
||||
|
||||
/**
|
||||
* يوقظ العرض المتسلسل في السوكِت بعد أن بنى dispatchStartBatched الطابور.
|
||||
* السوكِت هو صاحب المؤقّت لأنه العملية الوحيدة طويلة العمر (Workerman
|
||||
* Timer) — كرون كل دقيقة لا يصلح لمهلة عشر ثوانٍ.
|
||||
*/
|
||||
function notifySocketStartBatchedDispatch($rideId) {
|
||||
$url = getenv('LOCATION_SOCKET_URL') ?: 'http://socket_driver:2021';
|
||||
if (file_exists('/.dockerenv')) {
|
||||
$url = str_replace(['localhost', '127.0.0.1'], 'socket_driver', $url);
|
||||
}
|
||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
||||
'action' => 'start_batched_dispatch',
|
||||
'ride_id' => $rideId,
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 2000);
|
||||
if ($INTERNAL_KEY) {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $INTERNAL_KEY"]);
|
||||
}
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
function broadcastRideToMarket($rideId, $lat, $lng, $payloadData, $extraMarketData = []) {
|
||||
$url = getenv('LOCATION_SOCKET_URL') ?: 'http://socket_driver:2021';
|
||||
if (strpos($url, 'localhost') !== false || strpos($url, '127.0.0.1') !== false) {
|
||||
if (file_exists('/.dockerenv')) {
|
||||
$url = str_replace(['localhost', '127.0.0.1'], 'socket_driver', $url);
|
||||
}
|
||||
}
|
||||
$INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : '';
|
||||
// ⚠️ هذه الحمولة تُبَثّ لكل سائق قريب (وليس للفائز فقط) — لا نضع فيها أي
|
||||
// بيانات راكب حسّاسة (هاتف/إيميل/FCM token). ما ينقص السائق من تفاصيل
|
||||
// الراكب يجلبه بعد نجاح القبول، والسيرفر يحلّ التوكن بنفسه في acceptRide.php.
|
||||
//
|
||||
// أسماء الحقول تطابق getRideWaiting.php لأن available_rides_page.dart يحشر
|
||||
// رحلات الـ socket في نفس القائمة ويقرأ منها بنفس المفاتيح — أي حقل ناقص
|
||||
// يصل للسيرفر لاحقاً كنص "null".
|
||||
$marketPayload = buildMarketPayload($rideId, $lat, $lng, $payloadData, $extraMarketData);
|
||||
|
||||
$postData = [
|
||||
'action' => 'market_new_ride',
|
||||
@@ -69,13 +120,155 @@ function broadcastRideToMarket($rideId, $lat, $lng, $payloadData, $extraMarketDa
|
||||
error_log("[add_ride] Request started. passenger_id=" . ($_POST['passenger_id'] ?? '?'));
|
||||
|
||||
// ── 1. Input ───────────────────────────────────────────────────
|
||||
// وسم الحجز المسبق: يمرّره bot/cron_scheduled_rides.php حين يحوّل حجزاً
|
||||
// لرحلة فعلية. الرحلة العادية تتركه فارغاً فلا يظهر شيء في التطبيقات.
|
||||
$isScheduledRide = filterRequest("source") === 'scheduled';
|
||||
$scheduledAtLabel = filterRequest("scheduled_at") ?: '';
|
||||
|
||||
// وسم الطلب بالرسائل النصية: يمرّره sms/ride_request.php لراكب لا
|
||||
// يملك إنترنت. تطبيق السائق يقرأ الوسم ليعرف أن التواصل بالاتصال لا
|
||||
// بالمحادثة — الراكب لن يرى أي إشعار في التطبيق.
|
||||
$isSmsRide = filterRequest("source") === 'sms';
|
||||
|
||||
$start_location = filterRequest("start_location");
|
||||
$end_location = filterRequest("end_location");
|
||||
$price = filterRequest("price");
|
||||
$price_token = filterRequest("price_token");
|
||||
|
||||
// Force passenger_id from JWT — never trust user-supplied passenger_id
|
||||
$passenger_id = $user_id;
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// 🔐 مسار S2S للحجز المسبق — بوّابة مزدوجة لا مفتاح وحده
|
||||
//
|
||||
// bot/cron_scheduled_rides.php لا يملك JWT راكب ولا رمز سعر، فلا يمكنه
|
||||
// المرور من الحراسة العادية. لكن فتح استثناء بمفتاح S2S وحده يعني أن
|
||||
// تسريب المفتاح = إنشاء رحلات باسم أي راكب.
|
||||
//
|
||||
// لذلك شرطان معاً:
|
||||
// ١. مفتاح S2S صحيح
|
||||
// ٢. الحجز موجود فعلاً في scheduled_rides بحالة 'dispatching'، ويخصّ
|
||||
// هذا الراكب بالذات، وبنفس نقطة الانطلاق
|
||||
//
|
||||
// الشرط الثاني هو الحماية الحقيقية: لا رحلة تُنشأ إلا مقابل حجز أنشأه
|
||||
// الراكب بنفسه عبر المسار الموثَّق، والكرون قفله للتوّ. حامل المفتاح
|
||||
// وحده لا يستطيع اختلاق حجز.
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
$isInternalScheduled = false;
|
||||
|
||||
// يُملأ أحدهما فقط حسب المصدر. التصريح بهما هنا يمنع تحذير «متغيّر
|
||||
// غير معرّف» في كتلة التسعير الداخلي التي تقرأ الاثنين.
|
||||
$booking = null;
|
||||
$smsReq = null;
|
||||
|
||||
if ($isScheduledRide) {
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
$expectedKey = getenv('S2S_SHARED_KEY') ?: '';
|
||||
$scheduledId = (int) (filterRequest('scheduled_id', 'int') ?: 0);
|
||||
$claimedPassenger = filterRequest('passenger_id');
|
||||
|
||||
if (empty($expectedKey) || !hash_equals($expectedKey, (string) $providedKey)) {
|
||||
error_log('[add_ride] SECURITY: مسار الحجز بمفتاح S2S غير صالح');
|
||||
printFailure('Unauthorized');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($scheduledId <= 0 || empty($claimedPassenger)) {
|
||||
printFailure('Missing scheduled_id or passenger_id');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmtBooking = $con->prepare("
|
||||
SELECT passenger_id, start_location, end_location, car_type,
|
||||
distance, duration
|
||||
FROM scheduled_rides
|
||||
WHERE id = ? AND status = 'dispatching' LIMIT 1
|
||||
");
|
||||
$stmtBooking->execute([$scheduledId]);
|
||||
$booking = $stmtBooking->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $eB) {
|
||||
error_log('[add_ride] تعذّرت قراءة الحجز: ' . $eB->getMessage());
|
||||
printFailure('Server error');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$booking) {
|
||||
error_log("[add_ride] SECURITY: حجز #$scheduledId غير موجود أو ليس قيد الإطلاق");
|
||||
printFailure('Invalid booking');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ((string) $booking['passenger_id'] !== (string) $claimedPassenger
|
||||
|| !coordsMatch($booking['start_location'], $start_location)) {
|
||||
error_log("[add_ride] SECURITY: حجز #$scheduledId لا يطابق الراكب أو النقطة");
|
||||
printFailure('Booking mismatch');
|
||||
exit;
|
||||
}
|
||||
|
||||
// من هنا نثق ببيانات **الحجز المخزَّن** لا بما وصل في الطلب.
|
||||
$isInternalScheduled = true;
|
||||
$passenger_id = $booking['passenger_id'];
|
||||
$end_location = $booking['end_location'];
|
||||
$carType = $booking['car_type'];
|
||||
$distance = $booking['distance'];
|
||||
} elseif ($isSmsRide) {
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// نفس البوّابة المزدوجة بالضبط، على جدول sms_ride_requests.
|
||||
//
|
||||
// الفرق عن الحجز المسبق أن مصدر الطلب هنا رسالة نصية من رقم لا
|
||||
// يحمل JWT إطلاقاً — فالشرط الثاني هو كل الحماية: لا رحلة إلا
|
||||
// مقابل صف أنشأه الويبهوك من رسالة فعلية وقفله للتوّ.
|
||||
// ══════════════════════════════════════════════════════════
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
$expectedKey = getenv('S2S_SHARED_KEY') ?: '';
|
||||
$smsRequestId = (int) (filterRequest('sms_request_id', 'int') ?: 0);
|
||||
$claimedPassenger = filterRequest('passenger_id');
|
||||
|
||||
if (empty($expectedKey) || !hash_equals($expectedKey, (string) $providedKey)) {
|
||||
error_log('[add_ride] SECURITY: مسار SMS بمفتاح S2S غير صالح');
|
||||
printFailure('Unauthorized');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($smsRequestId <= 0 || empty($claimedPassenger)) {
|
||||
printFailure('Missing sms_request_id or passenger_id');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmtSms = $con->prepare("
|
||||
SELECT passenger_id, start_location, end_location, distance, duration
|
||||
FROM sms_ride_requests
|
||||
WHERE id = ? AND status = 'dispatching' LIMIT 1
|
||||
");
|
||||
$stmtSms->execute([$smsRequestId]);
|
||||
$smsReq = $stmtSms->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $eS) {
|
||||
error_log('[add_ride] تعذّرت قراءة طلب SMS: ' . $eS->getMessage());
|
||||
printFailure('Server error');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$smsReq) {
|
||||
error_log("[add_ride] SECURITY: طلب SMS #$smsRequestId غير موجود أو ليس قيد الإطلاق");
|
||||
printFailure('Invalid sms request');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ((string) $smsReq['passenger_id'] !== (string) $claimedPassenger
|
||||
|| !coordsMatch($smsReq['start_location'], $start_location)) {
|
||||
error_log("[add_ride] SECURITY: طلب SMS #$smsRequestId لا يطابق الراكب أو النقطة");
|
||||
printFailure('SMS request mismatch');
|
||||
exit;
|
||||
}
|
||||
|
||||
// نثق بالصف المخزَّن لا بما وصل في الطلب.
|
||||
$isInternalScheduled = true; // نفس الإعفاء من رمز السعر و JWT
|
||||
$passenger_id = $smsReq['passenger_id'];
|
||||
$end_location = $smsReq['end_location'];
|
||||
$distance = $smsReq['distance'];
|
||||
} else {
|
||||
// Force passenger_id from JWT — never trust user-supplied passenger_id
|
||||
$passenger_id = $user_id;
|
||||
}
|
||||
$driver_id = (string)(filterRequest("driver_id") ?: '0');
|
||||
$status = filterRequest("status") ?: 'nothing';
|
||||
$price_for_driver = filterRequest("price_for_driver") ?: ($price ?: '0');
|
||||
@@ -116,34 +309,73 @@ if (empty($passenger_id) || empty($start_location) || empty($end_location) || em
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── التسعير الداخلي لمسار الحجز ─────────────────────────────
|
||||
// السعر لا يأتي من الطلب: الكرون يمرّر تقدير وقت الحجز، وهو قديم بساعات
|
||||
// ولا يعرف الذروة. نحسبه هنا من أسعار الدولة لحظة الإنشاء.
|
||||
if ($isInternalScheduled) {
|
||||
require_once __DIR__ . '/../pricing/pricing_helper.php';
|
||||
|
||||
$kazanRow = [];
|
||||
try {
|
||||
$stmtK = $con->prepare("SELECT * FROM kazan WHERE country = ? LIMIT 1");
|
||||
$stmtK->execute([getenv('GLOBAL_COUNTRY') ?: 'Jordan']);
|
||||
$kazanRow = $stmtK->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
} catch (PDOException $eK) {
|
||||
error_log('[add_ride] تعذّر جلب أسعار الدولة للحجز: ' . $eK->getMessage());
|
||||
}
|
||||
|
||||
if (empty($kazanRow)) {
|
||||
printFailure('Pricing unavailable');
|
||||
exit;
|
||||
}
|
||||
|
||||
// المدّة من الصف المخزَّن أيّاً كان مصدره — الحجز المسبق أو طلب
|
||||
// الرسائل. قراءتها من `$booking` وحده كانت تمرّر صفراً لكل رحلة
|
||||
// قادمة من SMS، فيسقط شقّ الزمن من السعر.
|
||||
$internalDuration = (int) ($booking['duration'] ?? $smsReq['duration'] ?? 0);
|
||||
|
||||
$price = computeInternalRidePrice(
|
||||
$kazanRow, (string) $carType, (float) $distance, $internalDuration
|
||||
);
|
||||
$price_for_driver = $price;
|
||||
$price_for_passenger = $price;
|
||||
|
||||
$internalRef = $isSmsRide ? "طلب SMS #$smsRequestId" : "حجز #$scheduledId";
|
||||
error_log("[add_ride] $internalRef سُعِّر داخلياً: $price");
|
||||
}
|
||||
|
||||
// SECURE PRICE TOKEN VERIFICATION
|
||||
if (empty($price_token)) {
|
||||
if (!$isInternalScheduled && empty($price_token)) {
|
||||
error_log("[add_ride] Security failed — price_token is missing.");
|
||||
printFailure("Secure price token is required");
|
||||
exit;
|
||||
}
|
||||
|
||||
$decrypted = isset($encryptionHelper) ? $encryptionHelper->decryptData($price_token) : false;
|
||||
if (!$decrypted) {
|
||||
$decrypted = (!$isInternalScheduled && isset($encryptionHelper))
|
||||
? $encryptionHelper->decryptData($price_token) : false;
|
||||
if (!$isInternalScheduled && !$decrypted) {
|
||||
error_log("[add_ride] Security failed — failed to decrypt price_token.");
|
||||
printFailure("Invalid or tampered price token");
|
||||
exit;
|
||||
}
|
||||
|
||||
$tokenData = json_decode($decrypted, true);
|
||||
if (!$tokenData || !isset($tokenData['expires']) || $tokenData['expires'] < time()) {
|
||||
$tokenData = $decrypted ? json_decode($decrypted, true) : null;
|
||||
if (!$isInternalScheduled
|
||||
&& (!$tokenData || !isset($tokenData['expires']) || $tokenData['expires'] < time())) {
|
||||
error_log("[add_ride] Security failed — token is expired or invalid JSON.");
|
||||
printFailure("Price token has expired, please request estimation again");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($tokenData['passenger_id'] != $passenger_id) {
|
||||
if (!$isInternalScheduled && $tokenData['passenger_id'] != $passenger_id) {
|
||||
error_log("[add_ride] Security failed — passenger_id mismatch.");
|
||||
printFailure("Tampered price token (passenger mismatch)");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!coordsMatch($tokenData['start_location'], $start_location) || !coordsMatch($tokenData['end_location'], $end_location)) {
|
||||
if (!$isInternalScheduled
|
||||
&& (!coordsMatch($tokenData['start_location'], $start_location)
|
||||
|| !coordsMatch($tokenData['end_location'], $end_location))) {
|
||||
error_log("[add_ride] Security failed — coordinates mismatch. Token: " . ($tokenData['start_location'] . " / " . $tokenData['end_location']) . " Request: " . ($start_location . " / " . $end_location));
|
||||
printFailure("Tampered price token (route mismatch)");
|
||||
exit;
|
||||
@@ -350,6 +582,17 @@ try {
|
||||
// اللي بتوصلها البيانات كـ List مش كـ Map مسمّى مثل FCM
|
||||
isset($extraDispatchData['driver_earnings_extra']) ? $extraDispatchData['driver_earnings_extra'] : '',
|
||||
isset($extraDispatchData['driver_earnings_currency']) ? $extraDispatchData['driver_earnings_currency'] : '',
|
||||
// 🆕 Index 37/38: وسم العرض الحصري في الإسناد بالدفعات
|
||||
'',
|
||||
'',
|
||||
// 🆕 Index 39/40: رحلة محجوزة مسبقاً — السائق يجب أن يعرف أنها
|
||||
// ليست طلباً لحظياً بل موعد مضبوط، وأن الراكب ينتظره في وقت محدد.
|
||||
$isScheduledRide ? '1' : '',
|
||||
$isScheduledRide ? (string) $scheduledAtLabel : '',
|
||||
// 🆕 Index 41: طلب وصل عبر رسالة نصية — الراكب بلا إنترنت.
|
||||
// السائق يجب أن يعرف أن المحادثة داخل التطبيق لن تصل إليه،
|
||||
// وأن الاتصال الهاتفي هو وسيلة التواصل الوحيدة معه.
|
||||
$isSmsRide ? '1' : '',
|
||||
];
|
||||
|
||||
// Direct dispatch للسائقين القريبين
|
||||
@@ -428,15 +671,66 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($driversData)) {
|
||||
dispatchRideToDrivers($driversData, $insertedId, $payload, $start_name_loc, $encryptionHelper, $extraDispatchData);
|
||||
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.");
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// الإسناد — بالدفعات أو بالبثّ الحر
|
||||
//
|
||||
// batched: عرض حصري متسلسل على مرشحين مرتبين. لا بثّ للسوق، ولا
|
||||
// إشعار جماعي — وإلا لضاع معنى الحصرية.
|
||||
// broadcast (الافتراضي): السلوك القائم حرفياً بلا أي تغيير.
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
require_once __DIR__ . '/../dispatch/dispatch_helper.php';
|
||||
|
||||
$batchedStarted = false;
|
||||
if (dispatchIsBatched((string) ($payload[31] ?? ''))) {
|
||||
$batchedStarted = dispatchStartBatched(
|
||||
$redisLocation ?? null,
|
||||
$insertedId,
|
||||
$driversData ?: [],
|
||||
buildMarketPayload($insertedId, $startLat, $startLng, $payload, $extraDispatchData)
|
||||
);
|
||||
|
||||
if ($batchedStarted) {
|
||||
notifySocketStartBatchedDispatch($insertedId);
|
||||
|
||||
// السوكِت يوصل العرض لمن تطبيقه مفتوح فقط. بلا FCM، السائق
|
||||
// الأفضل ترتيباً وتطبيقه مغلق لا يعلم بسبقه إطلاقاً — فيضيع
|
||||
// السبق كله ويذهب للبثّ. نرسل له إشعاراً موازياً.
|
||||
$headStartDriver = dispatchHeadStartDriver($redisLocation ?? null, $insertedId);
|
||||
if ($headStartDriver !== null) {
|
||||
$headDriverRow = null;
|
||||
foreach ($driversData as $d) {
|
||||
if ((string)($d['driver_id'] ?? '') === $headStartDriver) {
|
||||
$headDriverRow = $d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($headDriverRow) {
|
||||
dispatchRideToDrivers(
|
||||
[$headDriverRow], $insertedId, $payload, $start_name_loc,
|
||||
$encryptionHelper, $extraDispatchData,
|
||||
['exclusive', dispatchHeadStartSeconds()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
error_log("[add_ride] RideID=$insertedId أُسند بالدفعات.");
|
||||
} else {
|
||||
error_log("[add_ride] تعذّر الإسناد بالدفعات RideID=$insertedId"
|
||||
. " — السقوط للبثّ الحر.");
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast للـ marketplace دائماً
|
||||
broadcastRideToMarket($insertedId, $startLat, $startLng, $payload, $extraDispatchData);
|
||||
if (!$batchedStarted) {
|
||||
if (!empty($driversData)) {
|
||||
dispatchRideToDrivers($driversData, $insertedId, $payload, $start_name_loc, $encryptionHelper, $extraDispatchData);
|
||||
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, $extraDispatchData);
|
||||
}
|
||||
|
||||
// رد النجاح للتطبيق
|
||||
printSuccess($insertedId);
|
||||
|
||||
Reference in New Issue
Block a user