Update codebase
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// safety/incident.php — تسجيل بلاغ سلامة
|
||||
//
|
||||
// كان زر SOS ينتهي عند رسالة واتساب لجهة اتصال شخصية. الشركة لا تعلم،
|
||||
// ولا تملك دليلاً، ولا تستطيع التدخل. هذه النقطة هي الأثر المفقود.
|
||||
//
|
||||
// رسالة الواتساب تبقى كما هي: التطبيق يرسلها **بالتوازي** لا بدلاً —
|
||||
// هي الأسرع وصولاً لمن يهمّه أمر الراكب فعلاً، وهذه النقطة للشركة.
|
||||
//
|
||||
// ثلاثة أشياء تحدث هنا، بهذا الترتيب عمداً:
|
||||
// ١. تسجيل البلاغ ولقطة الحالة ← الأهم، ولا يعتمد على شيء
|
||||
// ٢. تجميد إسناد الرحلات للسائق ← يمنع تكرار الواقعة فوراً
|
||||
// ٣. تنبيه الإدارة ← أفضل جهد، لا يُسقط ما قبله
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
$reporterType = filterRequest("reporter_type") ?: 'passenger';
|
||||
$reporterId = filterRequest("reporter_id");
|
||||
$rideId = filterRequest("ride_id");
|
||||
$triggerType = filterRequest("trigger_type") ?: 'sos_button';
|
||||
$note = filterRequest("note");
|
||||
$lat = filterRequest("lat");
|
||||
$lng = filterRequest("lng");
|
||||
|
||||
if (!$reporterId) {
|
||||
jsonError("Missing reporter_id");
|
||||
}
|
||||
|
||||
// قائمة بيضاء: القيم تُخزَّن وتُعرض في لوحة الإدارة، ولا نريد نصاً حراً.
|
||||
$allowedReporters = ['passenger', 'driver'];
|
||||
$allowedTriggers = ['sos_button', 'shake', 'route_deviation', 'long_stop', 'overtime'];
|
||||
if (!in_array($reporterType, $allowedReporters, true)) $reporterType = 'passenger';
|
||||
if (!in_array($triggerType, $allowedTriggers, true)) $triggerType = 'sos_button';
|
||||
|
||||
$driverId = null;
|
||||
$passengerId = null;
|
||||
$snapshot = ['reported_at' => date('c'), 'trigger' => $triggerType];
|
||||
|
||||
try {
|
||||
// ── ١. لقطة الحالة ──────────────────────────────────────
|
||||
// تُلتقط الآن لأن الرحلة تتغيّر بعد الحادثة (تُلغى، يُصفَّر السائق)،
|
||||
// فقراءتها لاحقاً لا تصف اللحظة التي وقع فيها البلاغ.
|
||||
if ($rideId) {
|
||||
$stmt = $con->prepare("SELECT * FROM ride WHERE id = ? LIMIT 1");
|
||||
$stmt->execute([$rideId]);
|
||||
$ride = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($ride) {
|
||||
$driverId = ($ride['driver_id'] ?? '0') !== '0' ? $ride['driver_id'] : null;
|
||||
$passengerId = $ride['passenger_id'] ?? null;
|
||||
|
||||
$snapshot['ride'] = [
|
||||
'status' => $ride['status'] ?? null,
|
||||
'start_location' => $ride['start_location'] ?? null,
|
||||
'end_location' => $ride['end_location'] ?? null,
|
||||
'carType' => $ride['carType'] ?? $ride['car_type'] ?? null,
|
||||
'price' => $ride['price'] ?? null,
|
||||
'rideTimeStart' => $ride['rideTimeStart'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// آخر موقع معروف للسائق من Redis — أدق من أي شيء في MySQL، ويجيب
|
||||
// على السؤال الأول الذي تسأله غرفة العمليات: أين هم الآن؟
|
||||
if ($driverId && isset($redisLocation) && $redisLocation) {
|
||||
try {
|
||||
$loc = $redisLocation->hGetAll("driver:public:$driverId");
|
||||
if (!empty($loc)) {
|
||||
$snapshot['driver_location'] = [
|
||||
'lat' => $loc['lat'] ?? null,
|
||||
'lng' => $loc['lng'] ?? null,
|
||||
'heading' => $loc['heading'] ?? null,
|
||||
'speed' => $loc['speed'] ?? null,
|
||||
'updated_at' => $loc['updated_at'] ?? null,
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
error_log("[safety] تعذّرت قراءة موقع السائق: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// موقع المبلِّغ كما أرسله التطبيق. نخزّنه كما هو ولا نبني عليه قراراً —
|
||||
// قيمة قادمة من العميل، لكنها في بلاغ سلامة أفضل من لا شيء.
|
||||
if (is_numeric($lat) && is_numeric($lng)) {
|
||||
$snapshot['reporter_location'] = ['lat' => (float) $lat, 'lng' => (float) $lng];
|
||||
}
|
||||
|
||||
// ── ٢. تسجيل البلاغ ─────────────────────────────────────
|
||||
$ins = $con->prepare("
|
||||
INSERT INTO safety_incidents
|
||||
(reporter_type, reporter_id, trigger_type, ride_id,
|
||||
driver_id, passenger_id, lat, lng, note, snapshot)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$ins->execute([
|
||||
$reporterType, $reporterId, $triggerType, $rideId ?: null,
|
||||
$driverId, $passengerId,
|
||||
is_numeric($lat) ? $lat : null,
|
||||
is_numeric($lng) ? $lng : null,
|
||||
$note ? mb_substr($note, 0, 500) : null,
|
||||
json_encode($snapshot, JSON_UNESCAPED_UNICODE),
|
||||
]);
|
||||
$incidentId = (int) $con->lastInsertId();
|
||||
|
||||
error_log("[safety] بلاغ #$incidentId — $triggerType من $reporterType"
|
||||
. " $reporterId (رحلة=" . ($rideId ?: '-') . " سائق=" . ($driverId ?: '-') . ")");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("[safety] فشل تسجيل البلاغ: " . $e->getMessage());
|
||||
jsonError("Failed to record incident", 500);
|
||||
}
|
||||
|
||||
// ── ٣. تجميد إسناد الرحلات للسائق ───────────────────────────
|
||||
// يوقف تكرار الواقعة فوراً. منفصل عن blockedUntil (عقوبة الإلغاءات) الذي
|
||||
// ينتهي بعد ساعات وحده — هذا يبقى حتى يرفعه إنسان.
|
||||
// لا يُطبَّق على بلاغ السائق عن نفسه: قد يكون هو المعتدى عليه.
|
||||
if ($driverId && $reporterType === 'passenger') {
|
||||
try {
|
||||
$con->prepare("UPDATE driver SET safetyHoldIncidentId = ? WHERE id = ?")
|
||||
->execute([$incidentId, $driverId]);
|
||||
|
||||
// نسخة Redis هي ما يفحصه acceptRide.php — بلا TTL: التجميد لا ينتهي بالوقت.
|
||||
if (isset($redisLocation) && $redisLocation) {
|
||||
$redisLocation->set("driver:$driverId:safety_hold", (string) $incidentId);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
error_log("[safety] تعذّر تجميد السائق $driverId: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ── ٤. تنبيه الإدارة ────────────────────────────────────────
|
||||
// أفضل جهد: البلاغ مسجَّل بالفعل، وفشل التنبيه لا يُسقطه.
|
||||
try {
|
||||
if (isset($redis) && $redis) {
|
||||
// قائمة يقرأها بانل الإدارة — أسرع من استعلام على الجدول عند كل تحديث
|
||||
$redis->lPush('safety:open_incidents', (string) $incidentId);
|
||||
$redis->lTrim('safety:open_incidents', 0, 199);
|
||||
}
|
||||
|
||||
if (function_exists('sendFCM_Internal')) {
|
||||
$topic = getenv('SAFETY_ALERT_TOPIC') ?: 'siro_safety_ops';
|
||||
sendFCM_Internal(
|
||||
$topic,
|
||||
"🚨 بلاغ سلامة #$incidentId",
|
||||
"بلاغ $triggerType من $reporterType" . ($rideId ? " — رحلة $rideId" : ""),
|
||||
['category' => 'safety_incident', 'incident_id' => (string) $incidentId],
|
||||
"safety_incident",
|
||||
true // topic
|
||||
);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
error_log("[safety] تعذّر تنبيه الإدارة للبلاغ #$incidentId: " . $e->getMessage());
|
||||
}
|
||||
|
||||
jsonSuccess([
|
||||
'incident_id' => $incidentId,
|
||||
'driver_frozen' => (bool) ($driverId && $reporterType === 'passenger'),
|
||||
], "Incident recorded");
|
||||
Reference in New Issue
Block a user