M-01: Host header injection - replaced HTTP_HOST with APP_DOMAIN M-02: Unauthenticated CRUD - ownership checks on carDrivers add/delete M-03: MD5 tracking token - replaced md5() with hash_hmac sha256 M-04: Webhook SMS - absolute log path instead of relative M-05: Weak 3-digit OTP - already noted as requirement (Fix #5) M-06: Redis without auth - added password + prefix to cancel_ride_by_driver M-07: SSRF bypass - str_ends_with -> strict equality in allowlist
585 lines
25 KiB
PHP
585 lines
25 KiB
PHP
<?php
|
|
/**
|
|
* Endpoint: register_driver_and_car.php
|
|
* [MODIFIED] Added vehicle_category_id and fuel_type_id support.
|
|
* [MODIFIED] Fixed birthdate logic: Append -01-01 BEFORE encryption.
|
|
* [MODIFIED] Added Syrian phone number formatting logic.
|
|
*/
|
|
//register_driver_and_car.php
|
|
$allowRegistration = true;
|
|
require_once __DIR__ . '/../../../connect.php';
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
try {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonError("Invalid method.");
|
|
exit;
|
|
}
|
|
|
|
$host = getenv('APP_DOMAIN') ?: 'api-syria.siromove.com';
|
|
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
|
|
$PUBLIC_BASE = "$protocol://$host/siro/auth/uploads/documents";
|
|
|
|
/* ================== 1) Input Fields ================== */
|
|
$raw_first_name = null;
|
|
$raw_last_name = null;
|
|
|
|
$required = ["phone", "password", "first_name", "last_name"];
|
|
$optional = [
|
|
"id","email","gender","license_type","national_number",
|
|
"name_arabic","issue_date","expiry_date","license_categories",
|
|
"address","licenseIssueDate","status","birthdate","site",
|
|
"employmentType","maritalStatus","fullNameMaritial","expirationDate"
|
|
];
|
|
$carRequired = [
|
|
"vin","car_plate","make","model","year","expiration_date",
|
|
"color","owner","color_hex","fuel"
|
|
];
|
|
// حقول اختيارية للسيارة (التصنيف والوقود الرقمي)
|
|
// vehicle_category_id, fuel_type_id
|
|
|
|
$docKeys = [
|
|
'id_front',
|
|
'id_back',
|
|
'driver_license',
|
|
'driver_license_back',
|
|
'profile_picture',
|
|
'criminal_record',
|
|
'car_license_front',
|
|
'car_license_back'
|
|
];
|
|
|
|
// Read driver fields
|
|
$data = [];
|
|
foreach ($required as $f) {
|
|
$v = filterRequest($f);
|
|
if ($v === null || $v === '') {
|
|
jsonError("Missing required field: $f");
|
|
exit;
|
|
}
|
|
$data[$f] = $v;
|
|
|
|
if ($f === 'first_name') $raw_first_name = $v;
|
|
if ($f === 'last_name') $raw_last_name = $v;
|
|
}
|
|
foreach ($optional as $f) {
|
|
$v = filterRequest($f);
|
|
$data[$f] = ($v === null || $v === '' || $v === 'Not specified') ? null : $v;
|
|
}
|
|
|
|
/* ================== 🟢 START PHONE FORMATTING LOGIC 🟢 ================== */
|
|
$country = 'Syria'; // Default
|
|
if (!empty($data['phone'])) {
|
|
$phone = $data['phone'];
|
|
|
|
// 1. إزالة المسافات والرموز
|
|
$phone = preg_replace('/[ \-\(\)\+]/', '', $phone);
|
|
$phone = trim($phone);
|
|
|
|
if (strpos($phone, '962') === 0 || strpos($phone, '00962') === 0) {
|
|
if (strpos($phone, '00962') === 0) $phone = substr($phone, 2);
|
|
$country = 'Jordan';
|
|
} elseif (strpos($phone, '20') === 0 || strpos($phone, '0020') === 0) {
|
|
if (strpos($phone, '0020') === 0) $phone = substr($phone, 2);
|
|
$country = 'Egypt';
|
|
} else {
|
|
// 2. توحيد البادئات الدولية (سوريا)
|
|
if (strpos($phone, '00963') === 0) {
|
|
$phone = substr($phone, 2);
|
|
} elseif (strpos($phone, '0963') === 0) {
|
|
$phone = substr($phone, 1);
|
|
}
|
|
|
|
// 3. معالجة الحالات الخاصة بالصفر الزائد بعد الرمز الدولي
|
|
if (strpos($phone, '96309') === 0) {
|
|
$phone = '9639' . substr($phone, 5);
|
|
}
|
|
elseif (strpos($phone, '9630') === 0) {
|
|
$phone = '9639' . substr($phone, 4);
|
|
}
|
|
|
|
// 4. معالجة الأرقام المحلية
|
|
elseif (strpos($phone, '09') === 0) {
|
|
$phone = '963' . substr($phone, 1);
|
|
}
|
|
elseif (strpos($phone, '9') === 0 && strlen($phone) == 9) {
|
|
$phone = '963' . $phone;
|
|
}
|
|
elseif (strpos($phone, '0') === 0 && strlen($phone) == 10) {
|
|
$phone = '963' . substr($phone, 1);
|
|
}
|
|
|
|
// 5. التأكد من وجود 9 بعد الرمز الدولي
|
|
if (strpos($phone, '963') === 0 && strlen($phone) > 3) {
|
|
if (strpos($phone, '9639') !== 0) {
|
|
$phone = '9639' . substr($phone, 3);
|
|
}
|
|
}
|
|
}
|
|
$data['phone'] = $phone;
|
|
}
|
|
/* ================== 🔴 END PHONE FORMATTING LOGIC 🔴 ================== */
|
|
|
|
|
|
// تجهيز تاريخ الميلاد قبل التشفير
|
|
if (!empty($data['birthdate'])) {
|
|
$data['birthdate'] = trim($data['birthdate']);
|
|
$data['birthdate'] = $data['birthdate'] . '-01-01';
|
|
} else {
|
|
$data['birthdate'] = '1970-01-01';
|
|
}
|
|
|
|
// Read car fields
|
|
$car = [];
|
|
foreach ($carRequired as $f) {
|
|
$v = filterRequest($f);
|
|
if ($v === null || $v === '') {
|
|
jsonError("Missing required field: $f");
|
|
exit;
|
|
}
|
|
$car[$f] = $v;
|
|
}
|
|
|
|
// Read document links
|
|
$docUrls = [];
|
|
foreach ($docKeys as $k) {
|
|
$u = filterRequest($k);
|
|
if (($k === 'driver_license_back' || $k === 'criminal_record') && ($u === null || $u === '')) continue;
|
|
if ($u === null || $u === '') {
|
|
jsonError("Missing document URL: $k");
|
|
exit;
|
|
}
|
|
if (!filter_var($u, FILTER_VALIDATE_URL)) {
|
|
jsonError("Invalid document URL: $k");
|
|
exit;
|
|
}
|
|
$docUrls[$k] = $u;
|
|
}
|
|
|
|
/* ================== AI PROCESSING START ================== */
|
|
$apiKey = getenv("GEMINI_API_KEY");
|
|
if ($apiKey) {
|
|
$promptBase = '
|
|
You are a highly secure AI Assistant specialized in analyzing identification and driver documents.
|
|
Country Context: ' . ($country ?? 'Syria') . '
|
|
|
|
### TASK
|
|
We are providing you with multiple images representing a driver\'s documents (National ID, Driver License, Profile Picture, Criminal Record, and Car Registration).
|
|
Extract all the required data accurately according to the exact schema provided, and perform a FACE MATCHING analysis.
|
|
Since the driver may be from Jordan, Egypt, or Syria, the layout, fields, and distribution of information between the front and back of each card varies widely by country.
|
|
Therefore, do NOT assume a specific field is on the front or the back of a card. You must scan all provided document images (e.g., both ID images, both license images) and intelligently locate the requested fields wherever they appear on the cards.
|
|
|
|
### RULES
|
|
1. Convert any Eastern-Arabic digits (٠١٢٣٤٥٦٧٨٩) to Western digits (0-9).
|
|
2. Dates must be formatted as ISO `YYYY-MM-DD`.
|
|
3. Smart Extraction: Scan all provided document images without restriction. Do not fail the overall request if some optional fields (like governorate or address or issue dates) are missing or unreadable on the card. Simply set those specific fields to `null` in the JSON, but do NOT set the overall status to failure. Overall status should only be \'failure\' if there is a critical security/authenticity issue (e.g., face mismatch, fake/forged documents, or missing primary driver identity).
|
|
4. FACE MATCHING (CRITICAL): Compare the face in the "Profile Picture" with the photos on the "National ID" and "Driver License".
|
|
5. Ensure the Criminal/Non-Conviction record is valid and matches the driver\'s name.
|
|
6. The `national_number` and `vin` (chassis) must contain Latin digits/characters only.
|
|
7. Normalize color names (e.g. "أبيض" -> "White") and provide a matching Hex code (e.g. "#FFFFFF").
|
|
8. Do NOT add markdown formatting around the output. Return ONLY raw JSON.
|
|
|
|
### REQUIRED JSON OUTPUT FORMAT
|
|
{
|
|
"status": "success|failure",
|
|
"reason": "If failure, state the reason (e.g., Face mismatch, blurry, invalid record)",
|
|
"face_match_confidence": "high|low",
|
|
"driver": {
|
|
"full_name": "", // Full name in Arabic
|
|
"national_number": "", // National ID/National number (Latin digits)
|
|
"dob": "YYYY-MM-DD", // Date of birth
|
|
"address": "", // Full address
|
|
"governorate": "", // Governorate/Site/City
|
|
"gender": "Male|Female", // Gender
|
|
"id_issue_date": "YYYY-MM-DD", // National ID issue date
|
|
"license_issue_date": "YYYY-MM-DD", // Driver license issue date
|
|
"license_expiry_date": "YYYY-MM-DD", // Driver license expiry date
|
|
"license_number": "", // Driver license number
|
|
"license_category": "", // License category (e.g., D1, B, Private, Public)
|
|
"blood_type": "", // Blood type (e.g., A+, O-)
|
|
"civil_registry": "", // Civil registry/Place of registration
|
|
"birth_place": "" // Place of birth
|
|
},
|
|
"car": {
|
|
"car_plate": "", // Full car plate (e.g., 155186 درعا)
|
|
"owner": "", // Owner full name
|
|
"vin": "", // Chassis/VIN number
|
|
"color": "", // Color name
|
|
"color_hex": "", // Color hex code (e.g., #FFFFFF)
|
|
"car_issue_date": "YYYY-MM-DD", // Car registration issue date
|
|
"inspection_date": "YYYY-MM-DD", // Car next inspection date
|
|
"make": "", // Car Make (e.g., Hyundai)
|
|
"model": "", // Car Model (e.g., H1)
|
|
"year": "", // Manufacturing year (e.g., 2019)
|
|
"fuel": "" // Fuel type (e.g., Petrol, Diesel, Electric)
|
|
}
|
|
}';
|
|
|
|
$contents = [
|
|
["role" => "user", "parts" => [["text" => $promptBase]]]
|
|
];
|
|
|
|
// ✅ SSRF Protection: Allowlist for document URLs
|
|
$allowedHosts = array_filter([
|
|
parse_url($PUBLIC_BASE, PHP_URL_HOST),
|
|
getenv('ALLOWED_UPLOAD_HOST'),
|
|
]);
|
|
$maxFileSize = 10 * 1024 * 1024; // 10MB max per image
|
|
|
|
foreach ($docUrls as $key => $url) {
|
|
$urlHost = parse_url($url, PHP_URL_HOST);
|
|
$allowed = false;
|
|
foreach ($allowedHosts as $host) {
|
|
if ($host && $urlHost === $host) {
|
|
$allowed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!$allowed) {
|
|
error_log("[SSRF_BLOCKED] Doc URL not in allowlist: $urlHost ($key)");
|
|
continue;
|
|
}
|
|
|
|
$ctx = stream_context_create(['http' => [
|
|
'timeout' => 10,
|
|
'ignore_errors' => true,
|
|
]]);
|
|
$imgData = @file_get_contents($url, false, $ctx, 0, $maxFileSize);
|
|
if ($imgData !== false) {
|
|
$base64 = base64_encode($imgData);
|
|
$ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
|
|
$mime = ($ext === 'png') ? 'image/png' : 'image/jpeg';
|
|
$contents[0]["parts"][] = ["text" => "Image type: " . $key];
|
|
$contents[0]["parts"][] = ["inlineData" => ["mimeType" => $mime, "data" => $base64]];
|
|
}
|
|
}
|
|
|
|
$apiURL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-lite-latest:generateContent?key=$apiKey";
|
|
$payload = ["contents" => $contents];
|
|
|
|
$ch = curl_init($apiURL);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
|
$response = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
if ($response) {
|
|
$aiData = json_decode($response, true);
|
|
$textRaw = $aiData['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
|
$textRaw = trim(preg_replace('/```json|```/', '', $textRaw));
|
|
$json = json_decode($textRaw, true);
|
|
|
|
if ($json && isset($json['status']) && strtolower($json['status']) === 'failure') {
|
|
jsonError("AI Verification Failed: " . ($json['reason'] ?? 'Unknown reason'));
|
|
exit;
|
|
}
|
|
|
|
if ($json && isset($json['driver'])) {
|
|
$ex = $json['driver'];
|
|
if (!empty($ex['full_name'])) {
|
|
$data['name_arabic'] = $ex['full_name'];
|
|
$parts = explode(' ', trim($ex['full_name']));
|
|
if (count($parts) >= 2) {
|
|
$data['first_name'] = $parts[0];
|
|
$data['last_name'] = implode(' ', array_slice($parts, 1));
|
|
} else {
|
|
$data['first_name'] = $ex['full_name'];
|
|
$data['last_name'] = '';
|
|
}
|
|
}
|
|
if (!empty($ex['national_number'])) $data['national_number'] = $ex['national_number'];
|
|
if (!empty($ex['dob'])) {
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $ex['dob'])) {
|
|
$data['birthdate'] = $ex['dob'];
|
|
} elseif (preg_match('/^\d{4}$/', $ex['dob'])) {
|
|
$data['birthdate'] = $ex['dob'] . '-01-01';
|
|
}
|
|
}
|
|
if (!empty($ex['address'])) $data['address'] = $ex['address'];
|
|
if (!empty($ex['governorate'])) $data['site'] = $ex['governorate'];
|
|
if (!empty($ex['gender'])) $data['gender'] = $ex['gender'];
|
|
if (!empty($ex['id_issue_date'])) $data['issue_date'] = $ex['id_issue_date'];
|
|
if (!empty($ex['license_issue_date'])) $data['licenseIssueDate'] = $ex['license_issue_date'];
|
|
if (!empty($ex['license_expiry_date'])) $data['expiry_date'] = $ex['license_expiry_date'];
|
|
if (!empty($ex['license_category'])) $data['license_categories'] = $ex['license_category'];
|
|
// Not mapped directly in basic DB schema but extracted: blood_type, civil_registry, birth_place
|
|
}
|
|
|
|
if ($json && isset($json['car'])) {
|
|
$ex = $json['car'];
|
|
if (!empty($ex['car_plate'])) $car['car_plate'] = $ex['car_plate'];
|
|
if (!empty($ex['make'])) $car['make'] = $ex['make'];
|
|
if (!empty($ex['model'])) $car['model'] = $ex['model'];
|
|
if (!empty($ex['year'])) $car['year'] = $ex['year'];
|
|
if (!empty($ex['color'])) $car['color'] = $ex['color'];
|
|
if (!empty($ex['color_hex'])) $car['color_hex'] = $ex['color_hex'];
|
|
if (!empty($ex['vin'])) $car['vin'] = $ex['vin'];
|
|
if (!empty($ex['owner'])) $car['owner'] = $ex['owner'];
|
|
if (!empty($ex['fuel'])) $car['fuel'] = $ex['fuel'];
|
|
}
|
|
}
|
|
}
|
|
/* ================== AI PROCESSING END ================== */
|
|
|
|
/* ================== 2) Generate default id/email ================== */
|
|
if (empty($data['id'])) {
|
|
$data['id'] = 'DRV' . date('YmdHis') . random_int(1000, 9999);
|
|
}
|
|
if ($data['email'] === null) {
|
|
$data['email'] = $data['phone'] . '@intaleqapp.com';
|
|
}
|
|
|
|
/* ================== 3) Encrypt sensitive fields ================== */
|
|
$toEncryptDriver = [
|
|
"phone","email","first_name","last_name","name_arabic","gender",
|
|
"national_number","address","site","fullNameMaritial","birthdate"
|
|
];
|
|
|
|
foreach ($toEncryptDriver as $f) {
|
|
if (!empty($data[$f])) {
|
|
$data[$f] = $encryptionHelper->encryptData($data[$f]);
|
|
}
|
|
}
|
|
|
|
// Encrypt car sensitive data
|
|
$car['vin'] = $encryptionHelper->encryptData($car['vin']);
|
|
$car['car_plate'] = $encryptionHelper->encryptData($car['car_plate']);
|
|
$car['owner'] = $encryptionHelper->encryptData($car['owner']);
|
|
|
|
/* ================== 4) Hash password (HMAC + password_hash) ================== */
|
|
|
|
// نقرأ الـ HMAC key من env
|
|
$pepper = getenv('SECRET_KEY_HMAC');
|
|
|
|
// نبني baseString من أكثر من بارامتر
|
|
// هنا نستخدم id + phone (بعد ما طبّقنا منطق تنسيق الهاتف)
|
|
$baseParts = [
|
|
$data['id'],
|
|
$data['phone'],
|
|
];
|
|
|
|
// نضيف رقم وطني أو سنة الميلاد إن توفروا (كما في الـ migration)
|
|
if (!empty($data['national_number'])) {
|
|
$baseParts[] = $data['national_number'];
|
|
} elseif (!empty($data['birthdate'])) {
|
|
// birthdate حالياً أصبح بصيغة YYYY-01-01
|
|
$year = substr($data['birthdate'], 0, 4);
|
|
if (preg_match('/^\d{4}$/', $year)) {
|
|
$baseParts[] = $year;
|
|
}
|
|
}
|
|
|
|
$baseString = implode('|', $baseParts);
|
|
|
|
// نشتق السر الخام باستخدام HMAC-SHA256 مع SECRET_KEY_HMAC
|
|
$rawSecret = hash_hmac('sha256', $baseString, $pepper, true);
|
|
|
|
// نخزّن فقط الهاش الناتج من password_hash في قاعدة البيانات
|
|
$pwdHashed = password_hash($rawSecret, PASSWORD_DEFAULT);
|
|
|
|
/* ================== 5) Start transaction ================== */
|
|
$con->beginTransaction();
|
|
|
|
/* ================== 6) Check duplicate ================== */
|
|
$dup = $con->prepare("SELECT id FROM driver WHERE phone = :p OR email = :e");
|
|
$dup->execute([':p' => $data['phone'], ':e' => $data['email']]);
|
|
if ($dup->rowCount() > 0) {
|
|
$con->rollBack();
|
|
jsonError("Phone or email already registered.");
|
|
exit;
|
|
}
|
|
|
|
/* ================== 7) Insert Driver ================== */
|
|
$sqlDriver = "
|
|
INSERT INTO driver (
|
|
id, phone, email, password, gender, license_type, national_number,
|
|
name_arabic, issue_date, expiry_date, license_categories,
|
|
address, licenseIssueDate, status, birthdate, site,
|
|
first_name, last_name, accountBank, bankCode,
|
|
employmentType, maritalStatus, fullNameMaritial, expirationDate,
|
|
created_at, updated_at
|
|
) VALUES (
|
|
:id, :phone, :email, :pwd, :gender, :license_type, :national_number,
|
|
:name_arabic, :issue_date, :expiry_date, :license_categories,
|
|
:address, :licenseIssueDate, :status, :birthdate, :site,
|
|
:first_name, :last_name, :accountBank, :bankCode,
|
|
:employmentType, :maritalStatus, :fullNameMaritial, :expirationDate,
|
|
NOW(), NOW()
|
|
)
|
|
";
|
|
$insD = $con->prepare($sqlDriver);
|
|
$okD = $insD->execute([
|
|
':id' => $data['id'],
|
|
':phone' => $data['phone'],
|
|
':email' => $data['email'],
|
|
':pwd' => $pwdHashed,
|
|
':gender' => !empty($data['gender']) ? $data['gender'] : 'Male',
|
|
':license_type' => !empty($data['license_type']) ? $data['license_type'] : 'yet',
|
|
':national_number' => $data['national_number'],
|
|
':name_arabic' => $data['name_arabic'],
|
|
':issue_date' => !empty($data['issue_date']) ? $data['issue_date'] : '2020-01-01',
|
|
':expiry_date' => !empty($data['expiry_date']) ? $data['expiry_date'] : 'yet',
|
|
':license_categories' => !empty($data['license_categories']) ? $data['license_categories'] : 'B',
|
|
':address' => $data['address'],
|
|
':licenseIssueDate' => !empty($data['licenseIssueDate']) ? $data['licenseIssueDate'] : '2020-01-01',
|
|
':status' => !empty($data['status']) ? $data['status'] : 'yet',
|
|
':birthdate' => $data['birthdate'],
|
|
':site' => !empty($data['site']) ? $data['site'] : 'demascus',
|
|
':first_name' => $data['first_name'],
|
|
':last_name' => $data['last_name'],
|
|
':accountBank' => 'yet',
|
|
':bankCode' => 'yet',
|
|
':employmentType' => !empty($data['employmentType']) ? $data['employmentType'] : 'yet',
|
|
':maritalStatus' => !empty($data['maritalStatus']) ? $data['maritalStatus'] : 'yet',
|
|
':fullNameMaritial' => !empty($data['fullNameMaritial']) ? $data['fullNameMaritial'] : 'yet',
|
|
':expirationDate' => !empty($data['expirationDate']) ? $data['expirationDate'] : 'yet',
|
|
]);
|
|
if (!$okD) {
|
|
$con->rollBack();
|
|
jsonError("Failed to insert driver.");
|
|
exit;
|
|
}
|
|
|
|
$driverID = $data['id'];
|
|
|
|
/* ================== 8) Insert Vehicle ================== */
|
|
// ✅ استقبال القيم الجديدة (التصنيف والوقود) مع تعيين افتراضي 1
|
|
$vCatID = filterRequest("vehicle_category_id");
|
|
$vCatID = ($vCatID !== null && $vCatID !== '') ? $vCatID : 1; // 1 = Car
|
|
|
|
$fTypeID = filterRequest("fuel_type_id");
|
|
$fTypeID = ($fTypeID !== null && $fTypeID !== '') ? $fTypeID : 1; // 1 = Petrol
|
|
|
|
$hasCar = $con->prepare("SELECT 1 FROM CarRegistration WHERE driverID = :d LIMIT 1");
|
|
$hasCar->execute([':d' => $driverID]);
|
|
$isDefault = $hasCar->rowCount() === 0 ? 1 : 0;
|
|
|
|
$sqlCar = "
|
|
INSERT INTO CarRegistration (
|
|
driverID, vin, car_plate, make, model, year, expiration_date,
|
|
color, owner, color_hex, fuel,
|
|
vehicle_category_id, fuel_type_id,
|
|
isDefault, created_at, status
|
|
) VALUES (
|
|
:driverID, :vin, :car_plate, :make, :model, :year, :expiration_date,
|
|
:color, :owner, :color_hex, :fuel,
|
|
:vehicle_category_id, :fuel_type_id,
|
|
:isDefault, NOW(), 'yet'
|
|
)
|
|
";
|
|
$insC = $con->prepare($sqlCar);
|
|
$okC = $insC->execute([
|
|
':driverID' => $driverID,
|
|
':vin' => $car['vin'],
|
|
':car_plate' => $car['car_plate'],
|
|
':make' => $car['make'],
|
|
':model' => $car['model'],
|
|
':year' => $car['year'],
|
|
':expiration_date' => $car['expiration_date'],
|
|
':color' => $car['color'],
|
|
':owner' => $car['owner'],
|
|
':color_hex' => $car['color_hex'],
|
|
':fuel' => $car['fuel'], // النص القديم (للتوافق)
|
|
':vehicle_category_id' => $vCatID, // ✅ العمود الجديد
|
|
':fuel_type_id' => $fTypeID, // ✅ العمود الجديد
|
|
':isDefault' => $isDefault,
|
|
]);
|
|
if (!$okC) {
|
|
$con->rollBack();
|
|
jsonError("Failed to insert car registration.");
|
|
exit;
|
|
}
|
|
|
|
$carRegID = $con->lastInsertId();
|
|
|
|
/* ================== 9) Store document links ================== */
|
|
$insDoc = $con->prepare("
|
|
INSERT INTO driver_documents (driverID, doc_type, image_name, link, upload_date)
|
|
VALUES (:driverID, :doc_type, :image_name, :link, NOW())
|
|
");
|
|
|
|
foreach ($docKeys as $k) {
|
|
if (!isset($docUrls[$k])) continue;
|
|
$url = $docUrls[$k];
|
|
$name = basename(parse_url($url, PHP_URL_PATH) ?? '');
|
|
if ($name === '') { $name = $k . '_' . time() . '.jpg'; }
|
|
|
|
$insDoc->execute([
|
|
':driverID' => $driverID,
|
|
':doc_type' => $k,
|
|
':image_name' => $name,
|
|
':link' => $url,
|
|
]);
|
|
|
|
if ($k === 'profile_picture') {
|
|
$insProfile = $con->prepare("
|
|
INSERT INTO imageProfileCaptain (driverID, image_name, link)
|
|
VALUES (:driverID, :image_name, :link)
|
|
");
|
|
$insProfile->execute([
|
|
':driverID' => $driverID,
|
|
':image_name' => $name,
|
|
':link' => $url,
|
|
]);
|
|
}
|
|
}
|
|
|
|
/* ================== 10) Commit ================== */
|
|
$con->commit();
|
|
|
|
/* ================== 11) Notification ================== */
|
|
try {
|
|
$fcmSendUrl = 'https://api.intaleq.xyz/siro/ride/firebase/send_fcm.php';
|
|
|
|
$driverFullName = $raw_first_name . ' ' . $raw_last_name;
|
|
$notificationTitle = 'تسجيل سائق جديد';
|
|
$notificationBody = "سائق جديد ($driverFullName) سجل برقم ID: $driverID وهو بانتظار المراجعة والتفعيل.";
|
|
|
|
$notificationPayload = json_encode([
|
|
'target' => 'service',
|
|
'title' => $notificationTitle,
|
|
'body' => $notificationBody,
|
|
'isTopic' => true,
|
|
'category' => 'new_driver_registration'
|
|
]);
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $fcmSendUrl);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=UTF-8']);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $notificationPayload);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
|
|
|
curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
} catch (Exception $notifyEx) {
|
|
error_log("register_driver_and_car NOTIFY ERROR: " . $notifyEx->getMessage());
|
|
}
|
|
|
|
printSuccess([
|
|
'status' => 'success',
|
|
'driverID' => $driverID,
|
|
'carRegID' => $carRegID,
|
|
'documents' => $docUrls
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
if (isset($con) && $con instanceof PDO && $con->inTransaction()) {
|
|
$con->rollBack();
|
|
}
|
|
error_log("register_driver_and_car ERROR: " . $e->getMessage());
|
|
jsonError("Server error");
|
|
} catch (PDOException $e) {
|
|
if (isset($con) && $con instanceof PDO && $con->inTransaction()) {
|
|
$con->rollBack();
|
|
}
|
|
error_log("register_driver_and_car PDO: " . $e->getMessage());
|
|
jsonError("Database error.");
|
|
}
|
|
?>
|