Update: 2026-06-29 23:09:43
This commit is contained in:
81
loction_server/api_get_nearby.php
Executable file
81
loction_server/api_get_nearby.php
Executable 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()]);
|
||||
}
|
||||
?>
|
||||
7
loction_server/composer.json
Normal file
7
loction_server/composer.json
Normal 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
601
loction_server/driver_socket.php
Executable 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();
|
||||
83
loction_server/find_drivers_redis.php
Executable file
83
loction_server/find_drivers_redis.php
Executable 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
3
loction_server/index.php
Executable file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
echo 'Hello World :-)';
|
||||
325
loction_server/locationDB.sql
Normal file
325
loction_server/locationDB.sql
Normal 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
64
loction_server/siro/connect.php
Executable 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
468
loction_server/siro/functions.php
Executable 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);
|
||||
}
|
||||
64
loction_server/siro/jwtconnect.php
Executable file
64
loction_server/siro/jwtconnect.php
Executable 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;
|
||||
}
|
||||
?>
|
||||
23
loction_server/siro/load_env.php
Executable file
23
loction_server/siro/load_env.php
Executable 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;
|
||||
}
|
||||
190
loction_server/siro/ride/driversLocation.html
Executable file
190
loction_server/siro/ride/driversLocation.html
Executable 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>
|
||||
160
loction_server/siro/ride/heatmap.html
Executable file
160
loction_server/siro/ride/heatmap.html
Executable 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: '© <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>
|
||||
80
loction_server/siro/ride/location/add.php
Executable file
80
loction_server/siro/ride/location/add.php
Executable 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");
|
||||
}
|
||||
?>
|
||||
129
loction_server/siro/ride/location/add_batch.php
Executable file
129
loction_server/siro/ride/location/add_batch.php
Executable 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");
|
||||
}
|
||||
?>
|
||||
34
loction_server/siro/ride/location/addpassengerLocation.php
Executable file
34
loction_server/siro/ride/location/addpassengerLocation.php
Executable 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");
|
||||
}
|
||||
?>
|
||||
20
loction_server/siro/ride/location/delete.php
Normal file
20
loction_server/siro/ride/location/delete.php
Normal 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");
|
||||
}
|
||||
|
||||
?>
|
||||
146
loction_server/siro/ride/location/get.php
Executable file
146
loction_server/siro/ride/location/get.php
Executable 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());
|
||||
}
|
||||
120
loction_server/siro/ride/location/getBalash.php
Executable file
120
loction_server/siro/ride/location/getBalash.php
Executable 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());
|
||||
}
|
||||
124
loction_server/siro/ride/location/getComfort.php
Executable file
124
loction_server/siro/ride/location/getComfort.php
Executable 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());
|
||||
}
|
||||
125
loction_server/siro/ride/location/getDelivery.php
Executable file
125
loction_server/siro/ride/location/getDelivery.php
Executable 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());
|
||||
}
|
||||
70
loction_server/siro/ride/location/getDrirversLocationsTrack.php
Executable file
70
loction_server/siro/ride/location/getDrirversLocationsTrack.php
Executable 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()
|
||||
]);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
?>
|
||||
126
loction_server/siro/ride/location/getElectric.php
Executable file
126
loction_server/siro/ride/location/getElectric.php
Executable 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());
|
||||
}
|
||||
111
loction_server/siro/ride/location/getFemalDriver.php
Executable file
111
loction_server/siro/ride/location/getFemalDriver.php
Executable 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());
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
?>
|
||||
42
loction_server/siro/ride/location/getLocationParents.php
Normal file
42
loction_server/siro/ride/location/getLocationParents.php
Normal 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");
|
||||
}
|
||||
|
||||
?>
|
||||
112
loction_server/siro/ride/location/getPinkBike.php
Executable file
112
loction_server/siro/ride/location/getPinkBike.php
Executable 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());
|
||||
}
|
||||
66
loction_server/siro/ride/location/getRidesDriverByDay.php
Executable file
66
loction_server/siro/ride/location/getRidesDriverByDay.php
Executable 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");
|
||||
}
|
||||
?>
|
||||
119
loction_server/siro/ride/location/getSpeed.php
Executable file
119
loction_server/siro/ride/location/getSpeed.php
Executable 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());
|
||||
}
|
||||
44
loction_server/siro/ride/location/getTotalDriverDuration.php
Executable file
44
loction_server/siro/ride/location/getTotalDriverDuration.php
Executable 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");
|
||||
}
|
||||
|
||||
?>
|
||||
70
loction_server/siro/ride/location/getTotalDriverDurationToday.php
Executable file
70
loction_server/siro/ride/location/getTotalDriverDurationToday.php
Executable 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()));
|
||||
}
|
||||
?>
|
||||
@@ -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());
|
||||
}
|
||||
86
loction_server/siro/ride/location/getfemalbehavior.php
Normal file
86
loction_server/siro/ride/location/getfemalbehavior.php
Normal 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());
|
||||
}
|
||||
?>
|
||||
59
loction_server/siro/ride/location/save_behavior.php
Normal file
59
loction_server/siro/ride/location/save_behavior.php
Normal 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();
|
||||
?>
|
||||
65
loction_server/siro/ride/location/update.php
Normal file
65
loction_server/siro/ride/location/update.php
Normal 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');
|
||||
}
|
||||
?>
|
||||
48
loction_server/siro/ride/location/update_location.php
Executable file
48
loction_server/siro/ride/location/update_location.php
Executable 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());
|
||||
}
|
||||
?>
|
||||
1
loction_server/siro/ride/locations_data.json
Executable file
1
loction_server/siro/ride/locations_data.json
Executable file
File diff suppressed because one or more lines are too long
2998
loction_server/siro/ride/locations_day.json
Normal file
2998
loction_server/siro/ride/locations_day.json
Normal file
File diff suppressed because it is too large
Load Diff
1074
loction_server/siro/ride/locations_live.json
Normal file
1074
loction_server/siro/ride/locations_live.json
Normal file
File diff suppressed because it is too large
Load Diff
88
loction_server/siro/ride/update_locations_web_app.php
Executable file
88
loction_server/siro/ride/update_locations_web_app.php
Executable 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());
|
||||
}
|
||||
?>
|
||||
87
loction_server/test_order.php
Normal file
87
loction_server/test_order.php
Normal 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";
|
||||
}
|
||||
?>
|
||||
43
loction_server/test_ride_taken.php
Executable file
43
loction_server/test_ride_taken.php
Executable 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";
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user