Update: 2026-06-29 23:09:43

This commit is contained in:
Hamza-Ayed
2026-06-29 23:09:43 +03:00
parent 65b2e68154
commit 3506b07bc7
42 changed files with 8252 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
<?php
// api_get_nearby.php (Updated)
require_once __DIR__ . '/vendor/autoload.php';
use Predis\Client;
header('Content-Type: application/json');
$INTERNAL_KEY = trim(file_get_contents('/home/location/.internal_socket_key'));
$PASS_REDIS = trim(file_get_contents('/home/location/.reds_pass_key'));
$headers = getallheaders();
$receivedKey = $headers['x-internal-key'] ?? $_SERVER['HTTP_X_INTERNAL_KEY'] ?? '';
if ($receivedKey !== $INTERNAL_KEY) {
http_response_code(403);
echo json_encode(['status' => false, 'msg' => 'Unauthorized']);
exit;
}
$lat = $_REQUEST['lat'] ?? null;
$lng = $_REQUEST['lng'] ?? null;
$radius = $_REQUEST['radius'] ?? 5;
$limit = $_REQUEST['limit'] ?? 100;
if (!$lat || !$lng) {
echo json_encode(['status' => false, 'msg' => 'Invalid Coordinates']); exit;
}
try {
$redis = new Client(['scheme'=>'tcp',
'host'=>'127.0.0.1',
'password' => $PASS_REDIS,
'port'=>6379,
'database' => 0]
);
$redis->connect();
// 🔥 التعديل هنا: إضافة WITHCOORD لجلب الإحداثيات من الريدز مباشرة
$geoResults = $redis->georadius(
'geo:drivers:available',
$lng, $lat, $radius, 'km',
['WITHDIST' => true, 'WITHCOORD' => true, 'COUNT' => $limit * 2, 'SORT' => 'ASC']
);
$validDrivers = [];
$currentTime = time();
$max_silence = 180;
foreach ($geoResults as $res) {
// هيكل النتيجة مع WITHCOORD يختلف قليلاً
$d_id = $res[0]; // ID
$d_dist = $res[1]; // Distance
$d_coord= $res[2]; // [0=>lng, 1=>lat] 🔥 الإحداثيات هنا
$profile = $redis->hgetall("driver:profile:$d_id");
$lastUpdate = isset($profile['updated_at']) ? (int)$profile['updated_at'] : 0;
if (empty($profile) || ($currentTime - $lastUpdate) > $max_silence) {
$redis->zrem('geo:drivers:available', $d_id);
$redis->zrem('geo:drivers:busy', $d_id);
continue;
}
$validDrivers[] = [
'id' => $d_id,
'distance' => $d_dist,
'heading' => $profile['heading'] ?? 0,
'speed' => $profile['speed'] ?? 0,
'lat' => $d_coord[1], // Latitude من الريدز (أدق)
'lng' => $d_coord[0] // Longitude من الريدز
];
if (count($validDrivers) >= $limit) break;
}
echo json_encode(['status' => true, 'data' => $validDrivers]);
} catch (Exception $e) {
echo json_encode(['status' => false, 'msg' => $e->getMessage()]);
}
?>

View File

@@ -0,0 +1,7 @@
{
"require": {
"workerman/phpsocket.io": "^2.2",
"firebase/php-jwt": "^7.0",
"predis/predis": "^3.3"
}
}

601
loction_server/driver_socket.php Executable file
View File

@@ -0,0 +1,601 @@
<?php
/**
* driver_socket.php
* ==================
* WebSocket Server للسائقين — بورت 2020
* Internal HTTP Server — بورت 2021
*
* 🚀 Level 2 Architecture (Production Ready):
* - Event Buffering (Batching)
* - Redis Pipelines (تقليل الـ I/O والـ Latency بشكل كبير)
* - Memory State Cache للسائقين
* - جميع طرق HTTP (Dispatch, Market, Force Disconnect...) موجودة بالكامل
*/
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Http\Client as AsyncHttp;
use PHPSocketIO\SocketIO;
use Predis\Client as RedisClient;
require_once __DIR__ . '/vendor/autoload.php';
// ============================================================
// ⚙️ إعدادات عامة
// ============================================================
ini_set('memory_limit', '512M');
date_default_timezone_set('Asia/Amman');
// ── Tunables (إعدادات الأداء) ──────────────────────────────────
const MIN_MOVE_METERS = 10.0; // GEOADD فقط إذا تحرك أكثر من 10 متر
const HMSET_SPEED_DELTA = 1.0; // فرق السرعة المطلوب لتحديث Redis
const HMSET_HEADING_DELTA = 5.0; // فرق الاتجاه المطلوب لتحديث Redis
const EXPIRE_REFRESH_SECONDS = 300; // 5 دقائق لتجديد الـ TTL
const FORWARD_MIN_METERS = 15.0; // HTTP forward للراكب
const FORWARD_MAX_SECONDS = 3; // أقصى مدة للـ Forward
const REDIS_BATCH_INTERVAL = 0.5; // تنفيذ مجمّع (Batch) كل نصف ثانية (500ms)
// ─────────────────────────────────────────────────────────────
function logMsg(string $msg): void {
echo '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
}
function loadEnvironment(string $filePath): void {
if (!file_exists($filePath)) {
logMsg("⚠️ .env not found: $filePath");
return;
}
foreach (file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
if (str_starts_with(trim($line), '#') || !str_contains($line, '=')) continue;
[$name, $value] = explode('=', $line, 2);
putenv(trim($name) . '=' . trim($value, "\"'"));
}
logMsg('✅ Environment loaded.');
}
loadEnvironment('/home/location/env/.env');
// ============================================================
// 🔐 مفاتيح الأمان
// ============================================================
$INTERNAL_KEY = trim((string) @file_get_contents('/home/location/.internal_socket_key'));
$redisPass = trim((string) @file_get_contents('/home/location/.reds_pass_key'));
if (empty($INTERNAL_KEY)) logMsg('❌ CRITICAL: Internal key missing!');
if (empty($redisPass)) logMsg('❌ CRITICAL: Redis password missing!');
// ============================================================
// 🗄️ Redis Singleton
// ============================================================
$redis = null;
function getRedis(): ?RedisClient {
global $redis, $redisPass;
if ($redis !== null) {
try {
$redis->ping();
return $redis;
} catch (\Exception $e) {
logMsg('⚠️ Redis ping failed, reconnecting...');
$redis = null;
}
}
try {
$client = new RedisClient([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
'password' => $redisPass,
'read_write_timeout' => 0,
]);
$client->connect();
$redis = $client;
return $redis;
} catch (\Exception $e) {
logMsg('❌ Redis Error: ' . $e->getMessage());
return null;
}
}
// ============================================================
// 📐 Haversine Distance (متر)
// ============================================================
function haversineDistance(float $lat1, float $lng1, float $lat2, float $lng2): float {
$R = 6371000;
$dLat = deg2rad($lat2 - $lat1);
$dLng = deg2rad($lng2 - $lng1);
$a = sin($dLat / 2) ** 2
+ cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
return $R * 2 * atan2(sqrt($a), sqrt(1 - $a));
}
// ============================================================
// 📡 Forward موقع السائق → سيرفر الراكب (ASYNC)
// ============================================================
function forwardLocationToPassengerSocket(
string $driverId,
string $passengerId,
array $payload,
string $internalKey,
array &$fwdThrottle
): void {
if (empty($passengerId)) return;
$now = time();
$last = $fwdThrottle[$driverId] ?? null;
if ($last !== null) {
$timeDiff = $now - $last['ts'];
$dist = haversineDistance(
$last['lat'], $last['lng'],
(float)$payload['lat'], (float)$payload['lng']
);
if ($dist < FORWARD_MIN_METERS && $timeDiff < FORWARD_MAX_SECONDS) return;
}
$fwdThrottle[$driverId] = [
'ts' => $now,
'lat' => (float)$payload['lat'],
'lng' => (float)$payload['lng'],
];
$passengerSocketUrl = getenv('PASSENGER_SOCKET_INTERNAL_URL') ?: 'http://127.0.0.1:3031';
$http = new AsyncHttp();
$http->request(
$passengerSocketUrl,
[
'method' => 'POST',
'data' => http_build_query([
'action' => 'update_driver_location',
'passenger_id' => $passengerId,
'payload' => json_encode($payload),
]),
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
'x-internal-key' => $internalKey,
'Connection' => 'close',
],
'timeout' => 3,
],
null,
fn(\Exception $e) => logMsg('⚠️ Forward failed: ' . $e->getMessage())
);
}
// ============================================================
// 📲 FCM (ASYNC)
// ============================================================
function sendFCM_Async(string $token, string $title, string $body, array $rideData): void {
if (empty($token)) return;
$http = new AsyncHttp();
$http->request(
'https://api.intaleq.xyz/intaleq/ride/firebase/send_fcm.php',
[
'method' => 'POST',
'data' => json_encode([
'target' => $token,
'title' => $title,
'body' => $body,
'isTopic' => false,
'category' => 'Order',
'tone' => 'start',
'passengerList' => json_encode($rideData),
]),
'headers' => ['Content-Type' => 'application/json; charset=UTF-8'],
'timeout' => 5,
],
null,
fn(\Exception $e) => logMsg('⚠️ FCM failed: ' . $e->getMessage())
);
}
// ============================================================
// 🧠 Memory State & Event Buffer
// ============================================================
$connectedDrivers = [];
$active_orders_drivers = [];
$driverState = [];
$fwdThrottle = [];
$eventBuffer = []; // 🚀 Level 2: مصفوفة تجميع الأحداث لـ Redis
// ============================================================
// 🚀 Socket.IO — بورت 2020
// ============================================================
$io = new SocketIO(2020);
// ============================================================
// A. Internal HTTP Server & Redis Batch Processor (Worker Start)
// ============================================================
$io->on('workerStart', function () use ($io, $INTERNAL_KEY) {
// 🚀 1. Redis Pipeline Batch Processor (Level 2)
// يعمل كل نصف ثانية، يجمع كل الأوامر ويرسلها لـ Redis دفعة واحدة
Timer::add(REDIS_BATCH_INTERVAL, function() {
global $eventBuffer;
if (empty($eventBuffer)) return;
$redis = getRedis();
if (!$redis) return;
try {
$pipe = $redis->pipeline();
$processedCount = 0;
foreach ($eventBuffer as $driverId => $ops) {
$profileKey = "driver:profile:$driverId";
$processedCount++;
if (isset($ops['hmset'])) {
$pipe->hmset($profileKey, $ops['hmset']);
}
if (isset($ops['expire'])) {
$pipe->expire($profileKey, $ops['expire']);
}
if (isset($ops['status_change'])) {
$oldStatus = $ops['status_change']['old'];
$newStatus = $ops['status_change']['new'];
// إزالة من المجموعة القديمة
if ($oldStatus === 'on') $pipe->zrem('geo:drivers:busy', $driverId);
if ($oldStatus === 'off') $pipe->zrem('geo:drivers:available', $driverId);
if ($newStatus === 'close' || $newStatus === 'blocked') {
$pipe->zrem('geo:drivers:available', $driverId);
$pipe->zrem('geo:drivers:busy', $driverId);
} elseif ($newStatus === 'off') {
// أصبح متاحاً → أضفه إلى geo:drivers:available
$pipe->zadd('geo:drivers:available', 0, $driverId);
} elseif ($newStatus === 'on') {
// أصبح مشغولاً → أضفه إلى geo:drivers:busy
$pipe->zadd('geo:drivers:busy', 0, $driverId);
}
}
if (isset($ops['geoadd'])) {
$st = $ops['geoadd']['status'];
$lng = $ops['geoadd']['lng'];
$lat = $ops['geoadd']['lat'];
if ($st === 'off') {
$pipe->geoadd('geo:drivers:available', $lng, $lat, $driverId);
} elseif ($st === 'on') {
$pipe->geoadd('geo:drivers:busy', $lng, $lat, $driverId);
}
}
}
$pipe->execute();
$eventBuffer = []; // إفراغ المصفوفة بعد التنفيذ الناجح
} catch (\Exception $e) {
logMsg("⚠️ Redis Pipeline Error: " . $e->getMessage());
}
});
// 🌐 2. Internal HTTP Server — بورت 2021
$innerHttp = new Worker('http://0.0.0.0:2021');
$innerHttp->onMessage = function ($connection, $request) use ($io, $INTERNAL_KEY) {
global $active_orders_drivers, $connectedDrivers;
$headers = $request->header();
if (($headers['x-internal-key'] ?? '') !== $INTERNAL_KEY) {
$connection->send('Unauthorized');
return;
}
$post = $request->post();
$action = trim($post['action'] ?? '');
$redis = getRedis();
// ── 1. Dispatch Order ────────────────────────────────
if ($action === 'dispatch_order') {
$rideId = $post['ride_id'] ?? null;
$drivers = json_decode($post['drivers_ids'] ?? '[]', true);
$payload = $post['payload'] ?? [];
if (is_array($payload)) $payload = array_values($payload);
if ($rideId && !empty($drivers)) {
$active_orders_drivers[$rideId] = $drivers;
logMsg("🚀 Dispatch Ride #$rideId" . count($drivers) . ' drivers.');
}
foreach ($drivers as $driverId) {
if (!isset($connectedDrivers[$driverId])) continue;
$io->to('driver_' . $driverId)->emit('new_ride_request', $payload);
$platform = $connectedDrivers[$driverId]['platform'] ?? 'android';
$token = $connectedDrivers[$driverId]['token'] ?? '';
if ($platform === 'ios' && !empty($token)) {
sendFCM_Async($token, 'طلب جديد', 'لديك رحلة جديدة قريبة منك', $payload);
}
}
$connection->send('Dispatched');
// ── 2. Market New Ride ────────────────────────────────
} elseif ($action === 'market_new_ride') {
$payload = $post['payload'] ?? [];
$rideId = $payload['id'] ?? null;
$lat = (float)($payload['start_lat'] ?? 0);
$lng = (float)($payload['start_lng'] ?? 0);
$endLat = isset($payload['end_lat']) ? (float)$payload['end_lat'] : null;
$endLng = isset($payload['end_lng']) ? (float)$payload['end_lng'] : null;
if (!$redis || !$rideId || $lat == 0 || $lng == 0) {
$connection->send('Error: Redis unavailable or invalid coords');
return;
}
$redis->geoadd('geo:rides:waiting', $lng, $lat, $rideId);
$nearbyDrivers = $redis->georadius('geo:drivers:available', $lng, $lat, 50, 'km');
$count = 0;
foreach ($nearbyDrivers as $driverId) {
if (isset($connectedDrivers[$driverId])) {
// Check if driver has a destination constraint in Redis
$profileKey = "driver:profile:$driverId";
$profile = $redis->hgetall($profileKey);
if ($profile && isset($profile['has_destination']) && $profile['has_destination'] == 1 && $endLat !== null && $endLng !== null) {
$driverDestLat = (float)($profile['destination_lat'] ?? 0);
$driverDestLng = (float)($profile['destination_lng'] ?? 0);
$destDistance = haversineDistance($endLat, $endLng, $driverDestLat, $driverDestLng);
// Filter out driver if destination is > 5km (5000 meters) away
if ($destDistance > 5000.0) {
continue;
}
}
$io->to('driver_' . $driverId)->emit('market_new_ride', $payload);
$count++;
}
}
logMsg("📢 Market Ride #$rideId$count drivers.");
$connection->send("Broadcasted to $count drivers");
// ── 3. Get Nearby Ride IDs ────────────────────────────
} elseif ($action === 'get_nearby_ride_ids') {
$lat = (float)($post['lat'] ?? 0);
$lng = (float)($post['lng'] ?? 0);
$radius = (float)($post['radius'] ?? 9);
if (!$redis) { $connection->send(json_encode([])); return; }
$results = $redis->georadius(
'geo:rides:waiting', $lng, $lat, $radius, 'km',
['WITHDIST' => true, 'SORT' => 'ASC', 'COUNT' => 40]
);
$connection->send(json_encode($results));
// ── 4. Ride Taken ─────────────────────────────────────
} elseif ($action === 'ride_taken_event') {
$rideId = $post['ride_id'] ?? null;
$winnerDriverId = $post['taken_by_driver_id'] ?? null;
if (!$rideId) { $connection->send('Error: Missing ride_id'); return; }
if ($redis) $redis->zrem('geo:rides:waiting', $rideId);
$io->emit('ride_taken', [
'ride_id' => $rideId,
'taken_by_driver_id' => $winnerDriverId,
]);
unset($active_orders_drivers[$rideId]);
logMsg("✅ Ride #$rideId taken by #$winnerDriverId.");
$connection->send('OK');
// ── 5. Force Disconnect ───────────────────────────────
} elseif ($action === 'force_disconnect') {
$driverId = $post['driver_id'] ?? null;
if ($driverId && isset($connectedDrivers[$driverId])) {
$connectedDrivers[$driverId]['conn']->disconnect();
unset($connectedDrivers[$driverId]);
if ($redis) {
$redis->zrem('geo:drivers:available', $driverId);
$redis->zrem('geo:drivers:busy', $driverId);
}
logMsg("🚫 Driver #$driverId force-disconnected.");
$connection->send('Disconnected');
} else {
$connection->send('Driver not connected');
}
// ── 6. Update Driver Destination ──────────────────────
} elseif ($action === 'update_driver_destination') {
$driverId = $post['driver_id'] ?? null;
$hasDest = isset($post['has_destination']) ? intval($post['has_destination']) : 0;
if (!$driverId || !$redis) {
$connection->send('Error: Missing driver_id or Redis unavailable');
return;
}
$profileKey = "driver:profile:$driverId";
if ($hasDest === 1) {
$destLat = $post['destination_lat'] ?? '';
$destLng = $post['destination_lng'] ?? '';
$destName = $post['destination_name'] ?? '';
$redis->hmset($profileKey, [
'has_destination' => 1,
'destination_lat' => $destLat,
'destination_lng' => $destLng,
'destination_name' => $destName
]);
$redis->expire($profileKey, 86400); // 24 Hours
logMsg("🎯 Destination set for Driver #$driverId: $destName ($destLat, $destLng)");
} else {
$redis->hmset($profileKey, ['has_destination' => 0]);
$redis->hdel($profileKey, ['destination_lat', 'destination_lng', 'destination_name']);
logMsg("🎯 Destination cleared for Driver #$driverId");
}
$connection->send('OK');
} else {
$connection->send('Unknown action');
}
};
$innerHttp->listen();
});
// ============================================================
// B. WebSocket Events للسائقين
// ============================================================
$io->on('connection', function ($socket) use ($INTERNAL_KEY) {
global $connectedDrivers, $driverState, $fwdThrottle, $eventBuffer;
$query = $socket->handshake['query'] ?? [];
$driverId = $query['driver_id'] ?? null;
$platform = $query['platform'] ?? 'android';
$token = $query['token'] ?? '';
if (!$driverId) {
$socket->disconnect();
return;
}
$socket->join('driver_' . $driverId);
$connectedDrivers[$driverId] = [
'conn' => $socket,
'platform' => $platform,
'token' => $token,
];
if (!isset($driverState[$driverId])) {
$driverState[$driverId] = [
'lat' => 0.0,
'lng' => 0.0,
'speed' => -999.0,
'heading' => -999.0,
'status' => '',
'expire_ts' => 0,
];
}
logMsg("✅ Driver Connected: #$driverId ($platform)");
$socket->on('ping_alive', function () {
// Socket.IO handles pong automatically
});
$socket->on('update_location', function ($data)
use ($driverId, $INTERNAL_KEY, &$driverState, &$fwdThrottle, &$eventBuffer)
{
global $connectedDrivers;
$data = (array) $data;
$lat = isset($data['lat']) ? (float)$data['lat'] : null;
$lng = isset($data['lng']) ? (float)$data['lng'] : null;
$heading = (float)($data['heading'] ?? 0);
$speed = (float)($data['speed'] ?? 0);
$status = (string)($data['status'] ?? 'off');
$distance = (float)($data['distance'] ?? 0);
$passengerId = (string)($data['passenger_id'] ?? '');
$rideId = $data['ride_id'] ?? null;
if ($lat === null || $lng === null) return;
$state = &$driverState[$driverId];
$now = time();
// 1. Forward للراكب (ASYNC + throttle)
if (!empty($passengerId)) {
forwardLocationToPassengerSocket(
$driverId, $passengerId,
[
'latitude' => $lat,
'longitude' => $lng,
'heading' => $heading,
'speed' => $speed,
'ride_id' => $rideId,
'driver_id' => $driverId,
],
$INTERNAL_KEY, $fwdThrottle
);
}
// 2. حساب ماذا تغيّر لتجنب ضغط Redis
$movedMeters = ($state['lat'] == 0.0 && $state['lng'] == 0.0)
? 999.0
: haversineDistance($state['lat'], $state['lng'], $lat, $lng);
$didMove = $movedMeters >= MIN_MOVE_METERS;
$speedMs = $speed / 3.6;
$speedChanged = abs($speedMs - $state['speed']) >= HMSET_SPEED_DELTA;
$headingChanged = abs($heading - $state['heading']) >= HMSET_HEADING_DELTA;
$statusChanged = ($status !== $state['status']);
$needHmset = $speedChanged || $headingChanged || $statusChanged;
$needGeoadd = $didMove;
$needExpireRefresh = ($now - $state['expire_ts']) >= EXPIRE_REFRESH_SECONDS;
if (!$needHmset && (!$needGeoadd && !$statusChanged) && !$needExpireRefresh) {
return; // لم يتغير شيء مهم، تجاهل تماماً (0 عمليات Redis)
}
// 🚀 3. Buffering Event بدل الإرسال المباشر لـ Redis (Level 2 Magic)
if (!isset($eventBuffer[$driverId])) {
$eventBuffer[$driverId] = [];
}
if ($needHmset) {
$eventBuffer[$driverId]['hmset'] = [
'id' => $driverId, 'heading' => $heading, 'speed' => $speed, 'status' => $status, 'updated_at' => $now
];
$state['speed'] = $speedMs;
$state['heading'] = $heading;
}
if ($needExpireRefresh || $needHmset) {
$eventBuffer[$driverId]['expire'] = 900;
$state['expire_ts'] = $now;
}
if ($statusChanged) {
$eventBuffer[$driverId]['status_change'] = [
'old' => $state['status'],
'new' => $status
];
$state['status'] = $status;
// Auto disconnect if blocked
if ($status === 'blocked') {
if (isset($connectedDrivers[$driverId])) {
$connectedDrivers[$driverId]['conn']->disconnect();
unset($connectedDrivers[$driverId]);
}
}
}
if ($needGeoadd || $statusChanged) {
$eventBuffer[$driverId]['geoadd'] = [
'status' => $status,
'lng' => $lng,
'lat' => $lat
];
if ($needGeoadd) {
$state['lat'] = $lat;
$state['lng'] = $lng;
}
}
});
$socket->on('disconnect', function () use ($driverId) {
global $connectedDrivers, $driverState, $fwdThrottle;
unset($connectedDrivers[$driverId]);
unset($driverState[$driverId]);
unset($fwdThrottle[$driverId]);
logMsg("❌ Driver Disconnected: #$driverId");
});
});
Worker::runAll();

View File

@@ -0,0 +1,83 @@
<?php
// find_drivers_redis.php
require_once __DIR__ . '/vendor/autoload.php';
use Predis\Client;
header('Content-Type: application/json');
$PASS_REDIS = trim(file_get_contents('/home/location/.reds_pass_key'));
// 1. استقبال البيانات (من تطبيق الراكب أو السيرفر الرئيسي)
// نفترض أنها تأتي POST أو GET
$lat = $_REQUEST['lat'] ?? null;
$lng = $_REQUEST['lng'] ?? null;
$radius = $_REQUEST['radius'] ?? 5; // القطر بالكيلومتر
$limit = $_REQUEST['limit'] ?? 50; // عدد السائقين
if (!$lat || !$lng) {
echo json_encode(['status' => false, 'message' => 'Missing coordinates']);
exit;
}
// 2. الاتصال بالريدز
try {
$redis = new Client([
'scheme' => 'tcp',
'host' => '127.0.0.1','password' => $PASS_REDIS,
'port' => 6379,
]);
$redis->connect();
} catch (Exception $e) {
echo json_encode(['status' => false, 'message' => 'Redis Error']);
exit;
}
// 3. البحث الجغرافي (السحر هنا) 🎩
// يبحث عن السائقين المتاحين (available) فقط
// التعديل: تفعيل WITHDIST و SORT كـ مفاتيح
$driversGeo = $redis->georadius(
'geo:drivers:available',
$lng,
$lat,
$radius,
'km',
['WITHDIST' => true,'WITHCOORD' => true, 'COUNT' => $limit, 'SORT' => 'ASC']
);
// النتيجة تكون مصفوفة: [ [driver_id, distance], ... ]
$resultDrivers = [];
// 4. جلب التفاصيل
if (!empty($driversGeo)) {
foreach ($driversGeo as $item) {
$driverId = $item[0];
$distance = $item[1]; // المسافة بالكيلومتر
$d_coord= $item[2]; // [0=>lng, 1=>lat] 🔥 الإحداثيات هنا
// جلب البيانات الوصفية من الرام (Profile)
// هذه البيانات خزنها السوكيت قبل قليل
$profile = $redis->hgetall("driver:profile:$driverId");
// دمج البيانات
$resultDrivers[] = [
'driver_id' => $driverId,
'distance_km' => $distance,
'heading' => $profile['heading'] ?? 0,
'speed' => $profile['speed'] ?? 0,
'status' => $profile['status'] ?? 'off',
'lat' => $d_coord[1], // Latitude من الريدز (أدق)
'lng' => $d_coord[0] // Longitude
// يمكنك هنا إضافة استعلام MySQL بسيط لجلب اسم السائق وصورته إذا لم تكن مخزنة في الريدز
// لكن للسرعة، سنكتفي بهذا الآن
];
}
}
// 5. إرجاع النتيجة
echo json_encode([
'status' => true,
'count' => count($resultDrivers),
'drivers' => $resultDrivers
]);
?>

3
loction_server/index.php Executable file
View File

@@ -0,0 +1,3 @@
<?php
echo 'Hello World :-)';

View File

@@ -0,0 +1,325 @@
-- phpMyAdmin SQL Dump
-- version 5.2.2
-- https://www.phpmyadmin.net/
--
-- Host: 127.0.0.1:3306
-- Generation Time: Jun 29, 2026 at 09:46 PM
-- Server version: 8.0.36-28
-- PHP Version: 8.1.32
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `locationDB`
--
-- --------------------------------------------------------
--
-- Table structure for table `car_locations`
--
CREATE TABLE `car_locations` (
`driver_id` varchar(100) NOT NULL,
`latitude` decimal(10,7) NOT NULL,
`longitude` decimal(10,7) NOT NULL,
`heading` decimal(10,2) NOT NULL,
`speed` double(10,3) NOT NULL,
`distance` decimal(10,2) NOT NULL,
`status` varchar(6) NOT NULL DEFAULT 'off',
`carType` varchar(100) NOT NULL DEFAULT 'Awfar',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`location_point` point NOT NULL SRID 4326
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
--
-- Triggers `car_locations`
--
DELIMITER $$
CREATE TRIGGER `trg_before_insert_car_locations` BEFORE INSERT ON `car_locations` FOR EACH ROW BEGIN
SET NEW.location_point = ST_PointFromText(CONCAT('POINT(', NEW.longitude, ' ', NEW.latitude, ')'), 4326);
END
$$
DELIMITER ;
DELIMITER $$
CREATE TRIGGER `trg_before_update_car_locations` BEFORE UPDATE ON `car_locations` FOR EACH ROW BEGIN
IF NEW.latitude <> OLD.latitude OR NEW.longitude <> OLD.longitude THEN
SET NEW.location_point = ST_PointFromText(CONCAT('POINT(', NEW.longitude, ' ', NEW.latitude, ')'), 4326);
END IF;
END
$$
DELIMITER ;
-- --------------------------------------------------------
--
-- Table structure for table `car_tracks`
--
CREATE TABLE `car_tracks` (
`id` int NOT NULL,
`driver_id` varchar(100) NOT NULL,
`latitude` decimal(10,7) NOT NULL,
`longitude` decimal(10,7) NOT NULL,
`heading` float DEFAULT NULL,
`speed` float DEFAULT NULL,
`distance` float DEFAULT NULL,
`status` enum('on','off') DEFAULT 'off',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
--
-- Table structure for table `driver_behavior`
--
CREATE TABLE `driver_behavior` (
`id` int NOT NULL,
`driver_id` varchar(255) NOT NULL,
`trip_id` varchar(255) NOT NULL,
`max_speed` double DEFAULT '0',
`avg_speed` double DEFAULT '0',
`hard_brakes` int DEFAULT '0',
`total_distance` double DEFAULT '0',
`behavior_score` double DEFAULT '0',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- --------------------------------------------------------
--
-- Table structure for table `driver_daily_summary`
--
CREATE TABLE `driver_daily_summary` (
`id` int NOT NULL,
`driver_id` varchar(33) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`date` date NOT NULL,
`total_seconds` int DEFAULT '0',
`last_updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-- --------------------------------------------------------
--
-- Table structure for table `driver_daily_work`
--
CREATE TABLE `driver_daily_work` (
`driver_id` int NOT NULL,
`work_date` date NOT NULL,
`total_seconds` int NOT NULL DEFAULT '0',
`last_point_at` datetime DEFAULT NULL,
`last_status` enum('on','off') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'off',
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `driver_orders`
--
CREATE TABLE `driver_orders` (
`id` int NOT NULL,
`driver_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`order_id` varchar(99) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`notes` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'nothing',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`status` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'applied'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `login_attempts`
--
CREATE TABLE `login_attempts` (
`id` int NOT NULL,
`ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`attempt_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `login_attempts_drivers`
--
CREATE TABLE `login_attempts_drivers` (
`id` int NOT NULL,
`ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`attempt_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `places`
--
CREATE TABLE `places` (
`id` int NOT NULL,
`latitude` double NOT NULL,
`longitude` double NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`name_ar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`name_en` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `server_locations`
--
CREATE TABLE `server_locations` (
`id` int NOT NULL,
`name` varchar(255) NOT NULL,
`min_latitude` decimal(10,6) NOT NULL,
`max_latitude` decimal(10,6) NOT NULL,
`min_longitude` decimal(10,6) NOT NULL,
`max_longitude` decimal(10,6) NOT NULL,
`server_link` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `car_locations`
--
ALTER TABLE `car_locations`
ADD PRIMARY KEY (`driver_id`),
ADD KEY `idx_loc_status_time` (`status`,`updated_at`,`latitude`,`longitude`),
ADD SPATIAL KEY `idx_location_point` (`location_point`);
--
-- Indexes for table `car_tracks`
--
ALTER TABLE `car_tracks`
ADD PRIMARY KEY (`id`),
ADD KEY `idx_driver_time` (`driver_id`,`created_at`);
--
-- Indexes for table `driver_behavior`
--
ALTER TABLE `driver_behavior`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `driver_daily_summary`
--
ALTER TABLE `driver_daily_summary`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `unique_driver_date` (`driver_id`,`date`);
--
-- Indexes for table `driver_daily_work`
--
ALTER TABLE `driver_daily_work`
ADD PRIMARY KEY (`driver_id`,`work_date`),
ADD KEY `idx_driver_date` (`driver_id`,`work_date`);
--
-- Indexes for table `driver_orders`
--
ALTER TABLE `driver_orders`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `login_attempts`
--
ALTER TABLE `login_attempts`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `login_attempts_drivers`
--
ALTER TABLE `login_attempts_drivers`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `places`
--
ALTER TABLE `places`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `server_locations`
--
ALTER TABLE `server_locations`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `name` (`name`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `car_tracks`
--
ALTER TABLE `car_tracks`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `driver_behavior`
--
ALTER TABLE `driver_behavior`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `driver_daily_summary`
--
ALTER TABLE `driver_daily_summary`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `driver_orders`
--
ALTER TABLE `driver_orders`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `login_attempts`
--
ALTER TABLE `login_attempts`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `login_attempts_drivers`
--
ALTER TABLE `login_attempts_drivers`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `places`
--
ALTER TABLE `places`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `server_locations`
--
ALTER TABLE `server_locations`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

64
loction_server/siro/connect.php Executable file
View File

@@ -0,0 +1,64 @@
<?php
// Load environment variables from .env file
require_once realpath(__DIR__ . '/../vendor/autoload.php');
require_once __DIR__ . '/load_env.php';
$env_file = file_exists(__DIR__ . '/../loction-keys/.env')
? __DIR__ . '/../loction-keys/.env'
: (file_exists('/home/location/env/.env') ? '/home/location/env/.env' : '');
if (!empty($env_file)) {
loadEnvironment($env_file);
}
// Get environment variables (You don't need user/pass for JWT auth itself)
$secretKeyFile = file_exists(__DIR__ . '/../loction-keys/.secret_key')
? __DIR__ . '/../loction-keys/.secret_key'
: '/home/location/.secret_key';
$secretKey = trim((string) @file_get_contents($secretKeyFile));
// Only need the secret key now
// Debug loaded environment variables
// --- CORS Headers ---
header("Access-Control-Allow-Origin: https://intaleqapp.com"); // Replace * with your Flutter app's origin
header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); // Adjust as needed
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header('Content-Type: application/json'); // Set content type to JSON
//SET time_zone = 'Asia/Damascus';
date_default_timezone_set('Asia/Damascus');
// Handle preflight requests (OPTIONS)
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
http_response_code(200);
exit;
}
// $TRIPZ_SMTP_PASSWORD=getenv('TRIPZ_SMTP_PASSWORD');
$dbname = getenv('dbname');
// --- Database Connection (Still needed for your application logic) ---
try {
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
$options = [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
];
$user = getenv('USER'); // Still used for DB connection
$pass = getenv('PASS'); // Still used for DB connection
$con = new PDO($dsn, $user, $pass, $options);
//$con->exec("SET time_zone = '+03:00'");
// --- JWT Authentication ---
// include "encrypt_decrypt.php";
include "functions.php"; // Include the functions file
$decodedToken = authenticateJWT(); // Call the authentication function
} catch (PDOException $e) {
error_log($e->getMessage());
http_response_code(500); // Internal Server Error
echo json_encode(['error' => 'A database error occurred.']);
exit;
}
?>

468
loction_server/siro/functions.php Executable file
View File

@@ -0,0 +1,468 @@
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;
//functions.php for location server
// --- JWT Authentication Function (Moved here for better organization) ---
//include "encrypt_decrypt.php";
// --- 3. دالة توجيه الموقع لسيرفر الركاب ---
function forwardLocationToPassengerSocket($passengerId, $payload) {
if (empty($passengerId)) return;
// نفترض أن سيرفر الركاب يعمل محلياً على 3031
$url = "http://127.0.0.1:3031";
$INTERNAL_KEY = trim(file_get_contents('/home/location/.internal_socket_key'));
$postData = [
'action' => 'update_driver_location',
'passenger_id' => $passengerId,
'payload' => $payload
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 100);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $INTERNAL_KEY"]);
curl_exec($ch);
curl_close($ch);
}
// 2. استدعها داخل $socket->on('update_location'...)
// يجب أن يرسل السائق passenger_id معه في الـ update_location أو تكون مخزنة في الـ session
// $socket->on('update_location', function($data) use ($socket) {
// ... كود الحفظ في الداتابيز ...
//
// if (!empty($data['passenger_id'])) {
// forwardLocationToPassengerSocket($data['passenger_id'], $data);
// }
// });
function authenticateJWT()
{
$secretKey = trim(file_get_contents('/home/location/.secret_key')); // Access secret key (ensure it's set in .env)
if (!$secretKey) {
error_log("SECRET_KEY not set in environment variables.");
http_response_code(500); // Internal Server Error
echo json_encode(['error' => 'Internal server configuration error.']);
exit;
}
// 1. Get the JWT from the Authorization header
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token = null;
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
$token = $matches[1];
}
// 2. Check if the token exists
if (!$token) {
http_response_code(401); // Unauthorized
echo json_encode(['error' => 'Authorization token required']);
exit;
}
// 3. Verify the JWT
try {
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
/* // 4. Validate claims (audience, issuer)
$decrypted_aud = $encryptionHelper->decryptData($decoded->aud);
$allowedAudiences = [getenv('allowed1'), getenv('allowed2'),getenv('allowedDriver1'),getenv('allowedDriver2'),
getenv('allowedService1'), getenv('allowedService2') ]; // "passenger", "driver"
if (!in_array($decrypted_aud, $allowedAudiences)) {
throw new Exception('Invalid audience');
error_log("[Debug] 'Invalid audience'");
}
$decrypted_iss = $encryptionHelper->decryptData($decoded->iss ?? '');
if ($decrypted_iss !== 'Tripz') {
throw new Exception('Invalid issuer');
error_log("[Debug] 'Invalid issuer'");
}
*/
// 5. Authentication successful!
return $decoded; // Return the decoded payload
} catch (ExpiredException $e) {
http_response_code(401);
echo json_encode(['error' => 'Token expired']);
exit;
} catch (SignatureInvalidException $e) {
http_response_code(401);
echo json_encode(['error' => 'Invalid token signature']);
exit;
} catch (BeforeValidException $e) {
http_response_code(401);
echo json_encode(['error' => 'Token not yet valid']);
exit;
} catch (Exception $e) {
http_response_code(401);
echo json_encode(['error' => 'Invalid token: ' . $e->getMessage()]);
exit;
}
}
define("MB", 1048576);
/**
* Send WhatsApp message using your server's API
*
* @param string $to The recipient phone number (e.g., 96279xxxxxxx)
* @param string $message The message to send
* @return mixed API response object or false on failure
*/
function sendWhatsAppFromServer($to, $message)
{
// 1) قائمة السيرفرات المتاحة
$servers = [
"https://whatsapp.intaleq.xyz/send"
//,
//"https://bot3.intaleq.xyz/send"
];
// 2) اختيار عشوائي
$url = $servers[array_rand($servers)];
// 3) إعداد البيانات
$payload = [
"to" => $to,
"message" => $message
];
// 4) تنفيذ الطلب
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
// 5) تسجيل النتيجة
if ($err) {
error_log("[sendWhatsAppFromServer] cURL Error on $url: $err");
return false;
}
return json_decode($response, true);
}
function debugLog($message) {
error_log($message);
}
function filterRequest($requestname, $type = 'string') {
if (isset($_POST[$requestname]) && !empty($_POST[$requestname])) {
$value = trim($_POST[$requestname]);
// Remove any control characters
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $value);
// Remove any HTML or XML tags
$value = strip_tags($value);
// Escape any special characters
$value = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
if ($type === 'numeric') {
if (filter_var($value, FILTER_VALIDATE_FLOAT) !== false) {
return $value;
}
} else {
return $value;
}
}
return null;
}
function getAllData($table, $where = null, $values = null, $json = true)
{
global $con;
$data = array();
if ($where == null) {
$stmt = $con->prepare("SELECT * FROM $table ");
} else {
$stmt = $con->prepare("SELECT * FROM $table WHERE $where ");
}
$stmt->execute($values);
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$count = $stmt->rowCount();
if ($json == true) {
if ($count > 0) {
echo json_encode(array("status" => "success","count" => $count, "data" => $data));
} else {
echo json_encode(array("status" => "failure"));
}
return $count;
} else {
if ($count > 0) {
return $data;
} else {
return json_encode(array("status" => "failure"));
}
}
}
function getData($table, $where = null, $values = null)
{
global $con;
$data = array();
$stmt = $con->prepare("SELECT * FROM $table WHERE $where ");
$stmt->execute($values);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
$count = $stmt->rowCount();
if ($count > 0) {
echo json_encode(array("status" => "success", "count" => $count, "data" => $data));
} else {
echo json_encode(array("status" => "failure"));
}
return $count;
}
function insertData($table, $data, $json = true)
{
global $con;
foreach ($data as $field => $v)
$ins[] = ':' . $field;
$ins = implode(',', $ins);
$fields = implode(',', array_keys($data));
$sql = "INSERT INTO $table ($fields) VALUES ($ins)";
$stmt = $con->prepare($sql);
foreach ($data as $f => $v) {
$stmt->bindValue(':' . $f, $v);
}
$stmt->execute();
$count = $stmt->rowCount();
if ($json == true) {
if ($count > 0) {
echo json_encode(array("status" => "success"));
} else {
echo json_encode(array("status" => "failure"));
}
}
return $count;
}
function updateData($table, $data, $where, $json = true)
{
global $con;
$cols = array();
$vals = array();
foreach ($data as $key => $val) {
$vals[] = "$val";
$cols[] = "`$key` = ? ";
}
$sql = "UPDATE $table SET " . implode(', ', $cols) . " WHERE $where";
$stmt = $con->prepare($sql);
$stmt->execute($vals);
$count = $stmt->rowCount();
if ($json == true) {
if ($count > 0) {
echo json_encode(array("status" => "success"));
} else {
echo json_encode(array("status" => "failure"));
}
}
return $count;
}
function deleteData($table, $where, $json = true)
{
global $con;
$stmt = $con->prepare("DELETE FROM $table WHERE $where");
$stmt->execute();
$count = $stmt->rowCount();
if ($json == true) {
if ($count > 0) {
echo json_encode(array("status" => "success"));
} else {
echo json_encode(array("status" => "failure"));
}
}
return $count;
}
function imageUpload($imageRequest)
{
global $msgError;
$imagename = rand(1000, 10000) . $_FILES[$imageRequest]['name'];
$imagetmp = $_FILES[$imageRequest]['tmp_name'];
$imagesize = $_FILES[$imageRequest]['size'];
$allowExt = array("jpg", "png", "gif", "mp3", "pdf");
$strToArray = explode(".", $imagename);
$ext = end($strToArray);
$ext = strtolower($ext);
if (!empty($imagename) && !in_array($ext, $allowExt)) {
$msgError = "EXT";
}
if ($imagesize > 2 * MB) {
$msgError = "size";
}
if (empty($msgError)) {
move_uploaded_file($imagetmp, "../upload/" . $imagename);
return $imagename;
} else {
return "fail";
}
}
function deleteFile($dir, $imagename)
{
if (file_exists($dir . "/" . $imagename)) {
unlink($dir . "/" . $imagename);
}
}
// function checkAuthenticate()
// {
// if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
// if ($_SERVER['PHP_AUTH_USER'] != "hamzaayedphp" || $_SERVER['PHP_AUTH_PW'] != "malDEV@2101") {
// header('WWW-Authenticate: Basic realm="My Realm"');
// header('HTTP/1.0 401 Unauthorized');
// echo 'Unauthorized';
// exit;
// }
// } else {
// exit;
// }
// // End
// }
function checkAuthenticate($username, $password)
{
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
// Redirect to HTTPS
header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
exit;
}
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
if ($_SERVER['PHP_AUTH_USER'] !== $username || $_SERVER['PHP_AUTH_PW'] !== $password) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo 'Unauthorized';
exit;
}
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo 'Unauthorized';
exit;
}
// Continue with authenticated code
}
// function checkAuthenticate()
// {
// global $secretKey;
// if (!isset($_SERVER['HTTP_AUTHORIZATION'])) {
// header('HTTP/1.0 401 Unauthorized');
// echo json_encode(['error' => 'Unauthorized']);
// exit;
// }
// $authHeader = $_SERVER['HTTP_AUTHORIZATION'];
// list($token) = sscanf($authHeader, 'Bearer %s');
// if (!$token) {
// header('HTTP/1.0 401 Unauthorized');
// echo json_encode(['error' => 'Token not provided']);
// exit;
// }
// try {
// $decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
// return $decoded;
// } catch (Exception $e) {
// header('HTTP/1.0 401 Unauthorized');
// echo json_encode(['error' => 'Invalid token']);
// exit;
// }
// }
function divideAndAddText($apiKey, $text) {
$parts = str_split($apiKey, strlen($apiKey) / 4);
$dividedApiKey = array();
$dividedApiKey['birinci'] = $parts[4] . $text;
$dividedApiKey['ikinci'] = $text . $parts[2] . $text;
$dividedApiKey['üçüncü'] = $text . $parts[1] . $text;
$dividedApiKey['dördüncü'] = $parts[0] . $text;
$dividedApiKey['beş'] = $text . $parts[3] . $text;
$concatenatedApiKey = implode('', $dividedApiKey);
return $concatenatedApiKey;
}
function retrieveOriginalApiKey($concatenatedApiKey, $text) {
$originalApiKey = str_replace($text, '', $concatenatedApiKey);
$resortedApiKey = array();
$resortedApiKey['birinci'] = $originalApiKey[strlen($originalApiKey) - 5] . $originalApiKey[strlen($originalApiKey) - 3];
$resortedApiKey['ikinci'] = $originalApiKey[strlen($originalApiKey) - 1] . $originalApiKey[strlen($originalApiKey) - 15];
$resortedApiKey['üçüncü'] = $originalApiKey[strlen($originalApiKey) - 9] . $originalApiKey[strlen($originalApiKey) - 12];
$resortedApiKey['dördüncü'] = $originalApiKey[strlen($originalApiKey) - 11] . $originalApiKey[strlen($originalApiKey) - 6];
$resortedApiKey['beş'] = $originalApiKey[strlen($originalApiKey) - 2] . $originalApiKey[strlen($originalApiKey) - 8];
return $resortedApiKey;
}
//////////
function printFailure($message = "none")
{
echo json_encode(array("status" => "failure", "message" => $message));
}
function printSuccess($message = "none")
{
echo json_encode(array("status" => "success", "message" => $message));
}
function result($count)
{
if ($count > 0) {
printSuccess();
} else {
printFailure();
}
}
function sendEmail($from,$to, $title, $body)
{
$header = "From: $from" . "\n" . "CC: $from";
mail($to, $title, $body, $header);
}

View File

@@ -0,0 +1,64 @@
<?php
// Load environment variables from .env file
require_once realpath(__DIR__ . '/../vendor/autoload.php');
require_once __DIR__ . '/load_env.php';
$env_file = file_exists(__DIR__ . '/../loction-keys/.env')
? __DIR__ . '/../loction-keys/.env'
: (file_exists('/home/location/env/.env') ? '/home/location/env/.env' : '');
if (!empty($env_file)) {
loadEnvironment($env_file);
}
// Get environment variables (You don't need user/pass for JWT auth itself)
$secretKeyFile = file_exists(__DIR__ . '/../loction-keys/.secret_key')
? __DIR__ . '/../loction-keys/.secret_key'
: '/home/location/.secret_key';
$secretKey = trim((string) @file_get_contents($secretKeyFile));
// Only need the secret key now
// Debug loaded environment variables
// --- CORS Headers ---
header("Access-Control-Allow-Origin: https://intaleqapp.com"); // Replace * with your Flutter app's origin
header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); // Adjust as needed
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header('Content-Type: application/json'); // Set content type to JSON
//SET time_zone = 'Asia/Damascus';
date_default_timezone_set('Asia/Damascus');
// Handle preflight requests (OPTIONS)
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
http_response_code(200);
exit;
}
// $TRIPZ_SMTP_PASSWORD=getenv('TRIPZ_SMTP_PASSWORD');
$dbname = getenv('dbname');
// --- Database Connection (Still needed for your application logic) ---
try {
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
$options = [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
];
$user = getenv('USER'); // Still used for DB connection
$pass = getenv('PASS'); // Still used for DB connection
$con = new PDO($dsn, $user, $pass, $options);
//$con->exec("SET time_zone = '+03:00'");
// --- JWT Authentication ---
// include "encrypt_decrypt.php";
include "functions.php"; // Include the functions file
// $decodedToken = authenticateJWT(); // Call the authentication function
} catch (PDOException $e) {
error_log($e->getMessage());
http_response_code(500); // Internal Server Error
echo json_encode(['error' => 'A database error occurred.']);
exit;
}
?>

View File

@@ -0,0 +1,23 @@
<?php
function loadEnvironment($env_file) {
if (!file_exists($env_file)) {
error_log("❌ .env not found: $env_file");
return false;
}
$lines = file($env_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || strpos($line, '#') === 0) continue;
$parts = explode('=', $line, 2);
if (count($parts) === 2) {
[$keyName, $value] = $parts;
$value = trim($value, "\"'");
putenv("$keyName=$value");
$_ENV[$keyName] = $value;
$_SERVER[$keyName] = $value;
}
}
return true;
}

View File

@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تتبع السائقين - Tripz</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
body { margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, sans-serif; overflow: hidden; }
#map { height: 100vh; width: 100%; z-index: 1; }
/* تصميم لوحة التحكم */
.dashboard {
position: absolute; top: 20px; right: 20px; width: 280px;
background: rgba(255, 255, 255, 0.95); padding: 15px;
border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.15);
z-index: 1000; text-align: right; border: 1px solid #ddd;
}
.dashboard h2 { margin: 0 0 10px 0; font-size: 16px; color: #333; border-bottom: 2px solid #3498db; padding-bottom: 8px; }
/* أزرار التبديل */
.mode-switcher { display: flex; gap: 5px; margin-bottom: 15px; }
.mode-btn {
flex: 1; padding: 8px; border: 1px solid #3498db; background: white;
color: #3498db; cursor: pointer; border-radius: 4px; font-size: 12px; transition: 0.2s;
}
.mode-btn.active { background: #3498db; color: white; font-weight: bold; }
/* الإحصائيات */
.info-row { display: flex; justify-content: space-between; margin-bottom: 5px; font-size: 13px; }
.val-active { color: #27ae60; font-weight: bold; }
.update-time { font-size: 11px; color: #7f8c8d; text-align: center; margin-top: 10px; display: block; }
/* زر التحديث */
.refresh-btn {
width: 100%; margin-top: 8px; padding: 8px; background: #2c3e50;
color: white; border: none; border-radius: 4px; cursor: pointer;
}
.refresh-btn:hover { background: #34495e; }
/* أيقونة السيارة */
.car-wrapper { transition: transform 0.5s ease; }
.car-svg { width: 35px; height: 35px; filter: drop-shadow(0px 2px 2px rgba(0,0,0,0.3)); }
/* Popup */
.popup-content { text-align: right; font-size: 12px; }
</style>
</head>
<body>
<div id="map"></div>
<div class="dashboard">
<h2>نظام التتبع المباشر</h2>
<div class="mode-switcher">
<button class="mode-btn active" id="btnLive" onclick="setMode('live')">مباشر (20د)</button>
<button class="mode-btn" id="btnDay" onclick="setMode('day')">اليوم كامل</button>
</div>
<div class="info-row">
<span>الوضع:</span>
<span id="modeLabel" style="font-weight:bold; color:#2980b9;">مباشر</span>
</div>
<div class="info-row">
<span>السائقين:</span>
<span id="countVal" class="val-active">0</span>
</div>
<small id="statusMsg" class="update-time">جاري التحميل...</small>
<button class="refresh-btn" onclick="fetchData()">تحديث البيانات</button>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// ================= إعدادات النظام =================
const BASE_URL = "https://location.intaleq.xyz/intaleq/ride/";
let currentMode = 'live';
// إعداد الخريطة
const map = L.map('map').setView([33.513, 36.276], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Tripz System'
}).addTo(map);
const markersGroup = L.layerGroup().addTo(map);
// ================= دوال التحكم =================
function setMode(mode) {
currentMode = mode;
// تحديث الأزرار
document.getElementById('btnLive').className = mode === 'live' ? 'mode-btn active' : 'mode-btn';
document.getElementById('btnDay').className = mode === 'day' ? 'mode-btn active' : 'mode-btn';
// تحديث البيانات
fetchData();
}
function fetchData() {
const statusElem = document.getElementById('statusMsg');
statusElem.innerText = "جاري الاتصال بالسيرفر...";
statusElem.style.color = "#7f8c8d";
// تحديد اسم الملف
const fileName = currentMode === 'day' ? 'locations_day.json' : 'locations_live.json';
const finalUrl = BASE_URL + fileName + '?t=' + Date.now(); // Cache busting
fetch(finalUrl)
.then(response => {
// التحقق من أن الملف موجود
if (!response.ok) {
throw new Error("الملف غير موجود (404)");
}
// التحقق من نوع المحتوى (لتجنب Hello World)
const contentType = response.headers.get("content-type");
if (contentType && contentType.indexOf("application/json") === -1) {
throw new Error("البيانات ليست JSON (الرجاء تشغيل ملف PHP أولاً)");
}
return response.json();
})
.then(data => {
renderMap(data);
statusElem.innerText = "آخر تحديث: " + (data.last_updated || "الآن");
})
.catch(err => {
console.error("Error:", err);
statusElem.innerText = "خطأ: " + err.message;
statusElem.style.color = "#e74c3c";
// لا نمسح الخريطة عند الخطأ للحفاظ على آخر عرض
});
}
function renderMap(json) {
markersGroup.clearLayers();
const drivers = json.drivers || [];
document.getElementById('countVal').innerText = json.count || drivers.length;
document.getElementById('modeLabel').innerText = json.title || (currentMode=='day'?'يومي':'مباشر');
let bounds = [];
drivers.forEach(d => {
// البحث الذكي عن الإحداثيات
let lat = parseFloat(d.lat || d.latitude || d.Lat || d.LAT);
let lon = parseFloat(d.lon || d.lng || d.longitude || d.long || d.Lon || d.LNG);
if (!isNaN(lat) && !isNaN(lon) && lat !== 0 && lon !== 0) {
bounds.push([lat, lon]);
const color = currentMode === 'live' ? '#27ae60' : '#2980b9'; // أخضر للمباشر، أزرق لليومي
const heading = d.heading || 0;
const iconHtml = `
<div class="car-wrapper" style="transform: rotate(${heading}deg);">
<svg class="car-svg" viewBox="0 0 24 24" fill="${color}">
<path d="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5.67 1.5 1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z"/>
</svg>
</div>`;
const icon = L.divIcon({ className: '', html: iconHtml, iconSize: [35, 35], iconAnchor: [17.5, 17.5] });
L.marker([lat, lon], {icon: icon})
.bindPopup(`
<div class="popup-content">
<strong>ID:</strong> ${d.driver_id || d.id}<br>
<strong>السرعة:</strong> ${d.speed || 0}<br>
<strong>الوقت:</strong> ${d.updated_at}
</div>
`)
.addTo(markersGroup);
}
});
// توجيه الكاميرا تلقائياً (Auto Zoom)
if (bounds.length > 0) {
map.fitBounds(bounds, { padding: [50, 50] });
}
}
// بدء التشغيل
fetchData();
// تحديث تلقائي كل 15 ثانية
setInterval(fetchData, 15000);
</script>
</body>
</html>

View File

@@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>خريطة الكثافة الحرارية (Grid Heatmap) - OpenStreetMap</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
body { margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
#map { height: 100vh; width: 100%; }
.info-box {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
max-width: 300px;
}
.legend {
margin-top: 10px;
line-height: 1.5;
}
.legend i {
width: 18px;
height: 18px;
float: right;
margin-left: 8px;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="info-box">
<h3>تحليل كثافة الرحلات</h3>
<p>هذه الخريطة تقسم المنطقة إلى مربعات جغرافية وتحسب عدد الرحلات في كل مربع.</p>
<div class="legend">
<div><i style="background: #bd0026"></i> طلبات عالية جداً (+5)</div>
<div><i style="background: #f03b20"></i> طلبات عالية (3-4)</div>
<div><i style="background: #fd8d3c"></i> طلبات متوسطة (2)</div>
<div><i style="background: #feb24c"></i> طلب واحد (1)</div>
</div>
</div>
<div id="map"></div>
<script>
// 1. تهيئة الخريطة (مركزها دمشق مبدئياً)
var map = L.map('map').setView([33.513, 36.276], 13);
// 2. إضافة طبقة OpenStreetMap
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// 3. البيانات المستخرجة من ملفك (ride.json)
// ملاحظة: قمت بوضع البيانات هنا لمحاكاتها، في الواقع سيقوم السكربت بطلبها من ملف heatmap.json
var rawData = [
{"type":"header","version":"5.2.2","comment":"Export to JSON plugin for PHPMyAdmin"},
{"type":"database","name":"intaleq-ridesDB"},
{"type":"table","name":"ride","database":"intaleq-ridesDB","data": [
// ... تم نسخ عينة من بياناتك هنا لتعمل الصفحة ...
{"start_location":"33.4323,36.2432"}, {"start_location":"34.68947,36.36329"},
{"start_location":"33.5445,36.30571"}, {"start_location":"33.4323,36.24326"},
{"start_location":"33.43222,36.24319"}, {"start_location":"33.51326,36.27646"},
{"start_location":"33.50895,36.29209"}, {"start_location":"33.49631,36.3221"},
{"start_location":"33.55463,36.32338"}, {"start_location":"33.53447,36.29727"},
{"start_location":"33.5727,36.192"}, {"start_location":"33.52811,36.37998"},
{"start_location":"33.54115,36.21846"}, {"start_location":"33.4323,36.24309"},
{"start_location":"33.50312,36.25959"}, {"start_location":"33.52518,36.35682"},
{"start_location":"33.51814,36.3119"}, {"start_location":"33.50142,36.27113"},
{"start_location":"33.49593,36.30942"}, {"start_location":"33.55189,36.32245"},
{"start_location":"33.5322,36.29513"}, {"start_location":"33.53994,36.22878"},
{"start_location":"33.52441,36.28758"}, {"start_location":"33.46744,36.19679"},
{"start_location":"33.52842,36.23082"}, {"start_location":"33.50236,36.27406"},
{"start_location":"33.4323,36.24331"}, {"start_location":"33.51246,36.29807"}
// (ملاحظة: البيانات هنا هي عينة لتعمل الصفحة، يمكنك استبدالها ببياناتك الكاملة)
]}
];
// في حال أردت استخدام بياناتك الكاملة، الصق محتوى المصفوفة "data" من ملفك داخل المتغير أدناه
// سأقوم الآن باستخراج البيانات من الهيكل المعقد الذي أرسلته
var rides = rawData[2].data;
// 4. خوارزمية الشبكة (Grid Algorithm)
var grid = {};
var precision = 0.005; // حجم المربع (تقريباً 500 متر). صغّر الرقم لـ 0.002 لدقة أعلى
rides.forEach(function(ride) {
if(ride.start_location) {
var coords = ride.start_location.split(',');
var lat = parseFloat(coords[0]);
var lng = parseFloat(coords[1]);
// استثناء القيم الصفرية أو البعيدة جداً
if(lat > 32 && lat < 38 && lng > 35 && lng < 39) {
// حساب مفتاح الشبكة (تقريب الإحداثيات)
// Math.floor(lat / precision) * precision -> يقوم بتوحيد الأرقام القريبة
var gridLat = Math.floor(lat / precision) * precision;
var gridLng = Math.floor(lng / precision) * precision;
var key = gridLat.toFixed(3) + "_" + gridLng.toFixed(3);
if(!grid[key]) {
grid[key] = {
lat: gridLat,
lng: gridLng,
count: 0
};
}
grid[key].count++;
}
}
});
// 5. دالة تحديد اللون بناءً على العدد
function getColor(d) {
return d > 5 ? '#bd0026' : // أحمر داكن (حار جداً)
d > 3 ? '#f03b20' : // أحمر
d > 1 ? '#fd8d3c' : // برتقالي
'#feb24c'; // أصفر (بارد)
}
// 6. رسم المربعات على الخريطة
var bounds = []; // لتحديد حدود الخريطة النهائية
for (var key in grid) {
var zone = grid[key];
// تحديد زوايا المربع
var southWest = [zone.lat, zone.lng];
var northEast = [zone.lat + precision, zone.lng + precision];
var zoneBounds = [southWest, northEast];
// رسم المستطيل
L.rectangle(zoneBounds, {
color: getColor(zone.count),
weight: 1,
fillOpacity: 0.6
}).bindPopup("<b>عدد الرحلات:</b> " + zone.count)
.addTo(map);
bounds.push(southWest);
bounds.push(northEast);
}
// 7. تحريك الكاميرا لتشمل كل النقاط
if(bounds.length > 0) {
map.fitBounds(bounds);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,80 @@
<?php
include "../../connect.php";
header('Content-Type: application/json; charset=utf-8');
//https://location.intaleq.xyz/intaleq/ride/location/add.php
try {
$driver_id = filterRequest("driver_id");
$latitude = filterRequest("latitude");
$longitude = filterRequest("longitude");
$status = filterRequest("status");
$heading = filterRequest("heading");
$speed = filterRequest("speed");
// ملاحظة: قمنا بتجاهل device_timestamp هنا لضمان دقة الترتيب الزمني في السيرفر
// إذا كنت تحتاج وقت الهاتف لأغراض الترتيب في حالة انقطاع النت، يفضل إضافته في عمود منفصل مستقبلاً
// 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);
$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)) {
printFailure("Invalid payload");
exit;
}
// ---------------------------------------------------------
// التعديل الجوهري هنا:
// ---------------------------------------------------------
// تم حذف منطق حساب الوقت بواسطة PHP أو الهاتف.
// سنستخدم NOW() داخل جملة SQL مباشرة لضمان توقيت UTC موحد.
$sql = "INSERT INTO `car_tracks`
(`driver_id`,`latitude`,`longitude`,`heading`,`speed`,`distance`,`status`,`created_at`)
VALUES
(:driver_id, :latitude, :longitude, :heading, :speed, :distance, :status, NOW())";
// 👆 استخدمنا NOW() بدلاً من المتغير
$stmt = $con->prepare($sql);
$ok = $stmt->execute([
':driver_id' => $driver_id,
':latitude' => $lat,
':longitude' => $lng,
':heading' => $head,
':speed' => $spd,
':distance' => $dist,
':status' => (string)($status ?? 'on'),
// ':created_at' => $created_at_to_use, // 👈 تم حذف هذا السطر لأنه لم يعد مطلوباً
]);
if ($ok) {
printSuccess("car_tracks saved successfully");
} else {
printFailure("Failed to save car track");
}
} catch (PDOException $e) {
// يفضل عدم طباعة تفاصيل الخطأ للمستخدم النهائي في الإنتاج، لكن لا بأس للـ Debug
error_log("car_tracks insert error: " . $e->getMessage());
printFailure("Database error");
} catch (Throwable $e) {
error_log("car_tracks insert fatal: " . $e->getMessage());
printFailure("Server error");
}
?>

View File

@@ -0,0 +1,129 @@
<?php
// add_batch.php
include "../../connect.php";
header('Content-Type: application/json; charset=utf-8');
try {
// 1. استقبال البيانات
$driver_id = filterRequest("driver_id");
$json_batch = $_POST['batch_data'];
if (empty($driver_id) || empty($json_batch)) {
printFailure("No data received");
exit;
}
$points = json_decode($json_batch, true);
if (!is_array($points) || count($points) === 0) {
printFailure("Invalid JSON");
exit;
}
// ---------------------------------------------------------
// الجزء الأول: حساب مدة العمل الإضافية (PHP Calculation)
// ---------------------------------------------------------
// أ. ترتيب النقاط زمنياً للتأكد (احتياطاً)
usort($points, function($a, $b) {
return strtotime($a['ts']) - strtotime($b['ts']);
});
$batch_added_seconds = 0;
$first_point_time = strtotime($points[0]['ts']);
// ب. جلب آخر وقت مسجل لهذا السائق من قاعدة البيانات
// (لحساب الزمن الضائع بين الباتش السابق وأول نقطة في هذا الباتش)
$stmtLast = $con->prepare("SELECT created_at FROM car_tracks WHERE driver_id = ? ORDER BY created_at DESC LIMIT 1");
$stmtLast->execute([$driver_id]);
$lastRow = $stmtLast->fetch(PDO::FETCH_ASSOC);
if ($lastRow) {
$last_db_time = strtotime($lastRow['created_at']);
$diff_from_db = $first_point_time - $last_db_time;
// إذا كان الفرق منطقياً (أقل من 5 دقائق) وموجباً، نحسبه
if ($diff_from_db > 0 && $diff_from_db < 300) {
$batch_added_seconds += $diff_from_db;
}
}
// ج. حساب الفروقات داخل الباتش نفسه
$prev_time = $first_point_time;
// نحدد تاريخ هذا الباتش (لنعرف أي يوم نحدث في الجدول اليومي)
$batch_date = date('Y-m-d', $first_point_time);
foreach ($points as $key => $point) {
if ($key === 0) continue; // تخطي النقطة الأولى لأننا قارناها مع الداتابيز
$current_time = strtotime($point['ts']);
$diff = $current_time - $prev_time;
// تجاهل القفزات الكبيرة (أكثر من 5 دقائق)
if ($diff > 0 && $diff < 300) {
$batch_added_seconds += $diff;
}
$prev_time = $current_time;
}
// ---------------------------------------------------------
// الجزء الثاني: إدخال التراكات (Bulk Insert) - كودك الأصلي
// ---------------------------------------------------------
$values = [];
$placeholders = [];
foreach ($points as $point) {
$lat = $point['lat'] ?? 0;
$lng = $point['lng'] ?? 0;
$spd = $point['spd'] ?? 0;
$head = $point['head'] ?? 0;
$dist = $point['dst'] ?? 0;
$stat = $point['st'] ?? 'off';
$time = $point['ts'] ?? date('Y-m-d H:i:s');
$placeholders[] = "(?, ?, ?, ?, ?, ?, ?, ?)";
array_push($values, $driver_id, $lat, $lng, $head, $spd, $dist, $stat, $time);
}
$sql = "INSERT INTO `car_tracks`
(`driver_id`, `latitude`, `longitude`, `heading`, `speed`, `distance`, `status`, `created_at`)
VALUES " . implode(', ', $placeholders);
$stmt = $con->prepare($sql);
$ok = $stmt->execute($values);
// ---------------------------------------------------------
// الجزء الثالث: تحديث جدول الملخص اليومي (The Smart Update)
// ---------------------------------------------------------
if ($ok && $batch_added_seconds > 0) {
// نستخدم ON DUPLICATE KEY UPDATE:
// إذا كان السائق موجوداً لهذا اليوم، أضف الثواني للرصيد الموجود
// إذا لم يكن موجوداً، أنشئ سجلاً جديداً
$sqlSummary = "INSERT INTO `driver_daily_summary` (`driver_id`, `date`, `total_seconds`)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE `total_seconds` = `total_seconds` + VALUES(`total_seconds`)";
$stmtSum = $con->prepare($sqlSummary);
$stmtSum->execute([$driver_id, $batch_date, $batch_added_seconds]);
}
if ($ok) {
echo json_encode(array(
"status" => "success",
"count" => count($points),
"added_seconds" => $batch_added_seconds // للتتبع فقط
));
} else {
printFailure("Failed to insert batch");
}
} catch (PDOException $e) {
error_log("Batch insert error: " . $e->getMessage());
printFailure("Database error");
} catch (Throwable $e) {
printFailure("Server error");
}
?>

View File

@@ -0,0 +1,34 @@
<?php
include "../../connect.php";
$passengerId = filterRequest("passengerId");
$lat = filterRequest("lat");
$lng = filterRequest("lng");
$rideId = filterRequest("rideId");
// Validate the latitude and longitude
if ($lat === '' || $lng === '') {
printFailure("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
printSuccess("Passenger location saved successfully");
} else {
// Print a failure message
printFailure("Failed to save passenger location");
}
?>

View File

@@ -0,0 +1,20 @@
<?
include "../../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
printSuccess($message = "Car location deleted successfully");
} else {
// Print a failure message
printFailure($message = "Failed to delete car location");
}
?>

View File

@@ -0,0 +1,146 @@
<?php
include "../../connect.php";
// Set timezone for PHP logs only (Keep DB on UTC)
date_default_timezone_set('Asia/Amman');
try {
// 1) Read and validate coordinates
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false ||
$northeastLat === false || $northeastLon === false) {
printFailure("Invalid coordinates provided");
exit;
}
// Fixed time window in seconds
$freshSeconds = 180; // 3 minutes
// =================================================================
// OPTIMIZATION: Create a Bounding Box Polygon in WKT format
// We create a geometric shape (a rectangle) that represents the map area.
// The SQL query will now find all points *inside* this shape.
// The format is POLYGON((lon lat, lon lat, ...))
// =================================================================
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
// =================================================================
// OPTIMIZATION: Modified SQL Query
// - We replaced the two `BETWEEN` clauses with a single, highly efficient
// `ST_CONTAINS` function.
// - `ST_GeomFromText` converts our text polygon into a geometry object.
// - `ST_CONTAINS` uses the SPATIAL INDEX to rapidly find all `location_point`s
// that are within our bounding box.
// =================================================================
$sql = "
SELECT
NOW() AS serverNow,
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 AS make,
cr.car_plate AS car_plate,
cr.model AS model,
cr.color AS color,
cr.vin AS vin,
cr.color_hex AS color_hex,
cr.year AS year,
dt.token AS token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver
FROM car_locations cl
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
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = cl.driver_id
WHERE
-- This is the optimized spatial condition
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), cl.location_point)
AND cl.status = 'off'
AND cl.updated_at >= NOW() - INTERVAL :freshSeconds SECOND
AND COALESCE(cr.year, 0) > 2000
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
ORDER BY cl.updated_at DESC, ratingDriver DESC
LIMIT 5
";
$stmt = $con->prepare($sql);
// Bind the new bounding box parameter
$stmt->bindValue(':boundingBox', $boundingBoxWKT);
$stmt->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$rows) {
printFailure("No car locations found");
exit;
}
// Decryption and age calculation logic remains the same
$fieldsToDecrypt = [
'phone','email','gender','birthdate',
'first_name','last_name','maritalStatus',
'token','make','car_plate','vin'
];
foreach ($rows as &$row) {
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;
}
if (isset($row['serverNow'], $row['updated_at'])) {
error_log('PHP Now: '.date('Y-m-d H:i:s')
.' | MySQL Now: '.$row['serverNow']
.' | updated_at: '.$row['updated_at']);
}
}
unset($row);
printSuccess($rows);
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
} catch (Throwable $e) {
printFailure("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,120 @@
<?php
include "../../connect.php";
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false ||
$northeastLat === false || $northeastLon === false) {
printFailure("Invalid coordinates provided");
exit;
}
// نافذة زمنية ثابتة (بدون توسيع)
$freshSeconds = 180; // 3 دقائق
$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(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS 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 :freshSeconds SECOND
AND COALESCE(cr.year, 0) < 2000
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
ORDER BY
ratingDriver DESC, -- ⭐ الأولوية للتقييم
ratingCount DESC, -- ثم الأكثر حصولاً على تقييمات
cl.updated_at DESC -- ثم الأحدث تحديثًا كفاصل
LIMIT 5
";
$stmt = $con->prepare($sql);
$stmt->bindValue(':southwestLat', $southwestLat);
$stmt->bindValue(':southwestLon', $southwestLon);
$stmt->bindValue(':northeastLat', $northeastLat);
$stmt->bindValue(':northeastLon', $northeastLon);
$stmt->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$rows) {
printFailure("No car locations found");
exit;
}
// تفكيك التشفير + حساب العمر
$fieldsToDecrypt = [
'phone','email','gender','birthdate',
'first_name','last_name','maritalStatus',
'token','make','car_plate','vin'
];
foreach ($rows as &$row) {
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;
}
}
unset($row);
printSuccess($rows);
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
} catch (Throwable $e) {
printFailure("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,124 @@
<?php
include "../../connect.php";
try {
// ✅ قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false ||
$northeastLat === false || $northeastLon === false) {
printFailure("Invalid coordinates provided");
exit;
}
// ⏱️ نافذة زمنية ثابتة بالثواني (بدّلها عند الحاجة)
$freshSeconds = 180; // 3 دقائق
// ✅ الاستعلام الآمن والمهيأ
$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(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS 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(*) 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 :freshSeconds SECOND
AND COALESCE(cr.year, 0) > 2017
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
ORDER BY
ratingDriver DESC, -- ⭐ الأولوية للأعلى تقييماً
ratingCount DESC, -- ثم الأكثر حصولاً على تقييمات
cl.updated_at DESC -- ثم الأحدث تحديثاً كفاصل
LIMIT 5
";
$stmt = $con->prepare($sql);
$stmt->bindValue(':southwestLat', $southwestLat);
$stmt->bindValue(':southwestLon', $southwestLon);
$stmt->bindValue(':northeastLat', $northeastLat);
$stmt->bindValue(':northeastLon', $northeastLon);
$stmt->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
// ✅ فك التشفير (مع حماية الأخطاء)
$fieldsToDecrypt = [
'phone','email','gender','birthdate',
'first_name','last_name','maritalStatus',
'token','make','car_plate','vin'
];
foreach ($car_locations as &$row) {
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;
}
}
unset($row);
printSuccess($car_locations);
} else {
printFailure("No car locations found");
}
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
} catch (Throwable $e) {
printFailure("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,125 @@
<?php
include "../../connect.php";
try {
// ✅ قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false ||
$northeastLat === false || $northeastLon === false) {
printFailure("Invalid coordinates provided");
exit;
}
// ⏱️ نافذة زمنية ثابتة (ثواني)
$freshSeconds = 180; // 3 دقائق — عدّلها إذا لزم
// ✅ الاستعلام الآمن والمهيأ
$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(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS 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(*) 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 :freshSeconds SECOND
-- ⛓️ اختيار فقط الدراجات
AND (cr.make LIKE '%دراج%' OR cr.model LIKE '%دراج%')
ORDER BY
ratingDriver DESC, -- ⭐ أولاً الأعلى تقييماً
ratingCount DESC, -- ثم الأكثر حصولاً على تقييمات
cl.updated_at DESC -- ثم الأحدث تحديثاً كفاصل
LIMIT 10
";
$stmt = $con->prepare($sql);
$stmt->bindValue(':southwestLat', $southwestLat);
$stmt->bindValue(':southwestLon', $southwestLon);
$stmt->bindValue(':northeastLat', $northeastLat);
$stmt->bindValue(':northeastLon', $northeastLon);
$stmt->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$car_locations) {
printFailure("No car locations found");
exit;
}
// ✅ فك التشفير (مع حماية الأخطاء)
$fieldsToDecrypt = [
'phone','email','gender','birthdate',
'first_name','last_name','maritalStatus',
'token','make','car_plate','vin'
];
foreach ($car_locations as &$row) {
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;
}
}
unset($row);
printSuccess($car_locations);
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
} catch (Throwable $e) {
printFailure("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* Real-time Driver Location Tracker (Last 10 Days - Full Records)
*/
include_once("../../jwtconnect.php");
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
// API Key
$expected_api_key = getenv('LOCATION_SERVER_API_KEY');
function get_api_key()
{
$headers = getallheaders();
if (isset($headers['x-api-key']))
return $headers['x-api-key'];
if (isset($headers['X-API-KEY']))
return $headers['X-API-KEY'];
if (isset($_SERVER['HTTP_X_API_KEY']))
return $_SERVER['HTTP_X_API_KEY'];
return '';
}
$provided_api_key = get_api_key();
if ($provided_api_key !== $expected_api_key) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized access']);
exit;
}
// 3. API Input (Optional days parameter, default 1)
$days = isset($_GET['days']) ? (int)$_GET['days'] : 1;
//if ($days < 1 || $days > 30) $days = 1;
if ($days < 1 || $days > 10) $days = 1;
// SQL
$sql = "
SELECT *
FROM car_tracks
WHERE created_at >= NOW() - INTERVAL $days DAY
ORDER BY created_at DESC
";
try {
$stmt = $con->prepare($sql);
$stmt->execute();
// جلب كل النتائج دفعة واحدة
$records = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'total_records' => count($records),
'from' => date('Y-m-d H:i:s', strtotime("-$days days")),
'to' => date('Y-m-d H:i:s'),
'data' => $records
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
catch (PDOException $e) {
http_response_code(500);
echo json_encode([
'error' => 'Database error',
'details' => $e->getMessage()
]);
}

View File

@@ -0,0 +1,43 @@
<?php
include "../../connect.php";
$driver_id = filterRequest("driver_id");
$sql = "SELECT
cl.driver_id,
cl.latitude,
cl.longitude,
cl.heading,
cl.speed,
cl.status,
cl.created_at,
cl.updated_at,
d.gender,
cr.model
FROM
car_locations cl
LEFT JOIN driver d ON
cl.driver_id = d.id
LEFT JOIN CarRegistration cr ON
cl.driver_id = cr.driverID
WHERE
cl.driver_id = '$driver_id'
ORDER BY
created_at
DESC
LIMIT 1;";
$stmt = $con->prepare($sql);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
// Print the car location data as JSON
printSuccess($data = $car_locations);
} else {
// Print a failure message
printFailure($message = "No car locations found");
}
?>

View File

@@ -0,0 +1,126 @@
<?php
include "../../connect.php";
try {
// ✅ قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false ||
$northeastLat === false || $northeastLon === false) {
printFailure("Invalid coordinates provided");
exit;
}
// ⏱️ نافذة زمنية ثابتة
$freshSeconds = 180; // 3 دقائق
// ✅ الاستعلام
$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,
cr.fuel,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS 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(*) 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 :freshSeconds SECOND
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
AND cr.fuel = 'كهربائي'
ORDER BY
ratingDriver DESC, -- ⭐ الأعلى تقييماً
ratingCount DESC, -- ثم الأكثر حصولاً على تقييمات
cl.updated_at DESC -- ثم الأحدث تحديثاً
LIMIT 10
";
$stmt = $con->prepare($sql);
$stmt->bindValue(':southwestLat', $southwestLat);
$stmt->bindValue(':southwestLon', $southwestLon);
$stmt->bindValue(':northeastLat', $northeastLat);
$stmt->bindValue(':northeastLon', $northeastLon);
$stmt->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!$car_locations) {
printFailure("No electric car locations found");
exit;
}
// ✅ فك التشفير + حساب العمر
$fieldsToDecrypt = [
'phone','email','gender','birthdate',
'first_name','last_name','maritalStatus',
'token','make','car_plate','vin'
];
foreach ($car_locations as &$row) {
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;
}
}
unset($row);
printSuccess($car_locations);
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
} catch (Throwable $e) {
printFailure("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,111 @@
<?php
include "../../connect.php";
try {
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false ||
$northeastLat === false || $northeastLon === false) {
printFailure("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,
COALESCE(AVG(rd.rating), 0) AS ratingDriver,
COUNT(rd.id) AS ratingCount,
'' AS age
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 BETWEEN :southwestLat AND :northeastLat
AND cl.longitude BETWEEN :southwestLon AND :northeastLon
AND cl.status = 'off'
AND cl.updated_at >= NOW() - INTERVAL 180 SECOND
AND (cr.make NOT LIKE '%دراجة%' AND cr.model NOT LIKE '%دراجة%')
AND d.gender = 'Female'
GROUP BY cl.driver_id
ORDER BY ratingDriver DESC, ratingCount 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','token',
'make','car_plate','vin','maritalStatus'
];
foreach ($rows as &$row) {
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;
}
}
unset($row);
printSuccess($rows);
} else {
printFailure("No car locations found");
}
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
} catch (Throwable $e) {
printFailure("Internal error: " . $e->getMessage());
}

View File

@@ -0,0 +1,29 @@
<?php
include "../../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();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
// Print the car location data as JSON
printSuccess($data = $car_locations);
} else {
// Print a failure message
printFailure($message = "No car locations found");
}
?>

View File

@@ -0,0 +1,42 @@
<?php
include "../../connect.php";
$driver_id = filterRequest("driver_id");
$sql = "SELECT
car_locations.id,
car_locations.driver_id,
car_locations.latitude,
car_locations.longitude,
car_locations.heading,
car_locations.speed,
car_locations.`status`,
car_locations.created_at,
car_locations.updated_at,
`driver`.`gender`,
CarRegistration.model
FROM
car_locations
LEFT JOIN `driver` ON `driver`.`id` = car_locations.driver_id
LEFT JOIN `CarRegistration`
ON `CarRegistration`.`driverID` = CarRegistration.driverID
WHERE
driver_id = '$driver_id'
ORDER BY
created_at
DESC
LIMIT 1;";
$stmt = $con->prepare($sql);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
// Print the car location data as JSON
printSuccess($data = $car_locations);
} else {
// Print a failure message
printFailure($message = "No car locations found");
}
?>

View File

@@ -0,0 +1,112 @@
<?php
include "../../connect.php";
try {
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
printFailure("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;
}
printSuccess($filteredRows);
} else {
printFailure("No car locations found");
}
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
}

View File

@@ -0,0 +1,66 @@
<?php
include "../../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
printSuccess($data = $car_locations);
} else {
// Print a failure message
printFailure($message = "No car locations found");
}
?>

View File

@@ -0,0 +1,119 @@
<?php
include "../../connect.php";
// Get and filter input
$southwestLat = filterRequest("southwestLat");
$southwestLon = filterRequest("southwestLon");
$northeastLat = filterRequest("northeastLat");
$northeastLon = filterRequest("northeastLon");
// Validate input
if (is_null($southwestLat) || is_null($southwestLon) || is_null($northeastLat) || is_null($northeastLon)) {
echo json_encode(['status' => 'failure', 'message' => 'Missing required parameters']);
exit;
}
try {
// نافذة زمنية مناسبة (3 دقائق)
$freshSeconds = 180;
$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(rd.ratingDriver, 0) AS ratingDriver,
COALESCE(rd.ratingCount, 0) AS 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
) rd ON rd.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 :freshSeconds SECOND
AND COALESCE(cr.year, 0) > 2000
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
ORDER BY
ratingDriver DESC,
ratingCount 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->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt->execute();
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($car_locations) {
$fieldsToDecrypt = [
'phone','email','gender','birthdate',
'first_name','last_name','token',
'make','car_plate','vin','maritalStatus'
];
foreach ($car_locations as &$row) {
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']);
$now = new DateTime();
$row['age'] = $now->diff($birthdate)->y;
} catch (Exception $e) {
$row['age'] = null;
}
} else {
$row['age'] = null;
}
}
unset($row);
printSuccess($car_locations);
} else {
printFailure("No car locations found");
}
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
}

View File

@@ -0,0 +1,44 @@
<?php
include "../../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
printSuccess($data = $car_locations);
} else {
// Print a failure message
printFailure($message = "No car locations found");
}
?>

View File

@@ -0,0 +1,70 @@
<?php
// 1. إعدادات التصحيح (Debug) - ضرورية جداً الآن
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, Access-Control-Allow-Origin");
header("Access-Control-Allow-Methods: POST, OPTIONS , GET");
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// 2. تضمين ملف الاتصال
if (file_exists("../../connect.php")) {
include "../../connect.php";
} else {
// في حال عدم وجود الملف، ننهي التنفيذ ونطبع السبب
die(json_encode(array("status" => "failure", "message" => "Connect file not found")));
}
// 3. التقاط البيانات بذكاء (لحل مشكلة Flutter JSON)
// هذا الجزء يفحص: هل البيانات في POST؟ أم في JSON Body؟
$driver_id = null;
if (isset($_POST['driver_id'])) {
$driver_id = filterRequest("driver_id");
} else {
// محاولة قراءة JSON Body (لأن فلاتر يرسل البيانات هكذا غالباً)
$jsonInput = json_decode(file_get_contents("php://input"), true);
if (isset($jsonInput['driver_id'])) {
$driver_id = htmlspecialchars(strip_tags($jsonInput['driver_id']));
}
}
// التحقق النهائي
if (!$driver_id) {
// طباعة الخطأ بوضوح
echo json_encode(array("status" => "failure", "message" => "driver_id is missing or empty"));
exit;
}
// 4. التنفيذ
try {
$date = date('Y-m-d');
$sql = "SELECT total_seconds FROM driver_daily_summary
WHERE driver_id = ? AND date = ?";
$stmt = $con->prepare($sql);
$stmt->execute([$driver_id, $date]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
$duration = "00:00:00";
if ($data) {
$seconds = $data['total_seconds'];
$duration = gmdate("H:i:s", $seconds);
}
// 5. بناء الاستجابة يدوياً لتطابق كود Flutter 100%
// الهيكل المطلوب: data['message'][0]['total_duration']
$response = array(
"status" => "success",
"message" => array(
array("total_duration" => $duration)
)
);
echo json_encode($response);
} catch (PDOException $e) {
echo json_encode(array("status" => "failure", "message" => "DB Error: " . $e->getMessage()));
}
?>

View File

@@ -0,0 +1,24 @@
<?php
include "../../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) {
printSuccess($car_locations);
} else {
printFailure("No car locations found");
}
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
}

View File

@@ -0,0 +1,86 @@
<?php
include "../../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) {
printSuccess($car_locations);
} else {
printFailure("No car locations found");
}
} catch (PDOException $e) {
printFailure("Database error: " . $e->getMessage());
}
?>

View File

@@ -0,0 +1,59 @@
<?php
include "../../connect.php";
// استلام البيانات من 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)) {
// Log the validation error
error_log("Driver Behavior Error: Missing driver_id ($driver_id) or trip_id ($trip_id)");
printFailure("Missing driver_id or trip_id");
exit();
}
try {
// إدخال البيانات في جدول driver_behavior
$stmt = $con->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) {
printSuccess("Behavior data saved");
} else {
// Log that the query ran but no rows were inserted
error_log("Driver Behavior Warning: Insert executed but 0 rows affected for driver $driver_id");
printFailure("Failed to save data");
}
} catch (PDOException $e) {
// --- THIS IS THE TYPE ERROR LOG YOU ASKED FOR ---
// This records the exact SQL error (e.g., duplicate entry, foreign key fail) to your server log
error_log("Driver Behavior SQL Error: " . $e->getMessage());
// Return a generic error to the app (or $e->getMessage() if debugging)
printFailure("Database Error");
}
exit();
?>

View File

@@ -0,0 +1,65 @@
<?php
include "../../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");
// 1. قمنا بحذف السطر التالي لأنه مصدر المشكلة
// $updated_at = date("Y-m-d H:i:s");
// Basic validation
if (!$driver_id || !$latitude || !$longitude || $status === null) {
http_response_code(400);
printFailure('Missing required fields');
exit;
}
// Secure SQL using prepared statement
// 2. لاحظ التغيير داخل جملة SQL
// بدلنا :updated_at بكلمة NOW() وهي دالة في قاعدة البيانات
$sql = "INSERT INTO `car_locations` (
`driver_id`, `latitude`, `longitude`, `heading`, `speed`, `distance`, `status`, `updated_at`
) VALUES (
:driver_id, :latitude, :longitude, :heading, :speed, :distance, :status, NOW()
)
ON DUPLICATE KEY UPDATE
`latitude` = VALUES(`latitude`),
`longitude` = VALUES(`longitude`),
`heading` = VALUES(`heading`),
`speed` = VALUES(`speed`),
`distance` = VALUES(`distance`),
`status` = VALUES(`status`),
`updated_at` = NOW()"; // وهنا أيضاً جعلنا التحديث يأخذ وقت السيرفر مباشرة
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
]);
if ($success) {
printSuccess("Car location updated successfully");
} else {
printFailure("Failed to update car location");
}
} catch (PDOException $e) {
http_response_code(500);
printFailure('Database error occurred');
}
?>

View File

@@ -0,0 +1,48 @@
<?php
include "../../connect.php";
// استقبال البيانات من تطبيق السائق
$driver_id = filterRequest("driver_id");
$lat = filterRequest("lat");
$lng = filterRequest("lng");
$heading = filterRequest("heading"); // اتجاه السيارة
$speed = filterRequest("speed");
$status = filterRequest("status"); // 'on' (متاح) أو 'off' (مشغول/غير متاح)
if (!$driver_id || !$lat || !$lng) {
printFailure("Missing Data");
exit;
}
try {
// استخدام ON DUPLICATE KEY UPDATE لضمان وجود صف واحد فقط لكل سائق
// الجدول: car_locations
$sql = "INSERT INTO car_locations (driver_id, latitude, longitude, heading, speed, status, updated_at)
VALUES (:id, :lat, :lng, :head, :spd, :stat, NOW())
ON DUPLICATE KEY UPDATE
latitude = :lat,
longitude = :lng,
heading = :head,
speed = :spd,
status = :stat,
updated_at = NOW()";
$stmt = $con_tracking->prepare($sql);
$stmt->execute([
':id' => $driver_id,
':lat' => $lat,
':lng' => $lng,
':head' => $heading,
':spd' => $speed,
':stat' => $status
]);
// ملاحظة: لا نحتاج لإرسال socket notification هنا لأن هذا يحدث كل ثانية
// الراكب يرى التحديث لأنه متصل بسوكيت اللوكيشن ويستمع لحدث 'update_driver_location'
printSuccess("Location Updated");
} catch (PDOException $e) {
printFailure("DB Error: " . $e->getMessage());
}
?>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
<?php
// =================================================================
// ملف: update_locations.php
// الوظيفة: إنشاء ملفات JSON للوضع المباشر واليومي
// =================================================================
// السماح بالوصول من أي مكان (CORS)
header("Access-Control-Allow-Origin: *");
header('Content-Type: text/html; charset=utf-8');
error_reporting(E_ALL);
ini_set('display_errors', 1);
include "../jwtconnect.php";
// 1. تحديد الوضع (Mode)
$mode = isset($_GET['mode']) && $_GET['mode'] == 'day' ? 'day' : 'live';
// 2. إعداد المتغيرات حسب الوضع
if ($mode == 'day') {
$fileName = 'locations_day.json';
// شرط: أي تحديث حدث اليوم
$condition = "DATE(updated_at) = CURDATE()";
$title = "سجل اليوم الكامل";
} else {
$fileName = 'locations_live.json';
// شرط: آخر 20 دقيقة فقط
$condition = "updated_at >= NOW() - INTERVAL 20 MINUTE";
$title = "مباشر (آخر 20 دقيقة)";
}
$savePath = __DIR__ . '/' . $fileName;
// دالة تنظيف النصوص العربية وإصلاح الترميز
function utf8ize($d) {
if (is_array($d)) {
foreach ($d as $k => $v) { $d[$k] = utf8ize($v); }
} else if (is_string ($d)) {
return mb_convert_encoding($d, 'UTF-8', 'UTF-8');
}
return $d;
}
try {
// إجبار قاعدة البيانات على ترميز UTF-8
if(isset($con)) { $con->exec("set names utf8mb4"); }
// تنفيذ الاستعلام
$sql = "SELECT * FROM `car_locations` WHERE $condition ORDER BY updated_at DESC";
$stmt = $con->prepare($sql);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// تنظيف البيانات
$cleanRows = utf8ize($rows);
// تجهيز البيانات للحفظ
$outputData = [
'mode' => $mode,
'title' => $title,
'last_updated' => date('Y-m-d H:i:s'),
'count' => count($cleanRows),
'drivers' => $cleanRows
];
// تحويل إلى JSON
$jsonContent = json_encode($outputData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
if ($jsonContent === false) {
die("خطأ في تكوين JSON: " . json_last_error_msg());
}
// حفظ الملف
if (file_put_contents($savePath, $jsonContent) !== false) {
echo "<div style='font-family:tahoma; direction:rtl; text-align:right; padding:20px; background:#e8f5e9; border:1px solid #4caf50; border-radius:5px;'>";
echo "<h3 style='color:green; margin:0;'>✅ تم التحديث بنجاح!</h3>";
echo "<p><b>الوضع:</b> $title</p>";
echo "<p><b>اسم الملف:</b> $fileName</p>";
echo "<p><b>عدد السائقين:</b> " . count($cleanRows) . "</p>";
echo "</div>";
} else {
die("فشل الكتابة في الملف. تأكد من صلاحيات المجلد.");
}
} catch (PDOException $e) {
die("خطأ قاعدة بيانات: " . $e->getMessage());
}
?>

View File

@@ -0,0 +1,87 @@
<?php
//test_order.php
// إعدادات الاتصال
$socketUrl = 'http://127.0.0.1:2021';
$INTERNAL_KEY = trim(file_get_contents('/home/location/.internal_socket_key'));
// 🔴 هام: ضع آيدي السائق الفعلي هنا
$targetDriverId = '34feffd3fa72d6bee56b';
// إحداثيات وهمية (عمان)
$pickupLat = 32.07374322273893;
$pickupLng = 36.09692047770945;
$dropLat = 31.963158; // العبدلي
$dropLng = 35.930359;
// =================================================================================
// بناء المصفوفة (Payload) بنفس ترتيب الفهارس (Indexes) في تطبيق Flutter
// =================================================================================
$fakeOrderList = [];
$fakeOrderList[0] = (string)$pickupLat; // myList[0]: Passenger Lat
$fakeOrderList[1] = (string)$pickupLng; // myList[1]: Passenger Lng
$fakeOrderList[2] = "53.50"; // myList[2]: Payment Amount (Total)
$fakeOrderList[3] = (string)$dropLat; // myList[3]: Destination Lat
$fakeOrderList[4] = (string)$dropLng; // myList[4]: Destination Lng (Also used as Price in some views, but mostly coords)
$fakeOrderList[5] = "8.9 km"; // myList[5]: Distance Text
$fakeOrderList[6] = $targetDriverId; // myList[6]: Driver ID
$fakeOrderList[7] = "55"; // myList[7]: Passenger ID
$fakeOrderList[8] = "Hamza Passenger"; // myList[8]: Passenger Name
$fakeOrderList[9] = "PASSENGER_FCM_TOKEN_XYZ"; // myList[9]: Passenger Token
$fakeOrderList[10] = "0791234567"; // myList[10]: Passenger Phone
$fakeOrderList[11] = "8800"; // myList[11]: Distance in Meters (used for calc)
$fakeOrderList[12] = "500"; // myList[12]: Duration in Seconds (used for calc)
$fakeOrderList[13] = "false"; // myList[13]: Payment Method ('true'=Visa, 'false'=Cash)
$fakeOrderList[14] = "8500"; // myList[14]: Distance (Integer/String for View)
$fakeOrderList[15] = "5 min"; // myList[15]: Duration to Passenger
$fakeOrderList[16] = "9999"; // myList[16]: Ride ID (Order ID)
$fakeOrderList[17] = ""; // myList[17]: (Empty/Unused)
$fakeOrderList[18] = $targetDriverId; // myList[18]: Driver ID (Repeated)
$fakeOrderList[19] = "18 min"; // myList[19]: Ride Duration Text
$fakeOrderList[20] = "false"; // myList[20]: Is Have Steps?
$fakeOrderList[21] = ""; // myList[21]: Step 0
$fakeOrderList[22] = ""; // myList[22]: Step 1
$fakeOrderList[23] = ""; // myList[23]: Step 2
$fakeOrderList[24] = ""; // myList[24]: Step 3
$fakeOrderList[25] = ""; // myList[25]: Step 4
$fakeOrderList[26] = "3.50"; // myList[26]: Wallet/Total Cost
$fakeOrderList[27] = ""; // myList[27]: (Empty)
$fakeOrderList[28] = "client@email.com"; // myList[28]: Email
$fakeOrderList[29] = "الجامعة الأردنية - البوابة الرئيسية"; // myList[29]: Pickup Address Name
$fakeOrderList[30] = "العبدلي مول - البوليفارد"; // myList[30]: Dropoff Address Name
$fakeOrderList[31] = "speed"; // myList[31]: Car Type
$fakeOrderList[32] = "2.75"; // myList[32]: Kazan (Earnings)
$fakeOrderList[33] = "4.8"; // myList[33]: Rating
// تحويل المصفوفة إلى قائمة مرتبة (Indexed Array) لضمان وصولها كـ List في فلاتر
// ksort يضمن الترتيب، و array_values يعيد فهرسة المفاتيح لتبدأ من 0
ksort($fakeOrderList);
$finalPayload = array_values($fakeOrderList);
// تجهيز البيانات للإرسال
$postData = [
'action' => 'dispatch_order',
'drivers_ids' => json_encode([$targetDriverId]),
'payload' => $finalPayload // 🔥 هنا نرسل المصفوفة وليس كائناً
];
// إرسال الطلب
$ch = curl_init($socketUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-internal-key: $INTERNAL_KEY"
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Response Code: $httpCode\n";
if ($response == 'Dispatched') {
echo "✅ Success! Order List sent to driver.\n";
} else {
echo "❌ Failed: $response\n";
}
?>

View File

@@ -0,0 +1,43 @@
<?php
// test_ride_taken.php
// إعدادات الاتصال
$socketUrl = 'http://127.0.0.1:2021';
$INTERNAL_KEY = trim(file_get_contents('/home/location/.internal_socket_key'));
// بيانات المحاكاة
// يجب أن يكون نفس رقم الطلب الذي أرسلته سابقاً
$rideId = '9999';
// آيدي سائق "وهمي" (غير آيديك الحقيقي)
// لكي يفهم تطبيقك أن شخصاً آخر أخذ الطلب
$fakeDriverId = 'DRIVER_XYZ_987654321';
$postData = [
'action' => 'simulate_ride_taken',
'ride_id' => $rideId,
'taken_by_driver_id' => $fakeDriverId
];
// إرسال الطلب عبر cURL
$ch = curl_init($socketUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-internal-key: $internalKey"
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Simulating Ride Taken...\n";
echo "Response: $response\n";
if ($response == 'Ride Taken Event Broadcasted') {
echo "✅ Success! All drivers should see 'Ride Taken' now.\n";
} else {
echo "❌ Failed.\n";
}
?>