Update: 2026-07-07 04:57:50

This commit is contained in:
Hamza-Ayed
2026-07-07 04:57:51 +03:00
parent 5725fb36f4
commit 628e169552
25 changed files with 288 additions and 1893 deletions
+119
View File
@@ -0,0 +1,119 @@
<?php
// ============================================================
// pricing_helper.php
// يحتوي على دوال التسعير الموحدة
// ============================================================
/**
* الحصول على سعر الكيلومتر حسب نوع السيارة من جدول أسعار الدولة
*
* @param string $carType نوع السيارة
* @param array $kazanRow صف من جدول kazan
* @return float
*/
function getPerKmRate(string $carType, array $kazanRow): float {
$rateColumns = [
'Comfort' => 'comfortPrice',
'Speed' => 'speedPrice',
'Lady' => 'ladyPrice',
'Electric' => 'electricPrice',
'Van' => 'vanPrice',
'Delivery' => 'deliveryPrice',
'Mishwar Vip' => 'mishwarVipPrice',
'Fixed Price' => 'fixedPrice',
'Awfar Car' => 'awfarPrice',
];
$column = $rateColumns[$carType] ?? 'speedPrice';
// دعم التوافق مع الإصدارات القديمة (backward compatibility)
$rate = floatval($kazanRow[$column] ?? 0);
if ($rate <= 0) {
$oldColumnMap = [
'Lady' => 'familyPrice',
'Mishwar Vip' => 'freePrice',
'Electric' => 'naturePrice',
'Van' => 'heavyPrice',
];
$oldColumn = $oldColumnMap[$carType] ?? null;
if ($oldColumn && isset($kazanRow[$oldColumn])) {
$rate = floatval($kazanRow[$oldColumn]);
}
}
// Fallback أخير
if ($rate <= 0) {
$rate = floatval($kazanRow['speedPrice'] ?? 36);
}
return $rate;
}
/**
* الحصول على سعر الدقيقة حسب وقت اليوم من جدول kazan
*
* @param array $countryPricing صف من جدول kazan
* @return float
*/
function getPerMinRate(array $countryPricing): float {
$hour = (int)date('H');
// قراءة الأسعار من الأعمدة الجديدة للدقيقة
$normalMinPrice = floatval($countryPricing['normalMinPrice'] ?? 0);
$peakMinPrice = floatval($countryPricing['peakMinPrice'] ?? 0);
$lateMinPrice = floatval($countryPricing['lateMinPrice'] ?? 0);
// دعم التوافق مع الإصدارات القديمة (latePrice, naturePrice)
if ($lateMinPrice <= 0) $lateMinPrice = floatval($countryPricing['latePrice'] ?? 0);
if ($normalMinPrice <= 0) $normalMinPrice = floatval($countryPricing['naturePrice'] ?? 0);
// Fallback: حساب سعر الدقيقة من speedPrice إذا كانت الأعمدة الجديدة فارغة
if ($normalMinPrice <= 0) {
$speedPrice = floatval($countryPricing['speedPrice'] ?? 36);
$normalMinPrice = $speedPrice / 4;
}
if ($peakMinPrice <= 0) $peakMinPrice = $normalMinPrice * 1.15; // 15% زيادة
if ($lateMinPrice <= 0) $lateMinPrice = $normalMinPrice * 1.25; // 25% زيادة
if ($hour >= 21 || $hour < 1) {
return round($lateMinPrice, 2); // Late Night
}
if ($hour >= 14 && $hour <= 17) {
return round($peakMinPrice, 2); // Peak
}
return round($normalMinPrice, 2); // Normal
}
/**
* تحديد رمز العملة حسب الدولة
*
* @param string $countryCode رمز الدولة (Syria, Egypt, Jordan, ...)
* @return string رمز العملة (SYP, EGP, JOD, ...)
*/
function getCurrencyByCountry(string $countryCode): string {
$currencies = [
'Syria' => 'SYP',
'Egypt' => 'EGP',
'Jordan' => 'JOD',
'Iraq' => 'IQD',
'UAE' => 'AED',
'Saudi Arabia' => 'SAR',
'Qatar' => 'QAR',
'Kuwait' => 'KWD',
'Bahrain' => 'BHD',
'Oman' => 'OMR',
'Turkey' => 'TRY',
'Lebanon' => 'LBP',
'Palestine' => 'ILS',
'Yemen' => 'YER',
'Libya' => 'LYD',
'Tunisia' => 'TND',
'Algeria' => 'DZD',
'Morocco' => 'MAD',
'Sudan' => 'SDG',
];
return $currencies[$countryCode] ?? 'SYP'; // افتراضي: ليرة سورية
}
?>
-117
View File
@@ -1,117 +0,0 @@
<?php
// cancelRideFromDriver.php
// تأكد أن هذا الملف يحتوي على دوال الإشعارات (notifyPassengerOnRideServer)
require_once __DIR__ . '/../../connect.php';
require_once __DIR__ . '/streak_helper.php';
// 🚀 تسجيل بداية العملية
error_log("🚀 [cancelRide.php] Request Started to Cancel Ride From Driver.");
$id = filterRequest("id"); // Ride ID
if (!$id) {
error_log("❌ [cancelRide.php] Missing Ride ID.");
jsonError("Missing ID");
exit;
}
// الحالة الجديدة (إلغاء نهائي من طرف السائق)
$newStatus = "cancelRideFromDriver";
// الحالات المسموح بإلغاء الرحلة فيها فقط
// نسمح بالإلغاء إذا كانت في وضع الانتظار أو القبول المبدئي
$allowedStatuses = "'wait', 'waiting', 'Apply', 'accepted', 'arrive'";
try {
// ---------------------------------------------------------
// 1. التحديث على سيرفر التتبع (Remote DB)
// ---------------------------------------------------------
// نستخدم شرط الحالة لضمان عدم إلغاء رحلة بدأت بالفعل (Start/Begin)
$sql = "UPDATE `ride`
SET `status` = ?, `updated_at` = CURRENT_TIMESTAMP
WHERE `id` = ?
AND `status` IN ($allowedStatuses)";
// استخدام Prepared Statements للأمان
$stmtRemote = $con_ride->prepare($sql);
$stmtRemote->execute([$newStatus, $id]);
$count = $stmtRemote->rowCount();
error_log("ℹ️ [cancelRide.php] Remote DB Rows Affected: $count");
// التحقق: هل تم التحديث؟
if ($count > 0) {
// ---------------------------------------------------------
// 2. التحديث على السيرفر المحلي (Local DB)
// ---------------------------------------------------------
// نبدأ معاملة لضمان تكامل البيانات
if (isset($con)) {
$con->beginTransaction();
try {
$stmtLocal = $con->prepare($sql);
$stmtLocal->execute([$newStatus, $id]);
// تحديث جدول driver_orders أيضاً لتوحيد الحالة (اختياري ولكنه مفضل)
$stmtDriverOrder = $con->prepare("UPDATE driver_orders SET status = ? WHERE order_id = ?");
$stmtDriverOrder->execute([$newStatus, $id]);
// 🆕 تصفير التتابع لأن السائق ألغى الرحلة
$stmtGetDriver = $con->prepare("SELECT driver_id FROM ride WHERE id = ?");
$stmtGetDriver->execute([$id]);
$driver_id_to_reset = $stmtGetDriver->fetchColumn() ?: $user_id;
if ($driver_id_to_reset) {
handleDriverStreak($con, $driver_id_to_reset, 'reset');
}
$con->commit();
} catch (Exception $eLocal) {
$con->rollBack();
error_log("⚠️ Local DB Update Failed: " . $eLocal->getMessage());
}
}
// ---------------------------------------------------------
// 3. 🔥 إشعار الراكب عبر السوكيت (القطعة المفقودة) 🔥
// ---------------------------------------------------------
// أ. جلب معرف الراكب لإرسال الإشعار له
// نستخدم connection الرحلات لضمان وجود البيانات
$stmtPas = $con_ride->prepare("SELECT passenger_id FROM ride WHERE id = ?");
$stmtPas->execute([$id]);
$passenger_id = $stmtPas->fetchColumn();
if ($passenger_id) {
$payload = [
'ride_id' => $id,
'status' => 'cancelled', // هذه الحالة يستقبلها الفلاتر ويغلق الواجهة
'msg' => 'للأسف، قام السائق بإلغاء الرحلة.'
];
// استدعاء الدالة المعرفة في functions.php/connect.php
if (function_exists('notifyPassengerOnRideServer')) {
notifyPassengerOnRideServer($passenger_id, $payload);
error_log("📡 [cancelRide.php] Notification sent to Passenger ID: $passenger_id");
} else {
error_log("⚠️ [cancelRide.php] Function notifyPassengerOnRideServer not found!");
}
}
// ---------------------------------------------------------
// 4. إنهاء العملية
// ---------------------------------------------------------
error_log("✅ [cancelRide.php] Ride cancelled successfully.");
jsonSuccess(null, "Ride cancelled successfully");
} else {
// الفشل يعني أن الرحلة غير موجودة أو حالتها لا تسمح بالإلغاء (مثلاً بدأت بالفعل)
error_log("⚠️ [cancelRide.php] Failed. ID invalid OR Status not allowed (maybe started?).");
jsonError("Cannot cancel ride. Status might be started or already completed.");
}
} catch (PDOException $e) {
error_log("❌ [cancelRide.php] Database Error: " . $e->getMessage());
jsonError("Database Error");
}
?>
-186
View File
@@ -1,186 +0,0 @@
<?php
// نقوم بتضمين ملف الاتصال المعدل الذي يحتوي على $con (الرئيسي)
require_once __DIR__ . '/../../connect.php';
// تهيئة اتصال قاعدة بيانات الرحلات
try {
$con_ride = Database::get('ride');
} catch (Exception $e) {
error_log("[getRideOrderID] Failed to connect to Ride Database: " . $e->getMessage());
http_response_code(500);
echo json_encode(["status" => "failure", "message" => "Database connection failed"]);
exit;
}
// استلام البيانات (يمكن استلام ID الرحلة أو ID الراكب)
$passengerID = filterRequest("passengerID");
$rideID = filterRequest("id"); // إضافة استقبال متغير رقم الرحلة
// التحقق من أن الراكب يطلب بيانات رحلته هو فقط (حماية IDOR)
if (!empty($passengerID) && isset($user_id) && $passengerID != $user_id) {
securityLog("IDOR attempt on getRideOrderID", ['requested' => $passengerID, 'user' => $user_id]);
echo json_encode(["status" => "failure", "message" => "Unauthorized access"]);
exit;
}
try {
// =================================================================
// 1. الخطوة الأولى: تحديد استراتيجية البحث (بواسطة رقم الرحلة أو الراكب)
// =================================================================
$sqlRide = "SELECT
id,
start_location,
end_location,
date,
driver_id,
passenger_id,
price,
status,
created_at,
DriverIsGoingToPassenger,
rideTimeStart,
rideTimeFinish,
price_for_driver,
distance
FROM ride ";
// المنطق الجديد:
// إذا تم إرسال rideID، نبحث عن الرحلة المحددة بدقة (تجنباً لأي تضارب)
// إذا لم يتم إرساله، نبحث عن أحدث رحلة للراكب (للتتبع المباشر)
if (!empty($rideID)) {
$sqlRide .= "WHERE id = :rideID";
} else {
$sqlRide .= "WHERE passenger_id = :passengerID ORDER BY id DESC LIMIT 1";
}
// نستخدم المتغير $con_ride (سيرفر الرحلات)
$stmtRide = $con_ride->prepare($sqlRide);
// ربط المتغيرات حسب نوع البحث
if (!empty($rideID)) {
$stmtRide->bindParam(':rideID', $rideID);
} else {
$stmtRide->bindParam(':passengerID', $passengerID);
}
$stmtRide->execute();
$rideData = $stmtRide->fetch(PDO::FETCH_ASSOC);
// إذا لم يتم العثور على رحلة في سيرفر الرحلات، نوقف العملية
if (!$rideData) {
echo json_encode(["status" => "failure", "message" => "No ride found"]);
exit;
}
// =================================================================
// 2. الخطوة الثانية: جلب البيانات الثابتة (سائق، سيارة، تقييم) من السيرفر الرئيسي ($con)
// نستخدم المعرفات التي حصلنا عليها من نتيجة الاستعلام الأول
// =================================================================
$driverID = $rideData['driver_id'];
$pID = $rideData['passenger_id']; // نأخذ معرف الراكب من الرحلة نفسها لضمان التطابق
// ملاحظة: استخدام :driverID_Sub في الاستعلام الفرعي لتجنب أخطاء PDO
$sqlDetails = "SELECT
passengers.first_name AS passengerName,
passengers.last_name,
CarRegistration.make,
CarRegistration.model,
CarRegistration.car_plate,
CarRegistration.year,
CarRegistration.color,
CarRegistration.color_hex,
driver.first_name AS driverName,
driver.gender,
driver.phone,
(
SELECT ROUND(AVG(ratingDriver.rating), 2)
FROM ratingDriver
WHERE ratingDriver.driver_id = :driverID_Sub
) AS ratingDriver,
(
SELECT COUNT(*)
FROM ratingDriver
WHERE ratingDriver.driver_id = :driverID_Sub
) AS ratingCount,
(
SELECT COUNT(*)
FROM ride
WHERE ride.driver_id = :driverID_Sub
AND ride.status IN ('Finished', 'finished')
) AS completedRides,
driverToken.token AS token
FROM driver
LEFT JOIN passengers ON passengers.id = :passengerID
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
LEFT JOIN driverToken ON driverToken.captain_id = driver.id
WHERE driver.id = :driverID";
// نستخدم المتغير الأصلي $con للسيرفر الرئيسي
$stmtDetails = $con->prepare($sqlDetails);
// نربط المتغيرات
$stmtDetails->bindParam(':driverID', $driverID);
$stmtDetails->bindParam(':driverID_Sub', $driverID);
$stmtDetails->bindParam(':passengerID', $pID);
$stmtDetails->execute();
$detailsData = $stmtDetails->fetch(PDO::FETCH_ASSOC);
// =================================================================
// 3. الخطوة الثالثة: دمج البيانات وتجهيز الرد
// =================================================================
$finalData = [];
if ($detailsData) {
// دمج مصفوفة الرحلة (من سيرفر الرحلات) مع مصفوفة التفاصيل (من الرئيسي)
$finalData = array_merge($rideData, $detailsData);
} else {
// في حال كانت الرحلة بدون سائق بعد، نكتفي ببيانات الرحلة
$finalData = $rideData;
}
// =================================================================
// 4. فك التشفير (Decrypt)
// =================================================================
if ($finalData) {
$fieldsToDecrypt = ['driverName', 'gender', 'phone', 'car_plate', 'passengerName', 'last_name', 'token'];
foreach ($fieldsToDecrypt as $field) {
if (!empty($finalData[$field])) {
$finalData[$field] = $encryptionHelper->decryptData($finalData[$field]);
}
}
$ratingValue = (float) ($finalData['ratingDriver'] ?: 5.0);
$ratingCount = (int) ($finalData['ratingCount'] ?? 0);
$completedRides = (int) ($finalData['completedRides'] ?? 0);
if ($ratingValue >= 4.8 && $ratingCount >= 50 && $completedRides >= 100) {
$finalData['driverTier'] = 'Professional driver';
} elseif ($ratingValue >= 4.5 && $ratingCount >= 15 && $completedRides >= 30) {
$finalData['driverTier'] = 'Trusted driver';
} else {
$finalData['driverTier'] = 'Verified driver';
}
}
echo json_encode([
"status" => "success",
"data" => $finalData
]);
} catch (Exception $e) {
error_log("API Error: " . $e->getMessage());
http_response_code(500);
echo json_encode(["status" => "failure", "message" => "An internal server error occurred."]);
}
?>