Initial commit with updated Auth and media ignored

This commit is contained in:
Hamza-Ayed
2026-04-28 13:04:27 +03:00
commit 67af97474c
477 changed files with 66444 additions and 0 deletions

73
ride/location/add.php Executable file
View File

@@ -0,0 +1,73 @@
<?php
require_once __DIR__ . '/../../connect.php';
header('Content-Type: application/json; charset=utf-8');
try {
$driver_id = filterRequest("driver_id");
$latitude = filterRequest("latitude");
$longitude = filterRequest("longitude");
$status = filterRequest("status");
$heading = filterRequest("heading");
$speed = filterRequest("speed");
// distance قد تأتي باسم totalDistance من التطبيق
$distanceRaw = filterRequest("distance");
if ($distanceRaw === null || $distanceRaw === '') {
$distanceRaw = filterRequest("totalDistance");
}
// تطبيع القيم الرقمية
$norm = function($v, $default = 0.0) {
if ($v === null) return $default;
$v = str_replace(',', '.', trim((string)$v));
return is_numeric($v) ? (float)$v : $default;
};
$lat = $norm($latitude, 0.0);
$lng = $norm($longitude, 0.0);
$head = $norm($heading, 0.0);
$spd = $norm($speed, 0.0);
// ✅ قرّب لمرتين عشريتين ليتطابق مع DECIMAL(10,2)
$dist = round($norm($distanceRaw, 0.0), 2);
// ✅ حارس بسيط للمدى (اختياري)
if ($dist > 99999999.99) { $dist = 99999999.99; }
if ($dist < -99999999.99){ $dist = -99999999.99; }
if (empty($driver_id) || ($lat == 0.0 && $lng == 0.0)) {
jsonError("Invalid payload");
exit;
}
$created_at = date("Y-m-d H:i:s");
$sql = "INSERT INTO `car_tracks`
(`driver_id`,`latitude`,`longitude`,`heading`,`speed`,`distance`,`status`,`created_at`)
VALUES
(:driver_id,:latitude,:longitude,:heading,:speed,:distance,:status,:created_at)";
$stmt = $con->prepare($sql);
$ok = $stmt->execute([
':driver_id' => $driver_id,
':latitude' => $lat,
':longitude' => $lng,
':heading' => $head,
':speed' => $spd,
':distance' => $dist, // ← now DECIMAL(10,2)-friendly
':status' => (string)($status ?? 'on'),
':created_at' => $created_at,
]);
if ($ok) {
jsonSuccess(null, "car_tracks saved successfully");
} else {
jsonError("Failed to save car track");
}
} catch (PDOException $e) {
error_log("car_tracks insert error: " . $e->getMessage());
jsonError("Database error");
} catch (Throwable $e) {
error_log("car_tracks insert fatal: " . $e->getMessage());
jsonError("Server error");
}

View File

@@ -0,0 +1,34 @@
<?php
require_once __DIR__ . '/../../connect.php';
$passengerId = filterRequest("passengerId");
$lat = filterRequest("lat");
$lng = filterRequest("lng");
$rideId = filterRequest("rideId");
// Validate the latitude and longitude
if ($lat === '' || $lng === '') {
jsonError("Latitude and longitude cannot be empty");
exit;
}
// Prepare an SQL statement with placeholders
$sql = "INSERT INTO `passengerlocation`( `passengerId`, `lat`, `lng`, `rideId`) VALUES (:passengerId, :lat, :lng, :rideId)";
$stmt = $con->prepare($sql);
// Bind the parameters to the SQL query
$stmt->bindParam(':passengerId', $passengerId);
$stmt->bindParam(':lat', $lat);
$stmt->bindParam(':lng', $lng);
$stmt->bindParam(':rideId', $rideId);
// Execute the statement
if ($stmt->execute()) {
// Print a success message
jsonSuccess(null, "Passenger location saved successfully");
} else {
// Print a failure message
jsonError("Failed to save passenger location");
}
?>

20
ride/location/delete.php Normal file
View File

@@ -0,0 +1,20 @@
<?
require_once __DIR__ . '/../../connect.php';
$driver_id = filterRequest("driver_id");
$sql = "DELETE FROM `car_locations` WHERE `driver_id` = $driver_id";
$stmt = $con->prepare($sql);
$stmt->execute();
if ($stmt->rowCount() > 0) {
// Print a success message
jsonSuccess($message = "Car location deleted successfully");
} else {
// Print a failure message
jsonError($message = "Failed to delete car location");
}
?>

341
ride/location/driversTime.html Executable file
View File

@@ -0,0 +1,341 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>متابعة السائقين - انطلق</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body { font-family: 'Tajawal', sans-serif; background-color: #f8fafc; }
.driver-card { transition: all 0.2s ease; }
.driver-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); }
/* Loading Spinner */
.loader { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 20px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body class="text-slate-800">
<div class="bg-slate-900 text-white p-6 shadow-lg sticky top-0 z-50">
<div class="container mx-auto flex flex-col md:flex-row justify-between items-center gap-4">
<div>
<h1 class="text-2xl font-bold flex items-center gap-3">
<i class="fa-solid fa-taxi text-yellow-400"></i>
لوحة متابعة السائقين
</h1>
<p class="text-slate-400 text-xs mt-1 flex items-center gap-2">
<i class="fa-solid fa-clock"></i>
توقيت آخر تحديث للبيانات:
<span id="lastUpdatedBadge" class="font-bold text-yellow-400 dir-ltr">جاري التحميل...</span>
</p>
</div>
<div class="flex gap-3">
<button onclick="location.reload()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm flex items-center gap-2 transition">
<i class="fa-solid fa-rotate-right"></i>
تحديث الصفحة
</button>
</div>
</div>
</div>
<div id="loadingState" class="text-center py-20">
<div class="loader"></div>
<p class="text-gray-500 mt-4">جاري جلب ملف البيانات (active_drivers_cache.json)...</p>
</div>
<div id="errorState" class="hidden container mx-auto p-6 text-center">
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-8 rounded-lg">
<i class="fa-solid fa-triangle-exclamation text-4xl mb-4"></i>
<h3 class="font-bold text-lg">تعذر تحميل البيانات</h3>
<p class="mt-2">تأكد من وجود ملف <b>active_drivers_cache.json</b> في نفس المجلد.</p>
<p class="text-sm mt-2 text-gray-500">ملاحظة تقنية: يجب تشغيل هذا الملف عبر سيرفر محلي (Localhost) ليعمل بشكل صحيح.</p>
</div>
</div>
<div id="mainContent" class="container mx-auto p-6 hidden animate-fade-in">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div class="bg-white p-4 rounded-xl shadow-sm border-r-4 border-blue-500">
<p class="text-gray-500 text-xs font-bold">إجمالي السائقين</p>
<h3 class="text-2xl font-bold text-blue-700" id="statTotal">0</h3>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border-r-4 border-yellow-500">
<p class="text-gray-500 text-xs font-bold">العمالقة (النخبة)</p>
<h3 class="text-2xl font-bold text-yellow-600" id="statElite">0</h3>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border-r-4 border-red-500">
<p class="text-gray-500 text-xs font-bold">الخاملون (للاتصال)</p>
<h3 class="text-2xl font-bold text-red-600" id="statInactive">0</h3>
</div>
<div class="bg-white p-4 rounded-xl shadow-sm border-r-4 border-green-500">
<p class="text-gray-500 text-xs font-bold">أعلى نشاط مسجل</p>
<h3 class="text-xl font-bold text-green-600 dir-ltr" id="statMaxTime">0</h3>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<div class="bg-white p-5 rounded-xl shadow-sm border border-gray-100">
<h3 class="font-bold text-sm mb-4 text-gray-700">توزيع حالة السائقين</h3>
<div class="h-48">
<canvas id="driversChart"></canvas>
</div>
</div>
<div class="bg-white p-5 rounded-xl shadow-sm border border-gray-100 lg:col-span-2 flex flex-col">
<h3 class="font-bold text-sm mb-4 text-gray-700">تصفية وفرز القائمة</h3>
<div class="flex flex-wrap gap-2 mb-4" id="filterButtons">
<button onclick="filterData('all')" class="px-3 py-1.5 rounded-md bg-slate-800 text-white text-xs hover:bg-slate-700 ring-2 ring-offset-1 ring-slate-800 active-filter">الكل</button>
<button onclick="filterData('elite')" class="px-3 py-1.5 rounded-md bg-yellow-500 text-white text-xs hover:bg-yellow-600">النخبة (+50س)</button>
<button onclick="filterData('stable')" class="px-3 py-1.5 rounded-md bg-green-500 text-white text-xs hover:bg-green-600">مستقرون</button>
<button onclick="filterData('experimental')" class="px-3 py-1.5 rounded-md bg-blue-400 text-white text-xs hover:bg-blue-500">تجريبيون</button>
<button onclick="filterData('inactive')" class="px-3 py-1.5 rounded-md bg-red-500 text-white text-xs hover:bg-red-600">خاملون (أولوية)</button>
</div>
<div class="relative mt-auto w-full">
<input type="text" id="searchInput" placeholder="ابحث عن اسم أو رقم..."
class="w-full p-2.5 pr-10 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none">
<i class="fa-solid fa-magnifying-glass absolute top-3 right-3 text-gray-400"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm text-right">
<thead class="bg-gray-50 text-gray-600 border-b">
<tr>
<th class="px-6 py-3 font-bold">السائق</th>
<th class="px-6 py-3 font-bold">الهاتف</th>
<th class="px-6 py-3 font-bold cursor-pointer hover:text-blue-600" onclick="sortData('minutes')">
الأداء (ساعات) <i class="fa-solid fa-sort"></i>
</th>
<th class="px-6 py-3 font-bold">الحالة</th>
<th class="px-6 py-3 text-center font-bold">اتصال</th>
</tr>
</thead>
<tbody id="tableBody" class="divide-y divide-gray-100">
</tbody>
</table>
</div>
<div class="p-3 bg-gray-50 text-xs text-gray-500 flex justify-between">
<span id="showingCount"></span>
</div>
</div>
</div>
<script>
const JSON_FILE_NAME = 'active_drivers_cache.json';
let rawData = [];
let filteredData = [];
let chartInstance = null;
// عند تحميل الصفحة
document.addEventListener('DOMContentLoaded', () => {
fetchData();
});
// دالة جلب البيانات تلقائياً
async function fetchData() {
try {
// إضافة طابع زمني لمنع الكاش وضمان جلب أحدث ملف
const timestamp = new Date().getTime();
const response = await fetch(`${JSON_FILE_NAME}?t=${timestamp}`);
if (!response.ok) throw new Error('Network response was not ok');
const json = await response.json();
// إخفاء اللودر وإظهار المحتوى
document.getElementById('loadingState').classList.add('hidden');
document.getElementById('mainContent').classList.remove('hidden');
// تحديث تاريخ آخر تحديث في الهيدر
if(json.last_updated) {
document.getElementById('lastUpdatedBadge').innerText = json.last_updated;
}
processData(json);
} catch (error) {
console.error('Error fetching data:', error);
document.getElementById('loadingState').classList.add('hidden');
document.getElementById('errorState').classList.remove('hidden');
}
}
function processData(json) {
rawData = json.data.map(driver => {
const timeStr = driver.active_time || "0 ساعة و 0 دقيقة";
const hoursMatch = timeStr.match(/(\d+)\s*ساعة/);
const minsMatch = timeStr.match(/(\d+)\s*دقيقة/);
const hours = hoursMatch ? parseInt(hoursMatch[1]) : 0;
const mins = minsMatch ? parseInt(minsMatch[1]) : 0;
const totalMinutes = (hours * 60) + mins;
// تصنيف السائقين
let category = 'inactive';
let catLabel = 'خامل';
let catColor = 'bg-red-100 text-red-700 border border-red-200';
if (totalMinutes >= 3000) { // 50 hours
category = 'elite';
catLabel = 'نخبة';
catColor = 'bg-yellow-100 text-yellow-700 border border-yellow-200';
} else if (totalMinutes >= 1200) { // 20 hours
category = 'stable';
catLabel = 'مستقر';
catColor = 'bg-green-100 text-green-700 border border-green-200';
} else if (totalMinutes >= 300) { // 5 hours
category = 'experimental';
catLabel = 'تجريبي';
catColor = 'bg-blue-100 text-blue-700 border border-blue-200';
}
return { ...driver, totalMinutes, hours, mins, category, catLabel, catColor };
});
// الترتيب الافتراضي: حسب الأكثر نشاطاً
rawData.sort((a, b) => b.totalMinutes - a.totalMinutes);
updateStats();
filterData('all');
}
function updateStats() {
const total = rawData.length;
const elite = rawData.filter(d => d.category === 'elite').length;
const inactive = rawData.filter(d => d.category === 'inactive').length;
document.getElementById('statTotal').innerText = total;
document.getElementById('statElite').innerText = elite;
document.getElementById('statInactive').innerText = inactive;
if(rawData.length > 0) {
document.getElementById('statMaxTime').innerText = rawData[0].active_time;
}
// Chart.js
const ctx = document.getElementById('driversChart').getContext('2d');
if (chartInstance) chartInstance.destroy();
const stable = rawData.filter(d => d.category === 'stable').length;
const exp = rawData.filter(d => d.category === 'experimental').length;
chartInstance = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['النخبة', 'مستقر', 'تجريبي', 'خامل'],
datasets: [{
data: [elite, stable, exp, inactive],
backgroundColor: ['#EAB308', '#22C55E', '#60A5FA', '#EF4444'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'left', labels: { font: { family: 'Tajawal', size: 10 }, boxWidth: 10 } }
}
}
});
}
function filterData(type) {
// Update UI Buttons
document.querySelectorAll('#filterButtons button').forEach(btn => {
btn.classList.remove('ring-2', 'ring-offset-1', 'ring-slate-800');
if(btn.getAttribute('onclick').includes(`'${type}'`)) {
btn.classList.add('ring-2', 'ring-offset-1', 'ring-slate-800');
}
});
filteredData = type === 'all' ? rawData : rawData.filter(d => d.category === type);
document.getElementById('searchInput').value = '';
renderTable(filteredData);
}
// البحث
document.getElementById('searchInput').addEventListener('input', (e) => {
const term = e.target.value.toLowerCase();
const currentData = filteredData;
// ملاحظة: البحث يتم داخل الفئة المختارة حالياً
const searchResults = filteredData.filter(driver =>
(driver.name_arabic && driver.name_arabic.toLowerCase().includes(term)) ||
(driver.phone && driver.phone.includes(term))
);
renderTable(searchResults);
});
function renderTable(data) {
const tbody = document.getElementById('tableBody');
tbody.innerHTML = '';
if (data.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="text-center py-8 text-gray-400">لا توجد بيانات</td></tr>`;
document.getElementById('showingCount').innerText = '';
return;
}
data.forEach(driver => {
const createdDate = driver.created_at ? driver.created_at.split(' ')[0] : '-';
const row = `
<tr class="hover:bg-slate-50 driver-card bg-white">
<td class="px-6 py-3">
<div class="flex flex-col">
<span class="font-bold text-slate-700">${driver.name_arabic || 'غير مسمى'}</span>
<span class="text-[10px] text-gray-400">انضم: ${createdDate}</span>
</div>
</td>
<td class="px-6 py-3 font-mono text-xs text-gray-600 dir-ltr text-right select-all">
${driver.phone}
</td>
<td class="px-6 py-3">
<div class="flex flex-col gap-1">
<div class="w-full bg-gray-100 rounded-full h-2 overflow-hidden">
<div class="h-2 ${driver.category === 'elite' ? 'bg-yellow-400' : driver.category === 'inactive' ? 'bg-red-400' : 'bg-blue-400'}" style="width: ${Math.min((driver.totalMinutes / 6000) * 100, 100)}%"></div>
</div>
<span class="text-[10px] text-gray-500 font-bold">${driver.active_time}</span>
</div>
</td>
<td class="px-6 py-3">
<span class="${driver.catColor} px-2 py-0.5 rounded text-[10px] font-bold inline-block min-w-[50px] text-center">
${driver.catLabel}
</span>
</td>
<td class="px-6 py-3 text-center">
<div class="flex justify-center gap-2">
<a href="https://wa.me/${driver.phone}" target="_blank" class="w-8 h-8 rounded-full bg-green-100 text-green-600 hover:bg-green-600 hover:text-white flex items-center justify-center transition">
<i class="fa-brands fa-whatsapp"></i>
</a>
<a href="tel:${driver.phone}" class="w-8 h-8 rounded-full bg-blue-100 text-blue-600 hover:bg-blue-600 hover:text-white flex items-center justify-center transition">
<i class="fa-solid fa-phone"></i>
</a>
</div>
</td>
</tr>
`;
tbody.insertAdjacentHTML('beforeend', row);
});
document.getElementById('showingCount').innerText = `عرض ${data.length} من أصل ${rawData.length}`;
}
let sortDir = 'desc';
function sortData(key) {
sortDir = sortDir === 'desc' ? 'asc' : 'desc';
filteredData.sort((a, b) => sortDir === 'asc' ? a.totalMinutes - b.totalMinutes : b.totalMinutes - a.totalMinutes);
renderTable(filteredData);
}
</script>
</body>
</html>

188
ride/location/get.php Executable file
View File

@@ -0,0 +1,188 @@
<?php
// get.php (Main Server)
// 1. تضمين ملف الاتصال والدوال المساعدة (مهم جداً)
require_once __DIR__ . '/../../connect.php';
// ضبط التوقيت (للسجلات فقط، قاعدة البيانات تبقى UTC)
// date_default_timezone_set('Asia/Amman');
try {
// ==========================================
// 1. استقبال الإحداثيات (دعم الطريقتين)
// ==========================================
$lat = filterRequest("lat");
$lng = filterRequest("lng");
// دعم Bounds القديم (تحويله لمركز وقطر)
if (!$lat || !$lng) {
$swLat = filterRequest("southwestLat"); $neLat = filterRequest("northeastLat");
$swLon = filterRequest("southwestLon"); $neLon = filterRequest("northeastLon");
if ($swLat && $neLat && $swLon && $neLon) {
$lat = ($swLat + $neLat) / 2;
$lng = ($swLon + $neLon) / 2;
} else {
jsonError("Invalid coordinates provided");
exit;
}
}
// ==========================================
// 2. طلب الـ IDs والمواقع من سيرفر اللوكيشن (Redis API)
// ==========================================
$locationServerUrl = "https://location.intaleq.xyz/api_get_nearby.php";
// تأكد من المسار الصحيح للمفتاح على السيرفر الرئيسي
$INTERNAL_KEY = trim(file_get_contents('/home/intaleq-api/.internal_socket_key'));
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $locationServerUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'lat' => $lat,
'lng' => $lng,
'radius' => 5, // 5 كم كافية للعرض
'limit' => 50
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $INTERNAL_KEY"]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$redisDrivers = [];
if ($httpCode == 200 && $response) {
$json = json_decode($response, true);
if (isset($json['status']) && $json['status'] === true) {
$redisDrivers = $json['data'];
}
}
if (empty($redisDrivers)) {
jsonSuccess([]);
exit;
}
// تجهيز خريطة لدمج البيانات لاحقاً (ID => RedisData)
$driversMap = [];
$driverIds = [];
foreach ($redisDrivers as $d) {
$driverIds[] = $d['id'];
$driversMap[$d['id']] = $d;
}
// ==========================================
// 3. جلب التفاصيل الكاملة من MySQL (مثل الملف القديم تماماً)
// ==========================================
// تجهيز الـ Placeholders
$placeholders = implode(',', array_fill(0, count($driverIds), '?'));
// الاستعلام الشامل (نفس الحقول القديمة)
$sql_drivers_info = "
SELECT
d.id AS driver_id,
d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
COALESCE(cr.make, d.make, '') AS make,
cr.car_plate,
COALESCE(cr.model, d.model, '') AS model,
cr.color, cr.vin, cr.color_hex,
COALESCE(cr.year, d.year, 0) AS year,
cr.vehicle_category_id,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
AND COALESCE(cr.year, d.year, 0) > 2000
AND (COALESCE(cr.make, d.make, '') NOT LIKE '%دراج%' AND COALESCE(cr.model, d.model, '') NOT LIKE '%دراج%')
";
$stmt = $con->prepare($sql_drivers_info);
$stmt->execute($driverIds);
$drivers_db = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($drivers_db)) {
jsonSuccess([], "No matching drivers in DB");
exit;
}
// ==========================================
// 4. معالجة البيانات، الدمج، وفك التشفير
// ==========================================
$final_result = [];
$serverNow = date('Y-m-d H:i:s');
$fieldsToDecrypt = ['phone','email','gender','birthdate','first_name','last_name','token','car_plate','vin'];
// الهاش الخاص بالإناث (لتحديد النوع لاحقاً إذا لزم الأمر)
// $femaleHash = 'bQ6yWJ2EVXKZooHdGclvmFiDlZCM8UYeO+ILFjDUvpQ=';
foreach ($drivers_db as $row) {
$did = $row['driver_id'];
// دمج بيانات الموقع الحية من الريدز (أهم خطوة)
if (isset($driversMap[$did])) {
$redisInfo = $driversMap[$did];
$row['latitude'] = $redisInfo['lat'];
$row['longitude'] = $redisInfo['lng'];
$row['heading'] = $redisInfo['heading'];
$row['speed'] = $redisInfo['speed'];
// $row['distance'] = $redisInfo['distance']; // إذا أردت إضافتها
} else {
// حالة نادرة: السائق موجود في الاستعلام ولكن ليس في مصفوفة الريدز (لا يجب أن تحدث)
continue;
}
$row['serverNow'] = $serverNow;
// فك التشفير (Decrypt)
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && $row[$field] !== null && $row[$field] !== '') {
try {
$row[$field] = $encryptionHelper->decryptData($row[$field]);
} catch (Exception $e) {
$row[$field] = null;
}
}
}
// حساب العمر
if (!empty($row['birthdate'])) {
try {
$birthdate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthdate)->y;
} catch (Exception $e) { $row['age'] = null; }
} else {
$row['age'] = null;
}
// إضافة نوع السيارة البسيط (اختياري، إذا كان التطبيق يعتمد عليه)
/*
$type = 'car';
if ($row['vehicle_category_id'] == 2) $type = 'bike';
elseif ($row['gender'] == 'female') $type = 'lady'; // بعد فك التشفير تكون female
$row['type'] = $type;
*/
$final_result[] = $row;
}
// إرجاع النتيجة بنفس الهيكل القديم
jsonSuccess($final_result);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}
?>

166
ride/location/getBalash.php Executable file
View File

@@ -0,0 +1,166 @@
<?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
//getBalash.php
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
jsonError("Invalid coordinates provided");
exit;
}
$freshSeconds = 180; // 3 دقائق
// =================================================================
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
// =================================================================
// إعادة استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
// ملاحظة: نزيل LIMIT 5 من هنا ونجلب مجموعة أكبر من المرشحين
// لأن الترتيب النهائي يعتمد على بيانات من القاعدة الأخرى
$sql_locations = "
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
FROM car_locations
WHERE
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
AND status = 'off'
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
ORDER BY updated_at DESC
LIMIT 20; -- نجلب 100 مرشح محتمل للفلترة والترتيب لاحقاً
";
$stmt_locations = $con_tracking->prepare($sql_locations);
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt_locations->execute();
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
if (!$locations) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 2: تجميع معرفات السائقين (driver_id)
// =================================================================
$driver_ids = array_column($locations, 'driver_id');
// =================================================================
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
// =================================================================
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
// هنا نطبق الشروط الخاصة بهذا السكريبت (موديل السيارة < 2000)
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
AND COALESCE(cr.year, 0) < 2000 -- ⭐ الشرط الخاص بهذا السكريبت
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
";
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
}
// =================================================================
// الخطوة 4: دمج النتائج في PHP
// =================================================================
$final_results = [];
foreach ($locations as $location) {
$driver_id = $location['driver_id'];
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
if (isset($drivers_info[$driver_id])) {
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
}
}
// =================================================================
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
// =================================================================
// الآن بعد أن دمجنا كل البيانات، يمكننا تطبيق الترتيب المعقد
usort($final_results, function ($a, $b) {
// الترتيب الأول: حسب التقييم (تنازلي)
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
}
// الترتيب الثاني: حسب عدد التقييمات (تنازلي)
if ($a['ratingCount'] != $b['ratingCount']) {
return $b['ratingCount'] <=> $a['ratingCount'];
}
// الترتيب الثالث: حسب حداثة الموقع (تنازلي)
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
});
// وأخيراً، نأخذ أفضل 5 نتائج فقط
$limited_results = array_slice($final_results, 0, 5);
if (empty($limited_results)) {
jsonError("No cars matching the specific criteria (year < 2000) found.");
exit;
}
// =================================================================
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
// =================================================================
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
catch (Exception $e) { $row[$field] = null; }
}
}
if (!empty($row['birthdate'])) {
try {
$birthDate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthDate)->y;
} catch (Exception $e) { $row['age'] = null; }
} else {
$row['age'] = null;
}
}
unset($row);
jsonSuccess($limited_results);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,160 @@
<?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
jsonError("Invalid coordinates provided");
exit;
}
$freshSeconds = 180; // 3 دقائق
// =================================================================
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
// =================================================================
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
$sql_locations = "
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
FROM car_locations
WHERE
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
AND status = 'off'
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
ORDER BY updated_at DESC
LIMIT 100; -- نجلب 100 مرشح محتمل
";
$stmt_locations = $con_tracking->prepare($sql_locations);
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt_locations->execute();
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
if (!$locations) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 2: تجميع معرفات السائقين (driver_id)
// =================================================================
$driver_ids = array_column($locations, 'driver_id');
// =================================================================
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
// =================================================================
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
// هنا نطبق الشروط الخاصة بهذا السكريبت (سيارات كهربائية فقط)
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
cr.make, cr.model, cr.color, cr.color_hex, cr.year, cr.fuel,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(*) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
AND cr.make = 'Van'or cr.model='Van'
";
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
}
// =================================================================
// الخطوة 4: دمج النتائج في PHP
// =================================================================
$final_results = [];
foreach ($locations as $location) {
$driver_id = $location['driver_id'];
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
if (isset($drivers_info[$driver_id])) {
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
}
}
// =================================================================
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
// =================================================================
usort($final_results, function ($a, $b) {
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
}
if ($a['ratingCount'] != $b['ratingCount']) {
return $b['ratingCount'] <=> $a['ratingCount'];
}
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
});
// وأخيراً، نأخذ أفضل 10 نتائج فقط
$limited_results = array_slice($final_results, 0, 10);
if (empty($limited_results)) {
jsonError("No electric cars matching the criteria found.");
exit;
}
// =================================================================
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
// =================================================================
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
catch (Exception $e) { $row[$field] = null; }
}
}
if (!empty($row['birthdate'])) {
try {
$birthDate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthDate)->y;
} catch (Exception $e) { $row['age'] = null; }
} else {
$row['age'] = null;
}
}
unset($row);
jsonSuccess($limited_results);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}

170
ride/location/getComfort.php Executable file
View File

@@ -0,0 +1,170 @@
<?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
//getComfort.php
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
jsonError("Invalid coordinates provided");
exit;
}
$freshSeconds = 180; // 3 دقائق
// =================================================================
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
// =================================================================
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
$sql_locations = "
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
FROM car_locations
WHERE
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
AND status = 'off'
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
ORDER BY updated_at DESC
LIMIT 100; -- نجلب 100 مرشح محتمل
";
$stmt_locations = $con_tracking->prepare($sql_locations);
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt_locations->execute();
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
if (!$locations) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 2: تجميع معرفات السائقين (driver_id)
// =================================================================
$driver_ids = array_column($locations, 'driver_id');
// =================================================================
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
// =================================================================
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
// هنا نطبق الشروط الخاصة بهذا السكريبت (موديل السيارة > 2017)
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
FROM driver d
-- 1. تغيير LEFT JOIN إلى INNER JOIN لضمان عدم جلب سائق إلا إذا كانت بيانات سيارته مطابقة تماماً
INNER JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(*) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
-- 2. الفلترة الصارمة للسنة
AND cr.year IS NOT NULL
AND TRIM(cr.year) != ''
AND CAST(TRIM(cr.year) AS UNSIGNED) > 2017
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
";
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
}
// =================================================================
// الخطوة 4: دمج النتائج في PHP
// =================================================================
$final_results = [];
foreach ($locations as $location) {
$driver_id = $location['driver_id'];
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
if (isset($drivers_info[$driver_id])) {
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
}
}
// =================================================================
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
// =================================================================
// الآن بعد أن دمجنا كل البيانات، يمكننا تطبيق الترتيب المعقد
usort($final_results, function ($a, $b) {
// الترتيب الأول: حسب التقييم (تنازلي)
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
}
// الترتيب الثاني: حسب عدد التقييمات (تنازلي)
if ($a['ratingCount'] != $b['ratingCount']) {
return $b['ratingCount'] <=> $a['ratingCount'];
}
// الترتيب الثالث: حسب حداثة الموقع (تنازلي)
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
});
// وأخيراً، نأخذ أفضل 5 نتائج فقط
$limited_results = array_slice($final_results, 0, 5);
if (empty($limited_results)) {
jsonError("No cars matching the specific criteria (year > 2017) found.");
exit;
}
// =================================================================
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
// =================================================================
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
catch (Exception $e) { $row[$field] = null; }
}
}
if (!empty($row['birthdate'])) {
try {
$birthDate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthDate)->y;
} catch (Exception $e) { $row['age'] = null; }
} else {
$row['age'] = null;
}
}
unset($row);
jsonSuccess($limited_results);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}

159
ride/location/getDelivery.php Executable file
View File

@@ -0,0 +1,159 @@
<?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
//getDelivery.php
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
jsonError("Invalid coordinates provided");
exit;
}
$freshSeconds = 180; // 3 دقائق
// =================================================================
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
// =================================================================
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
$sql_locations = "
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
FROM car_locations
WHERE
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
AND status = 'off'
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
ORDER BY updated_at DESC
LIMIT 100; -- نجلب 100 مرشح محتمل
";
$stmt_locations = $con_tracking->prepare($sql_locations);
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt_locations->execute();
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
if (!$locations) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 2: تجميع معرفات السائقين (driver_id)
// =================================================================
$driver_ids = array_column($locations, 'driver_id');
// =================================================================
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
// =================================================================
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
// هنا نطبق الشروط الخاصة بهذا السكريبت (دراجات فقط)
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(*) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
AND (cr.make LIKE '%دراج%' OR cr.model LIKE '%دراج%') -- ⭐ الشرط الخاص بهذا السكريبت
";
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
}
// =================================================================
// الخطوة 4: دمج النتائج في PHP
// =================================================================
$final_results = [];
foreach ($locations as $location) {
$driver_id = $location['driver_id'];
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
if (isset($drivers_info[$driver_id])) {
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
}
}
// =================================================================
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
// =================================================================
usort($final_results, function ($a, $b) {
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
}
if ($a['ratingCount'] != $b['ratingCount']) {
return $b['ratingCount'] <=> $a['ratingCount'];
}
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
});
// وأخيراً، نأخذ أفضل 10 نتائج فقط
$limited_results = array_slice($final_results, 0, 10);
if (empty($limited_results)) {
jsonError("No motorcycles matching the criteria found.");
exit;
}
// =================================================================
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
// =================================================================
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name','maritalStatus', 'token','make','car_plate','vin' ];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
catch (Exception $e) { $row[$field] = null; }
}
}
if (!empty($row['birthdate'])) {
try {
$birthDate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthDate)->y;
} catch (Exception $e) { $row['age'] = null; }
} else {
$row['age'] = null;
}
}
unset($row);
jsonSuccess($limited_results);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,91 @@
<?php
// تأكد من أن ملف connect.php يقوم بتهيئة اتصالين:
// $con -> يتصل بقاعدة البيانات الأساسية (driver, CarRegistration)
// $con_tracking -> يتصل بقاعدة بيانات التتبع (car_locations)
// وأنه يحتوي على كائن التشفير $encryptionHelper
require_once __DIR__ . '/../../connect.php';
try {
$driver_id = filterRequest("driver_id");
if ($driver_id === false || empty($driver_id)) {
jsonError("Invalid driver_id provided");
exit;
}
// =================================================================
// الخطوة 1: جلب آخر موقع للسائق من قاعدة بيانات التتبع
// =================================================================
$sql_location = "SELECT
driver_id,
latitude,
longitude,
heading,
speed,
status,
created_at,
updated_at
FROM
car_locations
WHERE
driver_id = ?
ORDER BY
updated_at DESC
LIMIT 1";
$stmt_location = $con_tracking->prepare($sql_location);
$stmt_location->execute([$driver_id]);
$location_data = $stmt_location->fetch(PDO::FETCH_ASSOC);
// إذا لم نجد أي موقع، لا داعي لإكمال البحث
if (empty($location_data)) {
jsonError("No car locations found");
exit;
}
// =================================================================
// الخطوة 2: جلب البيانات الثابتة للسائق من القاعدة الأساسية
// =================================================================
$sql_driver_info = "SELECT
d.gender,
cr.model
FROM
driver d
LEFT JOIN CarRegistration cr ON d.id = cr.driverID
WHERE
d.id = ?";
$stmt_driver_info = $con->prepare($sql_driver_info);
$stmt_driver_info->execute([$driver_id]);
// نستخدم fetch وليس fetchAll لأننا نتوقع سائق واحد فقط
$driver_info = $stmt_driver_info->fetch(PDO::FETCH_ASSOC);
// =================================================================
// الخطوة 2.5: فك تشفير الجندر (New Step)
// =================================================================
if (!empty($driver_info)) {
// التحقق من وجود قيمة في حقل الجندر قبل فك تشفيرها
if (isset($driver_info['gender']) && !empty($driver_info['gender'])) {
$driver_info['gender'] = $encryptionHelper->decryptData($driver_info['gender']);
}
} else {
$driver_info = []; // اجعله مصفوفة فارغة لتجنب خطأ في الدمج
}
// =================================================================
// الخطوة 3: دمج النتائج (Application-Side Join)
// =================================================================
// دمج بيانات الموقع مع بيانات السائق (التي تم فك تشفيرها الآن)
$final_result = array_merge($location_data, $driver_info);
// إرجاع النتيجة داخل مصفوفة كما في السكربت الأصلي
jsonSuccess([$final_result]);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}
?>

View File

@@ -0,0 +1,130 @@
<?php
// getDriverTimeOnline.php
// الغرض: توليد تقرير نشاط السائقين لآخر 10 أيام بناءً على الملخص اليومي
require_once __DIR__ . '/../../get_connect.php';
header('Content-Type: application/json; charset=utf-8');
// إعدادات التقرير
$daysToLookBack = 10; // عدد الأيام في الماضي
$jsonFileName = 'active_drivers_cache.json';
$savePath = __DIR__ . '/' . $jsonFileName;
try {
// =================================================================
// 1. جلب البيانات من جدول الملخصات (سيرفر اللوكيشن) ⚡
// =================================================================
// نجمع الثواني لكل سائق خلال الفترة المحددة
$sql_summary = "
SELECT
driver_id,
SUM(total_seconds) as grand_total_seconds,
COUNT(DISTINCT date) as days_worked -- (اختياري) معرفة عدد أيام العمل الفعلية
FROM driver_daily_summary
WHERE date >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
GROUP BY driver_id
HAVING grand_total_seconds > 60 -- (اختياري) تجاهل من عمل أقل من دقيقة
ORDER BY grand_total_seconds DESC
";
$stmt = $con_tracking->prepare($sql_summary);
$stmt->execute([$daysToLookBack]);
$summary_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($summary_data)) {
// حفظ ملف فارغ وإنهاء
saveJsonFile($savePath, ["last_updated" => date('Y-m-d H:i:s'), "data" => []]);
printSuccess("No active drivers found in summary.", $savePath);
exit;
}
// =================================================================
// 2. جلب تفاصيل السائقين (الأسماء) من السيرفر الرئيسي 📝
// =================================================================
// استخراج الـ IDs
$driver_ids = array_column($summary_data, 'driver_id');
// تجهيز الـ Placeholders (?,?,?)
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
$sql_drivers = "SELECT id, name_arabic, phone, created_at FROM driver WHERE id IN ($placeholders)";
$stmt_d = $con->prepare($sql_drivers);
$stmt_d->execute($driver_ids);
$drivers_raw = $stmt_d->fetchAll(PDO::FETCH_ASSOC);
// تحويل البيانات لـ Map لسرعة الدمج: [id => data]
$drivers_map = [];
foreach ($drivers_raw as $d) {
$drivers_map[$d['id']] = $d;
}
// =================================================================
// 3. دمج البيانات وفك التشفير وتنسيق الوقت 🔄
// =================================================================
$final_report = [];
$fieldsToDecrypt = ['phone', 'name_arabic']; // الحقول المشفرة
foreach ($summary_data as $row) {
$did = $row['driver_id'];
$seconds = $row['grand_total_seconds'];
// البيانات الشخصية
$personalData = isset($drivers_map[$did]) ? $drivers_map[$did] : ['name_arabic' => 'Unknown', 'phone' => ''];
// فك التشفير
foreach ($fieldsToDecrypt as $field) {
if (!empty($personalData[$field])) {
try {
$personalData[$field] = $encryptionHelper->decryptData($personalData[$field]);
} catch (Exception $e) {
// ابقها مشفرة أو ضع قيمة افتراضية عند الفشل
}
}
}
// تنسيق الوقت (مقروء للبشر)
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
$human_time = sprintf("%d ساعة و %d دقيقة", $hours, $minutes);
// بناء الصف النهائي
$final_report[] = [
'driver_id' => $did,
'name' => $personalData['name_arabic'],
'phone' => $personalData['phone'],
'join_date' => $personalData['created_at'], // تاريخ انضمام السائق
'total_seconds' => $seconds,
'active_time' => $human_time,
'days_active' => $row['days_worked'] // عدد الأيام التي عمل فيها خلال الـ 10 أيام
];
}
// =================================================================
// 4. الحفظ والنشر 💾
// =================================================================
$output = [
"last_updated" => date('Y-m-d H:i:s'),
"period_days" => $daysToLookBack,
"total_drivers" => count($final_report),
"data" => $final_report
];
saveJsonFile($savePath, $output);
printSuccess("Report generated based on Daily Summary.", $savePath);
} catch (Exception $e) {
jsonError("Error: " . $e->getMessage());
}
// --- دوال مساعدة ---
function saveJsonFile($path, $data) {
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
file_put_contents($path, $json);
}
?>

161
ride/location/getElectric.php Executable file
View File

@@ -0,0 +1,161 @@
<?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
//getElectric.php
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
jsonError("Invalid coordinates provided");
exit;
}
$freshSeconds = 180; // 3 دقائق
// =================================================================
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
// =================================================================
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
$sql_locations = "
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
FROM car_locations
WHERE
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
AND status = 'off'
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
ORDER BY updated_at DESC
LIMIT 100; -- نجلب 100 مرشح محتمل
";
$stmt_locations = $con_tracking->prepare($sql_locations);
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt_locations->execute();
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
if (!$locations) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 2: تجميع معرفات السائقين (driver_id)
// =================================================================
$driver_ids = array_column($locations, 'driver_id');
// =================================================================
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
// =================================================================
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
// هنا نطبق الشروط الخاصة بهذا السكريبت (سيارات كهربائية فقط)
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
cr.make, cr.model, cr.color, cr.color_hex, cr.year, cr.fuel,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(*) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
AND cr.fuel = 'كهربائي'
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
";
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
}
// =================================================================
// الخطوة 4: دمج النتائج في PHP
// =================================================================
$final_results = [];
foreach ($locations as $location) {
$driver_id = $location['driver_id'];
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
if (isset($drivers_info[$driver_id])) {
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
}
}
// =================================================================
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
// =================================================================
usort($final_results, function ($a, $b) {
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
}
if ($a['ratingCount'] != $b['ratingCount']) {
return $b['ratingCount'] <=> $a['ratingCount'];
}
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
});
// وأخيراً، نأخذ أفضل 10 نتائج فقط
$limited_results = array_slice($final_results, 0, 10);
if (empty($limited_results)) {
jsonError("No electric cars matching the criteria found.");
exit;
}
// =================================================================
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
// =================================================================
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
catch (Exception $e) { $row[$field] = null; }
}
}
if (!empty($row['birthdate'])) {
try {
$birthDate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthDate)->y;
} catch (Exception $e) { $row['age'] = null; }
} else {
$row['age'] = null;
}
}
unset($row);
jsonSuccess($limited_results);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}

160
ride/location/getFemalDriver.php Executable file
View File

@@ -0,0 +1,160 @@
<?php
//getFemalDriver.php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
jsonError("Invalid coordinates provided");
exit;
}
$freshSeconds = 180; // 3 دقائق
// =================================================================
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
// =================================================================
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
$sql_locations = "
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
FROM car_locations
WHERE
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
AND status = 'off'
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
ORDER BY updated_at DESC
LIMIT 100; -- نجلب 100 مرشح محتمل
";
$stmt_locations = $con_tracking->prepare($sql_locations);
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt_locations->execute();
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
if (!$locations) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 2: تجميع معرفات السائقين (driver_id)
// =================================================================
$driver_ids = array_column($locations, 'driver_id');
// =================================================================
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
// =================================================================
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
// هنا نطبق الشروط الخاصة بهذا السكريبت (سائقات إناث فقط)
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
dt.token,
COALESCE(AVG(rd.rating), 0) AS ratingDriver,
COUNT(rd.id) AS ratingCount
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN ratingDriver rd ON rd.driver_id = d.id
WHERE d.id IN ($placeholders)
AND d.gender = 'Female' -- ⭐ الشرط الخاص بهذا السكريبت
AND (cr.make NOT LIKE '%دراجة%' AND cr.model NOT LIKE '%دراجة%')
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
GROUP BY d.id -- تجميع النتائج حسب السائق لحساب التقييم بشكل صحيح
";
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
}
// =================================================================
// الخطوة 4: دمج النتائج في PHP
// =================================================================
$final_results = [];
foreach ($locations as $location) {
$driver_id = $location['driver_id'];
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني (أنثى)
if (isset($drivers_info[$driver_id])) {
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
}
}
// =================================================================
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
// =================================================================
usort($final_results, function ($a, $b) {
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
}
if ($a['ratingCount'] != $b['ratingCount']) {
return $b['ratingCount'] <=> $a['ratingCount'];
}
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
});
// وأخيراً، نأخذ أفضل 10 نتائج فقط
$limited_results = array_slice($final_results, 0, 10);
if (empty($limited_results)) {
jsonError("No female drivers matching the criteria found.");
exit;
}
// =================================================================
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
// =================================================================
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
try { $row[$field] = $encryptionHelper->decryptData($row[
$field]); }
catch (Exception $e) { $row[$field] = null; }
}
}
if (!empty($row['birthdate'])) {
try {
$birthDate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthDate)->y;
} catch (Exception $e) { $row['age'] = null; }
} else {
$row['age'] = null;
}
}
unset($row);
jsonSuccess($limited_results);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,29 @@
<?php
require_once __DIR__ . '/../../connect.php';
$rideId = filterRequest("rideId");
$sql = "SELECT
*
FROM
`passengerlocation` pl
WHERE
pl.rideId = :rideId
ORDER BY
pl.createdAt
DESC
LIMIT 1";
$stmt = $con->prepare($sql);
$stmt->execute([':rideId' => $rideId]);
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
// Print the car location data as JSON
jsonSuccess($data = $car_locations);
} else {
// Print a failure message
jsonError($message = "No car locations found");
}
?>

View File

@@ -0,0 +1,88 @@
<?php
// تأكد من أن ملف connect.php يقوم بتهيئة اتصالين:
// $con -> يتصل بقاعدة البيانات الأساسية (driver, CarRegistration)
// $con_tracking -> يتصل بقاعدة بيانات التتبع (car_locations)
require_once __DIR__ . '/../../connect.php';
try {
$driver_id = filterRequest("driver_id");
if ($driver_id === false || empty($driver_id)) {
jsonError("Invalid driver_id provided");
exit;
}
// =================================================================
// الخطوة 1: جلب آخر موقع للسائق من قاعدة بيانات التتبع
// =================================================================
// هذا الاستعلام يعمل على قاعدة بيانات التتبع السريعة
// (ملاحظة: تم التغيير إلى ORDER BY updated_at لجلب آخر تحديث)
$sql_location = "SELECT
id,
driver_id,
latitude,
longitude,
heading,
speed,
status,
created_at,
updated_at
FROM
car_locations
WHERE
driver_id = ?
ORDER BY
updated_at DESC
LIMIT 1";
$stmt_location = $con_tracking->prepare($sql_location);
$stmt_location->execute([$driver_id]);
$location_data = $stmt_location->fetch(PDO::FETCH_ASSOC);
// إذا لم نجد أي موقع، لا داعي لإكمال البحث
if (empty($location_data)) {
jsonError("No car locations found");
exit;
}
// =================================================================
// الخطوة 2: جلب البيانات الثابتة للسائق من القاعدة الأساسية
// =================================================================
// هذا الاستعلام يعمل على قاعدة البيانات الأساسية الهادئة
// (ملاحظة: تم إصلاح الخطأ في جملة JOIN)
$sql_driver_info = "SELECT
d.gender,
cr.model
FROM
driver d
LEFT JOIN CarRegistration cr ON d.id = cr.driverID
WHERE
d.id = ?";
$stmt_driver_info = $con->prepare($sql_driver_info);
$stmt_driver_info->execute([$driver_id]);
$driver_info = $stmt_driver_info->fetch(PDO::FETCH_ASSOC);
// =================================================================
// الخطوة 3: دمج النتائج (Application-Side Join)
// =================================================================
// دمج بيانات الموقع مع بيانات السائق
if (empty($driver_info)) {
$driver_info = []; // اجعله مصفوفة فارغة لتجنب خطأ في الدمج
}
$final_result = array_merge($location_data, $driver_info);
// السكربت الأصلي كان يستخدم fetchAll، لذا كان يرجع مصفوفة بداخلها عنصر واحد
// [ [ ... بيانات ... ] ]
// سنحافظ على نفس البنية لإرجاع البيانات
jsonSuccess([$final_result]);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}
?>

112
ride/location/getPinkBike.php Executable file
View File

@@ -0,0 +1,112 @@
<?php
require_once __DIR__ . '/../../connect.php';
try {
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
jsonError("Invalid coordinates provided");
exit;
}
$sql = "
SELECT
cl.driver_id,
cl.latitude,
cl.longitude,
cl.heading,
cl.speed,
cl.status,
cl.created_at,
cl.updated_at,
d.phone,
d.email,
d.birthdate,
d.first_name,
d.last_name,
d.gender,
d.maritalStatus,
cr.make,
cr.car_plate,
cr.model,
cr.color,
cr.vin,
cr.color_hex,
cr.year,
dt.token,
'' AS age,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
rdAvg.ratingCount
FROM
car_locations cl
LEFT JOIN driver d ON d.id = cl.driver_id
LEFT JOIN CarRegistration cr ON cr.driverID = cl.driver_id
LEFT JOIN driverToken dt ON dt.captain_id = cl.driver_id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = cl.driver_id
WHERE
cl.latitude BETWEEN :southwestLat AND :northeastLat
AND cl.longitude BETWEEN :southwestLon AND :northeastLon
AND cl.status = 'off'
AND cl.updated_at >= NOW() - INTERVAL 5 SECOND
AND (cr.make LIKE '%دراجة%' OR cr.model LIKE '%دراجة%')
GROUP BY cl.driver_id
ORDER BY ratingDriver DESC, cl.updated_at DESC
LIMIT 10;
";
$stmt = $con->prepare($sql);
$stmt->bindParam(':southwestLat', $southwestLat);
$stmt->bindParam(':southwestLon', $southwestLon);
$stmt->bindParam(':northeastLat', $northeastLat);
$stmt->bindParam(':northeastLon', $northeastLon);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($rows) {
$fieldsToDecrypt = [
'phone', 'email', 'gender', 'birthdate',
'first_name', 'last_name', 'maritalStatus', 'token',
'make', 'car_plate', 'vin'
];
$filteredRows = [];
foreach ($rows as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field])) {
$row[$field] = $encryptionHelper->decryptData($row[$field]);
}
}
// فلترة حسب الجنس
if (strtolower($row['gender']) !== 'female') {
continue;
}
// حساب العمر
if (!empty($row['birthdate'])) {
$birthDate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthDate)->y;
} else {
$row['age'] = null;
}
$filteredRows[] = $row;
}
jsonSuccess($filteredRows);
} else {
jsonError("No car locations found");
}
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
}

View File

@@ -0,0 +1,66 @@
<?php
require_once __DIR__ . '/../../connect.php';
$driver_id = filterRequest("driver_id");
$current_month = date('m');
$current_year = date('Y');
// Get the first and last days of the current month.
$first_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-01'));
$last_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-' . cal_days_in_month(CAL_GREGORIAN, $current_month, $current_year)));
// Create a SQL query to select the total duration for the driver for each day in the current month.
$sql = "SELECT
DATE(`ride`.created_at) AS day,
COUNT(`ride`.`id`) AS countRide,
SUM(`ride`.`price`) AS pricePerDay,
(
SELECT
SUM(`ride`.`price`)
FROM
`ride`
WHERE
`ride`.`driver_id` = :driver_id_total AND `ride`.`created_at` >= :first_day_total AND `ride`.created_at < :last_day_total AND `ride`.`status` = 'Finished'
) AS totalPrice,
(
SELECT
COUNT(`ride`.`id`)
FROM
`ride`
WHERE
`ride`.`driver_id` = :driver_id_count AND `ride`.`created_at` >= :first_day_count AND `ride`.created_at < :last_day_count AND `ride`.`status` = 'Finished'
) AS totalCount
FROM
`ride`
WHERE
`ride`.`driver_id` = :driver_id_main AND `ride`.`created_at` >= :first_day_main AND `ride`.created_at < :last_day_main AND `ride`.`status` = 'Finished'
GROUP BY
day
ORDER BY
day ASC;";
$stmt = $con->prepare($sql);
// Bind each parameter uniquely
$stmt->bindParam(':driver_id_total', $driver_id, PDO::PARAM_STR);
$stmt->bindParam(':first_day_total', $first_day_of_month, PDO::PARAM_STR);
$stmt->bindParam(':last_day_total', $last_day_of_month, PDO::PARAM_STR);
$stmt->bindParam(':driver_id_count', $driver_id, PDO::PARAM_STR);
$stmt->bindParam(':first_day_count', $first_day_of_month, PDO::PARAM_STR);
$stmt->bindParam(':last_day_count', $last_day_of_month, PDO::PARAM_STR);
$stmt->bindParam(':driver_id_main', $driver_id, PDO::PARAM_STR);
$stmt->bindParam(':first_day_main', $first_day_of_month, PDO::PARAM_STR);
$stmt->bindParam(':last_day_main', $last_day_of_month, PDO::PARAM_STR);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
// Print the car location data as JSON
jsonSuccess($data = $car_locations);
} else {
// Print a failure message
jsonError($message = "No car locations found");
}
?>

157
ride/location/getSpeed.php Executable file
View File

@@ -0,0 +1,157 @@
<?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
//getSpeed.php
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
jsonError("Invalid coordinates provided");
exit;
}
$freshSeconds = 180; // 3 دقائق
// =================================================================
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
// =================================================================
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
// نجلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
$sql_locations = "
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
FROM car_locations
WHERE
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
AND status = 'off'
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
ORDER BY updated_at DESC
LIMIT 100; -- نجلب 100 مرشح محتمل
";
$stmt_locations = $con_tracking->prepare($sql_locations);
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt_locations->execute();
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
if (!$locations) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 2: تجميع معرفات السائقين (driver_id)
// =================================================================
$driver_ids = array_column($locations, 'driver_id');
// =================================================================
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
// =================================================================
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
// هنا نطبق الشروط الخاصة بهذا السكريبت (موديل السيارة > 2000)
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
-- AND COALESCE(cr.year, 0) > 2000 -- ⭐ الشرط الخاص بهذا السكريبت
-- AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
";
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
}
// =================================================================
// الخطوة 4: دمج النتائج في PHP
// =================================================================
$final_results = [];
foreach ($locations as $location) {
$driver_id = $location['driver_id'];
if (isset($drivers_info[$driver_id])) {
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
}
}
// =================================================================
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
// =================================================================
usort($final_results, function ($a, $b) {
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
}
if ($a['ratingCount'] != $b['ratingCount']) {
return $b['ratingCount'] <=> $a['ratingCount'];
}
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
});
$limited_results = array_slice($final_results, 0, 10);
if (empty($limited_results)) {
jsonError("No cars matching the specific criteria (year > 2000) found.");
exit;
}
// =================================================================
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
// =================================================================
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
catch (Exception $e) { $row[$field] = null; }
}
}
if (!empty($row['birthdate'])) {
try {
$birthDate = new DateTime($row['birthdate']);
$today = new DateTime();
$row['age'] = $today->diff($birthDate)->y;
} catch (Exception $e) { $row['age'] = null; }
} else {
$row['age'] = null;
}
}
unset($row);
jsonSuccess($limited_results);
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,44 @@
<?php
require_once __DIR__ . '/../../connect.php';
$driver_id = filterRequest("driver_id");
$current_month = date('m');
$current_year = date('Y');
// Get the first and last days of the current month.
$first_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-01'));
$last_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-' . cal_days_in_month(CAL_GREGORIAN, $current_month, $current_year)));
// Create a SQL query to select the total duration for the driver for each day in the current month.
$sql = "SELECT
DATE(created_at) AS day,
SEC_TO_TIME(COUNT(*) * 60) AS total_duration
FROM
car_tracks
WHERE
car_tracks.driver_id = :driver_id
AND car_tracks.created_at >= :first_day_of_month
AND car_tracks.created_at < :last_day_of_month
GROUP BY
day
ORDER BY
day ASC;";
$stmt = $con->prepare($sql);
$stmt->bindParam(':driver_id', $driver_id, PDO::PARAM_STR);
$stmt->bindParam(':first_day_of_month', $first_day_of_month, PDO::PARAM_STR);
$stmt->bindParam(':last_day_of_month', $last_day_of_month, PDO::PARAM_STR);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
// Print the car location data as JSON
jsonSuccess($data = $car_locations);
} else {
// Print a failure message
jsonError($message = "No car locations found");
}
?>

View File

@@ -0,0 +1,31 @@
<?php
require_once __DIR__ . '/../../connect.php';
$driver_id = filterRequest("driver_id");
// Get the current date.
$current_date = date('Y-m-d');
// Create a SQL query to select the total duration for the driver today.
$sql = "SELECT
SEC_TO_TIME(COUNT(*) * 60) AS total_duration
FROM
car_tracks
WHERE
car_tracks.driver_id = '$driver_id'
AND car_tracks.created_at >= '$current_date'
AND car_tracks.created_at < DATE_ADD('$current_date', INTERVAL 1 DAY);";
$stmt = $con->prepare($sql);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
// Print the car location data as JSON
jsonSuccess($car_locations);
} else {
// Print a failure message
jsonError($message = "No car locations found");
}
?>

View File

@@ -0,0 +1,126 @@
<?php
// =================================================================
// ملف: getUpdatedLocationForAdmin.php
// =================================================================
require_once __DIR__ . '/../../get_connect.php';
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// تفعيل إظهار الأخطاء لمعرفة مشكلة الكتابة
error_reporting(E_ALL);
ini_set('display_errors', 1);
try {
$mode = isset($_GET['mode']) && $_GET['mode'] == 'day' ? 'day' : 'live';
if ($mode == 'day') {
$fileName = 'locations_day.json';
$timeCondition = "DATE(created_at) = CURDATE()";
} else {
$fileName = 'locations_live.json';
$freshSeconds = 1200;
$timeCondition = "created_at >= NOW() - INTERVAL $freshSeconds SECOND";
}
// تحديد المسار الكامل بدقة
$savePath = __DIR__ . '/' . $fileName;
// === فحص صلاحيات الكتابة ===
if (!is_writable(__DIR__)) {
// إذا لم تكن هناك صلاحية، سنطبع الخطأ ونوقف التنفيذ
echo json_encode([
"status" => "error",
"message" => "Permission Denied: Cannot write to directory. Please chmod 777 this folder.",
"path" => __DIR__
]);
exit;
}
// 1. جلب المواقع
$sql_locations = "
SELECT t.driver_id,
t.latitude AS lat,
t.longitude AS lon,
t.heading,
t.speed,
t.created_at
FROM car_tracks t
INNER JOIN (
SELECT driver_id, MAX(id) AS max_id
FROM car_tracks
WHERE $timeCondition
GROUP BY driver_id
) latest
ON t.id = latest.max_id
ORDER BY t.created_at DESC
";
$stmt = $con_tracking->prepare($sql_locations);
$stmt->execute();
$locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 2. جلب بيانات السائقين
$driver_ids = array_unique(array_column($locations, 'driver_id'));
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
$sql_drivers = "SELECT id, first_name, last_name, phone,
(SELECT COUNT(*) FROM ride WHERE driver_id = driver.id AND status = 'Completed') as completed,
(SELECT COUNT(*) FROM ride WHERE driver_id = driver.id AND status = 'CancelFromDriverAfterApply') as cancelled
FROM driver WHERE id IN ($placeholders)";
$stmt_drivers = $con->prepare($sql_drivers);
$stmt_drivers->execute(array_values($driver_ids));
foreach ($stmt_drivers->fetchAll(PDO::FETCH_ASSOC) as $row) {
$drivers_info[$row['id']] = $row;
}
}
// 3. الدمج
$final_drivers = [];
foreach ($locations as $loc) {
$d_id = $loc['driver_id'];
$merged = [
'id' => $d_id,
'lat' => $loc['lat'],
'lon' => $loc['lon'],
'heading' => $loc['heading'],
'speed' => $loc['speed'],
'name' => 'Unknown',
'phone' => '',
'completed' => 0,
'cancelled' => 0
];
if (isset($drivers_info[$d_id])) {
$info = $drivers_info[$d_id];
// فك التشفير البسيط (تأكد من عمل encryptionHelper)
if (isset($encryptionHelper)) {
try { $info['first_name'] = $encryptionHelper->decryptData($info['first_name']); } catch(Exception $e){}
try { $info['last_name'] = $encryptionHelper->decryptData($info['last_name']); } catch(Exception $e){}
try { $info['phone'] = $encryptionHelper->decryptData($info['phone']); } catch(Exception $e){}
}
$merged['name'] = trim(($info['first_name']??'') . ' ' . ($info['last_name']??''));
$merged['phone'] = $info['phone'] ?? '';
$merged['completed'] = $info['completed'] ?? 0;
$merged['cancelled'] = $info['cancelled'] ?? 0;
}
$final_drivers[] = $merged;
}
// 4. الحفظ
$jsonContent = json_encode(['drivers' => $final_drivers, 'last_updated' => date('Y-m-d H:i:s')], JSON_UNESCAPED_UNICODE);
// محاولة الحفظ
if (file_put_contents($savePath, $jsonContent) !== false) {
echo json_encode(["status" => "success", "message" => "File written successfully to $savePath"]);
} else {
echo json_encode(["status" => "error", "message" => "Failed to write file. Check permissions."]);
}
} catch (Exception $e) {
echo json_encode(["status" => "error", "message" => $e->getMessage()]);
}
?>

View File

@@ -0,0 +1,24 @@
<?php
require_once __DIR__ . '/../../connect.php';
// Use prepared statement to prevent SQL injection
$sql = "
SELECT * FROM `server_locations`
";
try {
$stmt = $con->prepare($sql);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
jsonSuccess($car_locations);
} else {
jsonError("No car locations found");
}
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
}

View File

@@ -0,0 +1,86 @@
<?php
require_once __DIR__ . '/../../connect.php';
try {
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
$sql = "
SELECT
cl.driver_id,
cl.latitude,
cl.longitude,
cl.heading,
cl.speed,
cl.status,
cl.created_at,
cl.updated_at,
d.phone,
d.email,
d.birthdate,
d.first_name,
d.last_name,
d.gender,
d.maritalStatus,
cr.make,
cr.car_plate,
cr.model,
cr.color,
cr.vin,
cr.color_hex,
cr.year,
dt.token,
COALESCE(AVG(rd.rating), 0) AS ratingDriver,
COUNT(rd.id) AS ratingCount,
TIMESTAMPDIFF(YEAR, d.birthdate, CURDATE()) AS age,
(
SELECT COALESCE(AVG(sub.behavior_score), 100)
FROM (
SELECT behavior_score
FROM driver_behavior db
WHERE db.driver_id = cl.driver_id
ORDER BY db.id DESC
LIMIT 5
) AS sub
) AS ai_behavior_score
FROM
car_locations cl
LEFT JOIN driver d ON
d.id = cl.driver_id
LEFT JOIN CarRegistration cr ON
cr.driverID = cl.driver_id
LEFT JOIN driverToken dt ON
dt.captain_id = cl.driver_id
LEFT JOIN ratingDriver rd ON
rd.driver_id = cl.driver_id
WHERE
cl.latitude >= :southwestLat AND cl.latitude <= :northeastLat
AND cl.longitude >= :southwestLon AND cl.longitude <= :northeastLon
AND cl.status = 'off'
AND cl.updated_at >= NOW() - INTERVAL 5 SECOND
AND (cr.make NOT LIKE '%دراجة%' OR cr.model NOT LIKE '%دراجة%')
AND d.gender = 'Female'
GROUP BY cl.driver_id
ORDER BY ratingDriver DESC, cl.updated_at DESC
LIMIT 10;
";
$stmt = $con->prepare($sql);
$stmt->bindParam(':southwestLat', $southwestLat);
$stmt->bindParam(':southwestLon', $southwestLon);
$stmt->bindParam(':northeastLat', $northeastLat);
$stmt->bindParam(':northeastLon', $northeastLon);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
jsonSuccess($car_locations);
} else {
jsonError("No car locations found");
}
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
}
?>

254
ride/location/print.php Executable file
View File

@@ -0,0 +1,254 @@
<?php
// ============================================================
// 1. DATA FETCHING (Dynamic from Server File)
// ============================================================
$source_file = 'active_drivers_cache.json';
// Check if file exists to avoid errors
if (!file_exists($source_file)) {
die('<div style="font-family:sans-serif; text-align:center; padding:50px; color:#dc2626;">
<h1>⚠️ Error: Data File Not Found</h1>
<p>Please ensure <strong>'.$source_file.'</strong> exists in the same directory.</p>
</div>');
}
// Get content
$json_data = file_get_contents($source_file);
$data = json_decode($json_data, true);
if (!$data) {
die('Error decoding JSON data.');
}
$drivers = $data['data'];
$last_updated = $data['last_updated'] ?? date('Y-m-d H:i:s');
// ============================================================
// 2. DATA PROCESSING (Logic Layer)
// ============================================================
$processed_drivers = [];
$stats = [
'total' => 0,
'elite' => 0, // +50 hours
'stable' => 0, // +20 hours
'experimental' => 0, // +5 hours
'inactive' => 0
];
foreach ($drivers as $driver) {
// Parse Active Time String (e.g., "293 ساعة و 48 دقيقة")
$timeStr = $driver['active_time'] ?? "0 ساعة و 0 دقيقة";
preg_match('/(\d+)\s*ساعة/', $timeStr, $hoursMatch);
preg_match('/(\d+)\s*دقيقة/', $timeStr, $minsMatch);
$hours = isset($hoursMatch[1]) ? (int)$hoursMatch[1] : 0;
$mins = isset($minsMatch[1]) ? (int)$minsMatch[1] : 0;
$totalMinutes = ($hours * 60) + $mins;
// Categorize Driver
$category = 'inactive';
$catLabel = 'خامل';
$catClass = 'cat-inactive';
if ($totalMinutes >= 3000) { // 50 hours
$category = 'elite';
$catLabel = 'نخبة';
$catClass = 'cat-elite';
$stats['elite']++;
} elseif ($totalMinutes >= 1200) { // 20 hours
$category = 'stable';
$catLabel = 'مستقر';
$catClass = 'cat-stable';
$stats['stable']++;
} elseif ($totalMinutes >= 300) { // 5 hours
$category = 'experimental';
$catLabel = 'تجريبي';
$catClass = 'cat-experimental';
$stats['experimental']++;
} else {
$stats['inactive']++;
}
$driver['total_minutes'] = $totalMinutes;
$driver['category_label'] = $catLabel;
$driver['category_class'] = $catClass;
$processed_drivers[] = $driver;
}
$stats['total'] = count($processed_drivers);
// Sort by Active Time (High to Low)
usort($processed_drivers, function($a, $b) {
return $b['total_minutes'] <=> $a['total_minutes'];
});
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>تقرير السائقين - Intaleq</title>
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&display=swap" rel="stylesheet">
<style>
:root {
--color-elite: #d97706;
--color-stable: #059669;
--color-exp: #2563eb;
--color-inactive: #dc2626;
}
body {
font-family: 'Tajawal', sans-serif;
background: #e5e7eb; /* Grey bg for screen */
margin: 0;
padding: 20px;
color: #1f2937;
}
.page {
background: white;
max-width: 210mm; /* A4 Width */
margin: 0 auto;
padding: 20px 40px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
min-height: 297mm;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #1f2937;
padding-bottom: 20px;
margin-bottom: 20px;
}
.header h1 { margin: 0; font-size: 24px; }
.meta { font-size: 12px; color: #6b7280; }
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
margin-bottom: 30px;
}
.stat-box {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 15px;
text-align: center;
}
.stat-value { font-size: 24px; font-weight: 800; display: block; }
.stat-label { font-size: 12px; color: #6b7280; font-weight: bold; }
/* Table */
table { width: 100%; border-collapse: collapse; font-size: 12px; }
th, td { border-bottom: 1px solid #e5e7eb; padding: 10px; text-align: right; }
th { background-color: #f9fafb; font-weight: 800; color: #374151; border-top: 2px solid #374151; }
tr:nth-child(even) { background-color: #fdfdfd; }
/* Categories */
.badge { padding: 2px 8px; border-radius: 4px; font-weight: bold; font-size: 10px; border: 1px solid; }
.cat-elite { background: #fef3c7; color: var(--color-elite); border-color: #fcd34d; }
.cat-stable { background: #d1fae5; color: var(--color-stable); border-color: #6ee7b7; }
.cat-experimental { background: #dbeafe; color: var(--color-exp); border-color: #93c5fd; }
.cat-inactive { background: #fee2e2; color: var(--color-inactive); border-color: #fca5a5; }
/* Notes Column */
.notes-col { width: 150px; border-left: 1px dashed #d1d5db; }
/* Print Rules */
@media print {
body { background: white; padding: 0; }
.page { box-shadow: none; max-width: 100%; padding: 0; margin: 0; }
.no-print { display: none; }
th { background-color: #f3f4f6 !important; -webkit-print-color-adjust: exact; }
.badge { -webkit-print-color-adjust: exact; }
tr { page-break-inside: avoid; }
}
</style>
</head>
<body>
<div class="no-print" style="text-align: center; margin-bottom: 20px;">
<button onclick="window.print()" style="padding: 10px 20px; background: #2563eb; color: white; border: none; border-radius: 5px; cursor: pointer; font-family: inherit; font-weight: bold; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
🖨️ طباعة التقرير (PDF)
</button>
<button onclick="location.reload()" style="padding: 10px 20px; background: #4b5563; color: white; border: none; border-radius: 5px; cursor: pointer; font-family: inherit; margin-right: 10px;">
🔄 تحديث البيانات
</button>
</div>
<div class="page">
<div class="header">
<div>
<h1>تقرير أداء السائقين</h1>
<div class="meta">تاريخ الطباعة: <?php echo date('Y-m-d H:i'); ?></div>
</div>
<div style="text-align: left">
<h2 style="margin:0; color: #2563eb;">Intaleq</h2>
<div class="meta dir-ltr"><?php echo $last_updated; ?> :آخر تحديث بيانات</div>
</div>
</div>
<div class="stats-grid">
<div class="stat-box" style="border-bottom: 3px solid #374151;">
<span class="stat-label">إجمالي السائقين</span>
<span class="stat-value" style="color: #374151"><?php echo $stats['total']; ?></span>
</div>
<div class="stat-box" style="border-bottom: 3px solid var(--color-elite);">
<span class="stat-label">النخبة (+50 ساعة)</span>
<span class="stat-value" style="color: var(--color-elite)"><?php echo $stats['elite']; ?></span>
</div>
<div class="stat-box" style="border-bottom: 3px solid var(--color-stable);">
<span class="stat-label">مستقرون (+20 ساعة)</span>
<span class="stat-value" style="color: var(--color-stable)"><?php echo $stats['stable']; ?></span>
</div>
<div class="stat-box" style="border-bottom: 3px solid var(--color-inactive);">
<span class="stat-label">يحتاجون متابعة</span>
<span class="stat-value" style="color: var(--color-inactive)"><?php echo $stats['inactive']; ?></span>
</div>
</div>
<table>
<thead>
<tr>
<th style="width: 5%">#</th>
<th style="width: 25%">اسم السائق</th>
<th style="width: 15%">رقم الهاتف</th>
<th style="width: 20%">ساعات النشاط</th>
<th style="width: 10%">الحالة</th>
<th style="width: 10%">تاريخ الانضمام</th>
<th class="notes-col">ملاحظات إدارية</th>
</tr>
</thead>
<tbody>
<?php $i = 1; foreach ($processed_drivers as $driver): ?>
<tr>
<td><?php echo $i++; ?></td>
<td>
<strong><?php echo $driver['name_arabic'] ?? '---'; ?></strong>
</td>
<td style="direction: ltr; text-align: right; font-family: monospace;">
<?php echo $driver['phone']; ?>
</td>
<td>
<?php echo $driver['active_time']; ?>
</td>
<td>
<span class="badge <?php echo $driver['category_class']; ?>">
<?php echo $driver['category_label']; ?>
</span>
</td>
<td><?php echo isset($driver['created_at']) ? date('Y-m-d', strtotime($driver['created_at'])) : '-'; ?></td>
<td class="notes-col"></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,58 @@
<?php
// تأكد من أن ملف connect.php يقوم بتهيئة اتصالين:
// $con -> يتصل بقاعدة البيانات الأساسية
// $con_tracking -> يتصل بقاعدة بيانات التتبع (driver_behavior, car_locations)
require_once __DIR__ . '/../../connect.php';
try {
// استلام البيانات من Flutter
$driver_id = filterRequest("driver_id");
$trip_id = filterRequest("trip_id");
$max_speed = filterRequest("max_speed");
$avg_speed = filterRequest("avg_speed");
$hard_brakes = filterRequest("hard_brakes");
$total_distance = filterRequest("total_distance");
$behavior_score = filterRequest("behavior_score");
// تحقق من القيم الأساسية
if (empty($driver_id) || empty($trip_id)) {
jsonError("Missing driver_id or trip_id");
exit();
}
// إدخال البيانات في جدول driver_behavior باستخدام اتصال التتبع
// تم تغيير $con إلى $con_tracking
$stmt = $con_tracking->prepare("
INSERT INTO driver_behavior (
driver_id, trip_id, max_speed, avg_speed,
hard_brakes, total_distance, behavior_score
) VALUES (?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$driver_id,
$trip_id,
$max_speed,
$avg_speed,
$hard_brakes,
$total_distance,
$behavior_score
]);
// التحقق من نجاح العملية
if ($stmt->rowCount() > 0) {
jsonSuccess(null, "Behavior data saved");
} else {
// في حالة عدم حدوث خطأ، ولكن لم يتم إدخال صف (قد يحدث)،
// من الأفضل إرجاع رسالة فشل عامة.
jsonError("Failed to save data (No rows affected)");
}
} catch (PDOException $e) {
jsonError("Database error: " . $e->getMessage());
} catch (Throwable $e) {
jsonError("Internal error: " . $e->getMessage());
}
// تم حذف exit() من هنا ليتم التعامل معها داخل try/catch
?>

69
ride/location/update.php Normal file
View File

@@ -0,0 +1,69 @@
<?php
require_once __DIR__ . '/../../connect.php';
$driver_id = filterRequest("driver_id");
$latitude = filterRequest("latitude");
$longitude = filterRequest("longitude");
$status = filterRequest("status");
$heading = filterRequest("heading");
$speed = filterRequest("speed");
$distance = filterRequest("distance");
$updated_at = date("Y-m-d H:i:s");
// Basic validation
if (!$driver_id || !$latitude || !$longitude || $status === null) {
http_response_code(400);
// Use your custom printFailure function for consistency
jsonError('Missing required fields');
exit;
}
// Secure SQL using prepared statement
$sql = "INSERT INTO `car_locations` (
`driver_id`, `latitude`, `longitude`, `heading`, `speed`, `distance`, `status`, `updated_at`
) VALUES (
:driver_id, :latitude, :longitude, :heading, :speed, :distance, :status, :updated_at
)
ON DUPLICATE KEY UPDATE
`latitude` = VALUES(`latitude`),
`longitude` = VALUES(`longitude`),
`heading` = VALUES(`heading`),
`speed` = VALUES(`speed`),
`distance` = VALUES(`distance`),
`status` = VALUES(`status`),
`updated_at` = VALUES(`updated_at`)";
try {
$stmt = $con->prepare($sql);
// The execute method returns true on success and false on failure.
$success = $stmt->execute([
':latitude' => $latitude,
':longitude' => $longitude,
':heading' => $heading,
':speed' => $speed,
':distance' => $distance,
':status' => $status,
':updated_at' => $updated_at,
':driver_id' => $driver_id
]);
// The reliable way to check for success is if execute() returns true
// and doesn't throw an exception. We no longer need rowCount().
if ($success) {
// Print a success message
jsonSuccess(null, "Car location updated successfully");
} else {
// This case is rare but might happen if execute fails without an exception
jsonError("Failed to update car location");
}
} catch (PDOException $e) {
// A real database error occurred.
http_response_code(500);
// You can log the detailed error for debugging
// error_log('Database error: ' . $e->getMessage());
jsonError('Database error occurred');
}
?>