Refactor(Auth): Complete auth folder restructuring, security patches, and Flutter endpoint updates
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// Sanitize and validate input
|
||||
$driverId = filterRequest("driverId");
|
||||
$issueDate = filterRequest("IssueDate");
|
||||
$inspectionResult = filterRequest("InspectionResult");
|
||||
|
||||
// Prepare SQL statement
|
||||
$sql = "INSERT INTO criminalDocuments (driverId, IssueDate, InspectionResult)
|
||||
VALUES (:driverId, :issueDate, :inspectionResult)";
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
// Bind parameters
|
||||
$stmt->bindParam(':driverId', $driverId, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':issueDate', $issueDate, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':inspectionResult', $inspectionResult, PDO::PARAM_STR);
|
||||
|
||||
// Execute the statement
|
||||
$stmt->execute();
|
||||
|
||||
// Check if the insertion was successful
|
||||
if ($stmt->rowCount() > 0) {
|
||||
jsonSuccess(null, "Criminal document data saved successfully");
|
||||
} else {
|
||||
jsonError("Failed to save criminal document data");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// Log the error and print a generic failure message
|
||||
error_log("Database Error: " . $e->getMessage());
|
||||
jsonError("An error occurred while saving the data");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../connect.php';
|
||||
|
||||
// استقبال وتشفير رقم الهاتف
|
||||
$phoneNumber = filterRequest("phone_number");
|
||||
$phoneNumber = $encryptionHelper->encryptData($phoneNumber);
|
||||
|
||||
// تجهيز الاستعلام باستخدام bindParam للحماية
|
||||
$sql = "SELECT * FROM `phone_verification` WHERE `phone_number` = :phone_number";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(":phone_number", $phoneNumber);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
jsonSuccess($rows);
|
||||
} else {
|
||||
jsonError("No phone verified yet found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$id = filterRequest("id");
|
||||
// يمكن استقبال سبب الحظر من التطبيق أو وضعه كقيمة افتراضية
|
||||
$reason = "Driver requested deletion (deleteFromHimself)";
|
||||
|
||||
// تأكد أن المعرف رقم صحيح
|
||||
if (!is_numeric($id)) {
|
||||
jsonError("Invalid ID");
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. جلب رقم الهاتف الخاص بالسائق قبل التحديث
|
||||
// نحتاج الهاتف لإضافته في القائمة السوداء
|
||||
$stmtPhone = $con->prepare("SELECT phone FROM `driver` WHERE `id` = :id");
|
||||
$stmtPhone->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmtPhone->execute();
|
||||
$driverData = $stmtPhone->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// التحقق من وجود السائق
|
||||
if (!$driverData) {
|
||||
jsonError("Driver not found");
|
||||
exit();
|
||||
}
|
||||
|
||||
$phone = $driverData['phone'];
|
||||
|
||||
// 2. تحديث حالة السائق
|
||||
$sql = "UPDATE `driver` SET `status` = 'deleteFromHimself' WHERE `id` = :id";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// 3. الإضافة إلى القائمة السوداء (blacklist_driver)
|
||||
// نستخدم NOW() لتسجيل الوقت الحالي تلقائياً
|
||||
// لا نمرر id العمود الأول لأنه غالباً Auto Increment في قاعدة البيانات
|
||||
$insertSql = "INSERT INTO `blacklist_driver` (`driver_id`, `phone`, `reason`, `created_at`)
|
||||
VALUES (:driver_id, :phone, :reason, NOW())";
|
||||
|
||||
$insertStmt = $con->prepare($insertSql);
|
||||
$insertStmt->execute([
|
||||
':driver_id' => $id,
|
||||
':phone' => $phone,
|
||||
':reason' => $reason
|
||||
]);
|
||||
|
||||
jsonSuccess(null, "Record marked as deleted and added to blacklist successfully");
|
||||
} else {
|
||||
jsonError("Failed to update record or no change made");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// في حال حدوث خطأ في قاعدة البيانات (مثلاً تكرار الإضافة)
|
||||
error_log("[deletecaptainAccounr] " . $e->getMessage());
|
||||
jsonError("Database Error");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
// 🔥 [Fix Broken Access Control] كان يتحقق من صلاحية التوكن فقط — أي مستخدم
|
||||
// مسجّل دخول (راكب/سائق آخر) كان يقدر يجلب بيانات أي سائق مفكوكة التشفير
|
||||
// (هوية وطنية، هاتف، عنوان...) بالإضافة لروابط وثائقه الشخصية.
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized access. Admin role required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$driverId = filterRequest("id");
|
||||
|
||||
if (empty($driverId)) {
|
||||
jsonError("driver_id is required.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// تفاصيل السائق
|
||||
$sql = "SELECT * FROM driver WHERE id = :id LIMIT 1";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([':id' => $driverId]);
|
||||
$driver = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$driver) {
|
||||
jsonError("Driver not found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// فك التشفير للحقول الحساسة
|
||||
foreach ($driver as $k => $v) {
|
||||
if (in_array($k, ['phone',
|
||||
'email',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'national_number',
|
||||
'address','gender','site',
|
||||
'birthdate',
|
||||
'name_arabic'])) {
|
||||
$driver[$k] = $encryptionHelper->decryptData($v);
|
||||
}
|
||||
}
|
||||
|
||||
// الوثائق
|
||||
$sql2 = "SELECT doc_type, image_name, link FROM driver_documents WHERE driverID = :id";
|
||||
$stmt2 = $con->prepare($sql2);
|
||||
$stmt2->execute([':id' => $driverId]);
|
||||
$docs = $stmt2->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess([
|
||||
"driver" => $driver,
|
||||
"documents" => $docs
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("[driver_details] " . $e->getMessage());
|
||||
jsonError("Error fetching details");
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
// 🔥 [Fix Broken Access Control] كان يتحقق من صلاحية التوكن فقط بدون التحقق
|
||||
// من الدور — أي توكن صالح (حتى راكب) كان يقدر يسحب قائمة السائقين المعلّقين
|
||||
// وبياناتهم الشخصية المفكوكة التشفير.
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized access. Admin role required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$limit = isset($_POST['limit']) ? (int)$_POST['limit'] : (isset($_GET['limit']) ? (int)$_GET['limit'] : 10);
|
||||
$offset = isset($_POST['offset']) ? (int)$_POST['offset'] : (isset($_GET['offset']) ? (int)$_GET['offset'] : 0);
|
||||
|
||||
try {
|
||||
$sql = "SELECT id, first_name, last_name, phone FROM driver WHERE status <> 'active' ORDER BY id DESC LIMIT :limit OFFSET :offset";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// فك التشفير
|
||||
foreach ($rows as &$r) {
|
||||
$r['phone'] = $encryptionHelper->decryptData($r['phone']);
|
||||
$r['first_name'] = $encryptionHelper->decryptData($r['first_name']);
|
||||
$r['last_name'] = $encryptionHelper->decryptData($r['last_name']);
|
||||
}
|
||||
|
||||
jsonSuccess($rows); // يرجع كـ message: [...]
|
||||
} catch (PDOException $e) {
|
||||
error_log("[drivers_pending_list] " . $e->getMessage());
|
||||
jsonError("Error fetching data");
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$driverID = filterRequest("id");
|
||||
|
||||
// تحقق أن المعرف رقم صحيح
|
||||
if (!is_numeric($driverID)) {
|
||||
jsonError("Invalid driver ID");
|
||||
exit();
|
||||
}
|
||||
|
||||
// استخدم bindParam لتفادي حقن SQL
|
||||
$sql = "SELECT `accountBank` FROM `driver` WHERE `id` = :id";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':id', $driverID, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
jsonSuccess($row);
|
||||
} else {
|
||||
jsonError("No account bank record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// $driverID = filterRequest("id");
|
||||
|
||||
$sql = "
|
||||
SELECT * FROM `promptDriverIDEgypt`";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
jsonSuccess($row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
jsonError($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
$phoneNumber = filterRequest("phone_number");
|
||||
|
||||
// تشفير الرقم قبل البحث
|
||||
$phoneNumber_encrypted = $encryptionHelper->encryptData($phoneNumber);
|
||||
|
||||
try {
|
||||
// الاستعلام عن السائق حسب رقم الهاتف وحالة التحقق
|
||||
$stmt = $con->prepare("
|
||||
SELECT * FROM phone_verification
|
||||
WHERE phone_number = ? AND is_verified = 1
|
||||
");
|
||||
$stmt->execute([$phoneNumber_encrypted]);
|
||||
$driver = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($driver) {
|
||||
jsonSuccess(null, "Phone number is verified.");
|
||||
} else {
|
||||
jsonError("Phone number is not verified or does not exist.");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("[isPhoneVerified] " . $e->getMessage());
|
||||
jsonError("Database error");
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$email = filterRequest('email');
|
||||
$phone = filterRequest('phone');
|
||||
$password = filterRequest('password');
|
||||
|
||||
// تشفير الحقول المطلوبة قبل الاستعلام
|
||||
$email = $encryptionHelper->encryptData($email);
|
||||
$phone = $encryptionHelper->encryptData($phone);
|
||||
|
||||
$sql = "SELECT
|
||||
driver.id,
|
||||
driver.phone,
|
||||
driver.email,
|
||||
driver.password,
|
||||
driver.gender,
|
||||
driver.birthdate,
|
||||
driver.site,
|
||||
driver.first_name,
|
||||
driver.last_name,
|
||||
driver.education,
|
||||
driver.employmentType,
|
||||
driver.maritalStatus,
|
||||
driver.created_at,
|
||||
driver.updated_at,
|
||||
email_verifications.verified
|
||||
FROM
|
||||
driver
|
||||
LEFT JOIN email_verifications ON email_verifications.email = driver.email
|
||||
WHERE
|
||||
driver.phone = :phone AND driver.email = :email";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':email', $email);
|
||||
$stmt->bindParam(':phone', $phone);
|
||||
$stmt->execute();
|
||||
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$count = $stmt->rowCount();
|
||||
|
||||
if ($count > 0) {
|
||||
$stored_password = $data[0]['password'];
|
||||
if (password_verify($password, $stored_password)) {
|
||||
|
||||
// فك التشفير للحقول الحساسة
|
||||
$data[0]['phone'] = $encryptionHelper->decryptData($data[0]['phone']);
|
||||
$data[0]['email'] = $encryptionHelper->decryptData($data[0]['email']);
|
||||
$data[0]['gender'] = $encryptionHelper->decryptData($data[0]['gender']);
|
||||
$data[0]['birthdate'] = $encryptionHelper->decryptData($data[0]['birthdate']);
|
||||
$data[0]['site'] = $encryptionHelper->decryptData($data[0]['site']);
|
||||
$data[0]['first_name'] = $encryptionHelper->decryptData($data[0]['first_name']);
|
||||
$data[0]['last_name'] = $encryptionHelper->decryptData($data[0]['last_name']);
|
||||
$data[0]['education'] = $encryptionHelper->decryptData($data[0]['education']);
|
||||
$data[0]['employmentType'] = $encryptionHelper->decryptData($data[0]['employmentType']);
|
||||
$data[0]['maritalStatus'] = $encryptionHelper->decryptData($data[0]['maritalStatus']);
|
||||
|
||||
unset($data[0]['password']); // لا نرجّع الباسورد
|
||||
jsonSuccess($data);
|
||||
} else {
|
||||
jsonError("Incorrect password.");
|
||||
}
|
||||
} else {
|
||||
jsonError("User does not exist.");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
// loginFromGoogle.php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
/* ────────────────────────────────
|
||||
1) استخدام ID: من الطلب أولاً، ثم من JWT
|
||||
───────────────────────────────── */
|
||||
$driverID = filterRequest('driver_id') ?: $user_id;
|
||||
|
||||
error_log("[Debug] DriverID from JWT: $driverID");
|
||||
|
||||
/* ────────────────────────────────
|
||||
3) إعداد الاستعلام الموحَّد
|
||||
───────────────────────────────── */
|
||||
$sql = "
|
||||
SELECT
|
||||
driver.id, driver.phone, driver.email, driver.gender, driver.birthdate,
|
||||
driver.site, driver.first_name, driver.last_name, driver.bankCode,
|
||||
driver.accountBank, driver.employmentType,driver.status, driver.maritalStatus,
|
||||
driver.created_at, driver.updated_at,
|
||||
phone_verification.is_verified,
|
||||
CarRegistration.make, CarRegistration.model, CarRegistration.year,
|
||||
df.is_claimed, inv.isInstall, inv.isGiftToken
|
||||
FROM driver
|
||||
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone
|
||||
LEFT JOIN driver_gifts df ON df.driver_id = driver.id
|
||||
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
|
||||
LEFT JOIN invites inv ON inv.driverId = driver.id
|
||||
WHERE
|
||||
driver.id = :id
|
||||
-- AND phone_verification.is_verified = '1'
|
||||
LIMIT 1
|
||||
";
|
||||
|
||||
// error_log("[Debug] queryString:\n$sql");
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
// باراميترات الربط
|
||||
$params = [
|
||||
':id' => $driverID,
|
||||
];
|
||||
foreach ($params as $k => $v) {
|
||||
$stmt->bindValue($k, $v);
|
||||
}
|
||||
|
||||
/* ───────── dumpParams (اختياري) ───────── */
|
||||
ob_start();
|
||||
$stmt->debugDumpParams();
|
||||
error_log("[Debug] dumpParams:\n" . ob_get_clean());
|
||||
|
||||
/* ────────────────────────────────
|
||||
4) تنفيذ الاستعلام
|
||||
───────────────────────────────── */
|
||||
$stmt->execute();
|
||||
error_log("[Debug] stmt->rowCount(): " . $stmt->rowCount());
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
// error_log("[Debug] Raw fetched JSON: " . json_encode($rows, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$rows) {
|
||||
jsonError("User does not exist or phone not verified.");
|
||||
exit;
|
||||
}
|
||||
|
||||
/* ────────────────────────────────
|
||||
5) فك التشفير للحقول الحسّاسة
|
||||
───────────────────────────────── */
|
||||
$data = &$rows[0]; // مرجع لتوفير الذاكرة
|
||||
|
||||
$decryptIfNotNull = function($field) use (&$data, $encryptionHelper) {
|
||||
if (isset($data[$field]) && $data[$field] !== null) {
|
||||
$data[$field] = $encryptionHelper->decryptData($data[$field]);
|
||||
}
|
||||
};
|
||||
|
||||
foreach ([
|
||||
'phone', 'email', 'gender', 'birthdate', 'site',
|
||||
'first_name', 'last_name'
|
||||
] as $field) {
|
||||
$decryptIfNotNull($field);
|
||||
}
|
||||
error_log("[Debug] Raw fetched JSON: " . json_encode($rows, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"count" => 1,
|
||||
"data" => $rows // نتيجة واحدة فقط
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} catch (PDOException $e) {
|
||||
error_log("[PDO ERROR] " . $e->getMessage());
|
||||
jsonError("Database error: ".$e->getCode());
|
||||
} catch (Exception $e) {
|
||||
error_log("[GENERAL ERROR] " . $e->getMessage());
|
||||
jsonError("Error occurred.");
|
||||
} finally {
|
||||
$stmt = null;
|
||||
$con = null;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// loginUsingCredentialsWithoutGoogle.php
|
||||
// مخصص لدخول الفاحصين (Testers) بالإيميل والباسورد
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||
|
||||
$email = filterRequest('email');
|
||||
$password = filterRequest('password');
|
||||
$audience = filterRequest('aud') ?? 'siro-driver-android'; // الافتراضي
|
||||
$fingerprint = filterRequest('fingerPrint') ?? filterRequest('fingerprint');
|
||||
|
||||
// 1. تطبيق حد معدل الطلبات (Rate Limiting) للفاحصين: 3 محاولات بالدقيقة لكل IP
|
||||
$rateLimiter = new RateLimiter($redis);
|
||||
$rateLimiter->enforce(RateLimiter::identifier(), 'tester_login');
|
||||
|
||||
if (!$email || !$password) {
|
||||
echo json_encode(["status" => "failure", "message" => "Email and password are required"]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// 2. التحقق من أن الحساب مخصص للفحص فقط (isTest check)
|
||||
$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: '';
|
||||
$allowedEmails = array_filter(array_map('trim', explode(',', $allowedTesterEmailsEnv)));
|
||||
if (empty($allowedEmails)) {
|
||||
$allowedEmails = [
|
||||
'driver_tester@siromove.com',
|
||||
'passenger_tester@siromove.com',
|
||||
];
|
||||
}
|
||||
|
||||
$cleanEmail = strtolower(trim($email));
|
||||
$isTester = in_array($cleanEmail, $allowedEmails) ||
|
||||
substr($cleanEmail, -13) === '@siromove.com' ||
|
||||
str_contains($cleanEmail, 'tester') ||
|
||||
str_contains($cleanEmail, 'reviewer');
|
||||
|
||||
// تشفير الإيميل لاستخدامه في الاستعلام
|
||||
$encryptedEmail = $encryptionHelper->encryptData($email);
|
||||
|
||||
try {
|
||||
$con = Database::get('main');
|
||||
|
||||
// Auto-seed/create tester driver if it doesn't exist
|
||||
if ($cleanEmail === 'driver_tester@siromove.com') {
|
||||
$stmtCheck = $con->prepare("SELECT id FROM driver WHERE email = :email LIMIT 1");
|
||||
$stmtCheck->bindParam(':email', $encryptedEmail);
|
||||
$stmtCheck->execute();
|
||||
if (!$stmtCheck->fetch()) {
|
||||
$driverId = 'tester_driver_id_2026';
|
||||
$phone = '+962790000002';
|
||||
$hashedPassword = password_hash('SiroDriver2026!', PASSWORD_DEFAULT);
|
||||
|
||||
$encryptedPhone = $encryptionHelper->encryptData($phone);
|
||||
$encryptedFirstName = $encryptionHelper->encryptData('Driver');
|
||||
$encryptedLastName = $encryptionHelper->encryptData('Tester');
|
||||
$encryptedGender = $encryptionHelper->encryptData('Male');
|
||||
$encryptedBirthdate = $encryptionHelper->encryptData('1990-01-01');
|
||||
$encryptedSite = $encryptionHelper->encryptData('Jordan');
|
||||
|
||||
// Insert driver
|
||||
$insert = $con->prepare("INSERT INTO driver (id, phone, email, password, gender, birthdate, site, first_name, last_name)
|
||||
VALUES (:id, :phone, :email, :password, :gender, :birthdate, :site, :first_name, :last_name)");
|
||||
$insert->execute([
|
||||
':id' => $driverId,
|
||||
':phone' => $encryptedPhone,
|
||||
':email' => $encryptedEmail,
|
||||
':password' => $hashedPassword,
|
||||
':gender' => $encryptedGender,
|
||||
':birthdate' => $encryptedBirthdate,
|
||||
':site' => $encryptedSite,
|
||||
':first_name' => $encryptedFirstName,
|
||||
':last_name' => $encryptedLastName
|
||||
]);
|
||||
|
||||
// Ensure phone_verification row exists
|
||||
$stmtPhone = $con->prepare("SELECT * FROM phone_verification WHERE phone_number = :phone LIMIT 1");
|
||||
$stmtPhone->bindParam(':phone', $encryptedPhone);
|
||||
$stmtPhone->execute();
|
||||
if (!$stmtPhone->fetch()) {
|
||||
$insertPhone = $con->prepare("INSERT INTO phone_verification (phone_number, is_verified) VALUES (:phone, 1)");
|
||||
$insertPhone->bindParam(':phone', $encryptedPhone);
|
||||
$insertPhone->execute();
|
||||
} else {
|
||||
$updatePhone = $con->prepare("UPDATE phone_verification SET is_verified = 1 WHERE phone_number = :phone");
|
||||
$updatePhone->bindParam(':phone', $encryptedPhone);
|
||||
$updatePhone->execute();
|
||||
}
|
||||
|
||||
// Ensure CarRegistration row exists
|
||||
$stmtCar = $con->prepare("SELECT * FROM CarRegistration WHERE driverID = :driverID LIMIT 1");
|
||||
$stmtCar->bindParam(':driverID', $driverId);
|
||||
$stmtCar->execute();
|
||||
if (!$stmtCar->fetch()) {
|
||||
$insertCar = $con->prepare("INSERT INTO CarRegistration (driverID, vin, car_plate, make, model, year, expiration_date, color, owner, color_hex, fuel)
|
||||
VALUES (:driverID, :vin, :car_plate, 'Toyota', 'Prius', 2020, '2030-01-01', 'White', :owner, '#FFFFFF', 'Petrol')");
|
||||
$encryptedVin = $encryptionHelper->encryptData('TESTVIN1234567890');
|
||||
$encryptedPlate = $encryptionHelper->encryptData('155186');
|
||||
$encryptedOwner = $encryptionHelper->encryptData('Driver Tester');
|
||||
$insertCar->execute([
|
||||
':driverID' => $driverId,
|
||||
':vin' => $encryptedVin,
|
||||
':car_plate' => $encryptedPlate,
|
||||
':owner' => $encryptedOwner
|
||||
]);
|
||||
} else {
|
||||
$updateCar = $con->prepare("UPDATE CarRegistration SET make = 'Toyota', model = 'Prius', year = 2020 WHERE driverID = :driverID");
|
||||
$updateCar->bindParam(':driverID', $driverId);
|
||||
$updateCar->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر
|
||||
$sql = "SELECT
|
||||
driver.*,
|
||||
phone_verification.is_verified,
|
||||
CarRegistration.make,
|
||||
CarRegistration.model,
|
||||
CarRegistration.year
|
||||
FROM driver
|
||||
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone
|
||||
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
|
||||
WHERE
|
||||
driver.email = :email
|
||||
LIMIT 1";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':email', $encryptedEmail);
|
||||
$stmt->execute();
|
||||
|
||||
$data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($data) {
|
||||
// التحقق من أن الحساب معلم كحساب فحص في قاعدة البيانات أو البيئة
|
||||
$isTestInDb = (isset($data['is_test']) && $data['is_test'] == 1) || (isset($data['isTest']) && $data['isTest'] == 1);
|
||||
if (!$isTestInDb && !$isTester) {
|
||||
jsonError("Access denied. Not a tester account.");
|
||||
exit();
|
||||
}
|
||||
// فحص الباسورد (في نظامنا، يمكن أن يكون الباسورد هو HMAC أو نص عادي للفاحصين)
|
||||
// لنفترض أن الفاحص له باسورد عادي أو مشفر بـ bcrypt
|
||||
if (password_verify($password, $data['password']) || $password === $data['password']) {
|
||||
unset($data['password']);
|
||||
|
||||
// فك تشفير الحقول الحساسة
|
||||
$data['phone'] = $encryptionHelper->decryptData($data['phone']);
|
||||
$data['email'] = $encryptionHelper->decryptData($data['email']);
|
||||
$data['gender'] = $encryptionHelper->decryptData($data['gender']);
|
||||
$data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']);
|
||||
$data['site'] = $encryptionHelper->decryptData($data['site']);
|
||||
$data['first_name'] = $encryptionHelper->decryptData($data['first_name']);
|
||||
$data['last_name'] = $encryptionHelper->decryptData($data['last_name']);
|
||||
if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']);
|
||||
if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']);
|
||||
|
||||
// توليد الـ JWT بصلاحية (tester) لتميزهم عن السائقين الفعليين
|
||||
$jwtService = new JwtService($redis);
|
||||
$jwt = $jwtService->generateAccessToken($data['id'], 'tester', $audience, $fingerprint);
|
||||
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
"jwt" => $jwt,
|
||||
"data" => [$data] // مطابق لنسق التطبيق الذي يتوقع مصفوفة
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
jsonError("Incorrect password.");
|
||||
}
|
||||
} else {
|
||||
jsonError("User does not exist.");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[Tester Login Error] " . $e->getMessage());
|
||||
jsonError("Server error occurred.");
|
||||
} finally {
|
||||
$stmt = null;
|
||||
$con = null;
|
||||
}
|
||||
exit();
|
||||
?>
|
||||
@@ -0,0 +1,607 @@
|
||||
<?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');
|
||||
|
||||
// Rate Limiting: الحماية من التسجيل العشوائي وهجمات الـ Bots
|
||||
$rateLimiter = new RateLimiter($redis);
|
||||
$rateLimiter->enforce(RateLimiter::identifier(), 'register_driver');
|
||||
|
||||
|
||||
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 🔴 ================== */
|
||||
|
||||
// ======================================================
|
||||
// Step 1.5: التحقق الفعلي من ملكية رقم الهاتف قبل إكمال المعالجة (سد الثغرة)
|
||||
// ======================================================
|
||||
require_once __DIR__ . '/../../../core/Auth/EncryptionHelper.php';
|
||||
$tempEncryptionHelper = new EncryptionHelper($redis);
|
||||
$phoneNumber_encrypted_check = $tempEncryptionHelper->encryptData($data['phone']);
|
||||
|
||||
$verifyCheckStmt = $con->prepare(
|
||||
"SELECT id FROM phone_verification_driver
|
||||
WHERE phone_number = ? AND verified = 1 AND created_at > DATE_SUB(NOW(), INTERVAL 30 MINUTE)
|
||||
LIMIT 1"
|
||||
);
|
||||
$verifyCheckStmt->execute([$phoneNumber_encrypted_check]);
|
||||
if ($verifyCheckStmt->rowCount() === 0) {
|
||||
error_log("[Register_Debug_driver] Error: Phone number not verified via OTP.");
|
||||
jsonError("Phone number must be verified before registration.");
|
||||
exit();
|
||||
}
|
||||
// ======================================================
|
||||
|
||||
// تجهيز تاريخ الميلاد قبل التشفير
|
||||
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 ================== */
|
||||
$userInputJson = json_encode([
|
||||
'driver' => $data,
|
||||
'car' => $car,
|
||||
]);
|
||||
|
||||
/* ================== AI PROCESSING START ================== */
|
||||
$apiKey = getenv("GEMINI_API_KEY");
|
||||
$aiRawText = null;
|
||||
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);
|
||||
$aiRawText = ($json !== null && $json !== false) ? $textRaw : null; // فقط إذا كان JSON صحيح
|
||||
|
||||
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) Hash password (HMAC + password_hash) ================== */
|
||||
// 🔴 مهم: يجب أن يكون قبل التشفير - يستخدم القيم الخام
|
||||
$pepper = getenv('SECRET_KEY_HMAC');
|
||||
$baseParts = [
|
||||
$data['id'],
|
||||
$data['phone'],
|
||||
];
|
||||
if (!empty($data['national_number'])) {
|
||||
$baseParts[] = $data['national_number'];
|
||||
} elseif (!empty($data['birthdate'])) {
|
||||
$year = substr($data['birthdate'], 0, 4);
|
||||
if (preg_match('/^\d{4}$/', $year)) {
|
||||
$baseParts[] = $year;
|
||||
}
|
||||
}
|
||||
$baseString = implode('|', $baseParts);
|
||||
$rawSecret = hash_hmac('sha256', $baseString, $pepper, true);
|
||||
$pwdHashed = password_hash($rawSecret, PASSWORD_DEFAULT);
|
||||
|
||||
/* ================== 4) 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']);
|
||||
|
||||
/* ================== 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, ai_data, user_input, 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, :ai_data, :user_input, :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' => 'pending_review',
|
||||
':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',
|
||||
':ai_data' => $aiRawText ?: null,
|
||||
':user_input' => $userInputJson ?: null,
|
||||
':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();
|
||||
}
|
||||
$msg = $e->getMessage();
|
||||
error_log("register_driver_and_car ERROR: " . $msg);
|
||||
jsonError("Server error: $msg", 400);
|
||||
} 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: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$id = filterRequest("id");
|
||||
|
||||
$sql = "DELETE FROM `passengers` WHERE `id` = :id";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
jsonSuccess(null, "Passenger deleted successfully.");
|
||||
} else {
|
||||
jsonError("Failed to delete passenger.");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// File: send_otp_driver.php (إصدار بدون RaseelPlus)
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
/* 1) توليد رمز التحقق (3 خانات) --------------------------------------------------- */
|
||||
$otp = (string)random_int(100, 999);
|
||||
$receiver = filterRequest("receiver");
|
||||
|
||||
if (empty($receiver)) {
|
||||
jsonError('Phone number is required.');
|
||||
exit();
|
||||
}
|
||||
|
||||
/* 2) إرسال عبر بوابة الفلاش كول / واتساب ------------------------------ */
|
||||
$nabehUrl = 'https://otp.intaleqapp.com/api/request-otp.php';
|
||||
$appKey = getenv('NABEH_OTP_APP_KEY');
|
||||
|
||||
$payload = [
|
||||
'phone' => $receiver,
|
||||
'device_type' => 'android',
|
||||
'method' => 'whatsapp',
|
||||
'code' => $otp
|
||||
];
|
||||
|
||||
$ch = curl_init($nabehUrl);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
"X-App-Key: $appKey"
|
||||
],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 5
|
||||
]);
|
||||
|
||||
$res = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
error_log("⚠️ [Flash Call OTP Token Driver] Curl Error: $error");
|
||||
jsonError('Failed to connect to OTP service');
|
||||
exit;
|
||||
}
|
||||
|
||||
$decoded = json_decode((string)$res, true);
|
||||
$sentOK = ($httpCode === 200 && ($decoded['success'] ?? false));
|
||||
|
||||
if ($sentOK) {
|
||||
/* 3) تشفير البيانات وحفظها في DB ----------------------------------- */
|
||||
$receiver_enc = $encryptionHelper->encryptData($receiver);
|
||||
$otp_enc = $encryptionHelper->encryptData($otp);
|
||||
|
||||
$exp = date('Y-m-d H:i:s', strtotime('+5 minutes'));
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
try {
|
||||
// حذف رموز قديمة
|
||||
$con->prepare("DELETE FROM token_verification_driver WHERE phone_number = ?")
|
||||
->execute([$receiver_enc]);
|
||||
|
||||
$stmt = $con->prepare("
|
||||
INSERT INTO token_verification_driver
|
||||
(phone_number, token, expiration_time, verified, created_at)
|
||||
VALUES (?, ?, ?, 0, ?)
|
||||
");
|
||||
$stmt->execute([$receiver_enc, $otp_enc, $exp, $now]);
|
||||
|
||||
// Also save to Redis for verify_otp compatibility
|
||||
if ($redis) {
|
||||
$redis->setex("otp:driver:$receiver", 300, $otp);
|
||||
}
|
||||
|
||||
jsonSuccess(null, 'OTP sent and saved successfully');
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("[send_otp_driver.php] " . $e->getMessage());
|
||||
jsonError('OTP sent but failed to save to database');
|
||||
}
|
||||
|
||||
} else {
|
||||
jsonError('Failed to send OTP');
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
$phoneNumber = filterRequest("phone_number");
|
||||
$otp = filterRequest("otp");
|
||||
|
||||
if (empty($phoneNumber) || empty($otp)) {
|
||||
jsonError("Phone number and OTP are required.");
|
||||
exit();
|
||||
}
|
||||
|
||||
$phoneNumber_encrypted = $encryptionHelper->encryptData($phoneNumber);
|
||||
$otp_encrypted = $encryptionHelper->encryptData($otp);
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare("
|
||||
SELECT * FROM token_verification_driver
|
||||
WHERE phone_number = ? AND token = ?
|
||||
");
|
||||
$stmt->execute([$phoneNumber_encrypted, $otp_encrypted]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result) {
|
||||
$expiration_time = strtotime($result['expiration_time']);
|
||||
|
||||
if (time() <= $expiration_time) {
|
||||
$con->prepare("UPDATE token_verification_driver SET verified = 1 WHERE id = ?")
|
||||
->execute([$result['id']]);
|
||||
|
||||
$driverStmt = $con->prepare("SELECT id FROM driver WHERE phone = ?");
|
||||
$driverStmt->execute([$phoneNumber_encrypted]);
|
||||
$driver = $driverStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($driver) {
|
||||
$driverID = $driver['id'];
|
||||
$newToken = filterRequest("token");
|
||||
$fingerPrint = filterRequest("fingerPrint");
|
||||
|
||||
if ($newToken && $fingerPrint) {
|
||||
$tokenEncrypted = $encryptionHelper->encryptData($newToken);
|
||||
|
||||
$checkTokenStmt = $con->prepare("SELECT id FROM driverToken WHERE captain_id = ?");
|
||||
$checkTokenStmt->execute([$driverID]);
|
||||
|
||||
if ($checkTokenStmt->rowCount() > 0) {
|
||||
$con->prepare("UPDATE driverToken SET token = ?, fingerPrint = ? WHERE captain_id = ?")
|
||||
->execute([$tokenEncrypted, $fingerPrint, $driverID]);
|
||||
} else {
|
||||
$con->prepare("INSERT INTO driverToken (token, fingerPrint, captain_id, created_at) VALUES (?, ?, ?, NOW())")
|
||||
->execute([$tokenEncrypted, $fingerPrint, $driverID]);
|
||||
}
|
||||
|
||||
$response = [
|
||||
"message" => "Driver token verified and updated.",
|
||||
"isRegistered" => true,
|
||||
"driverID" => $driverID
|
||||
];
|
||||
jsonSuccess($response);
|
||||
|
||||
} else {
|
||||
jsonError("Token or fingerprint missing.");
|
||||
}
|
||||
|
||||
} else {
|
||||
printSuccess([
|
||||
"message" => "Phone verified, but driver not found.",
|
||||
"isRegistered" => false
|
||||
]);
|
||||
}
|
||||
|
||||
} else {
|
||||
jsonError("OTP expired. Request a new one.");
|
||||
}
|
||||
|
||||
} else {
|
||||
jsonError("Invalid OTP.");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("[verify_otp_driver.php] " . $e->getMessage());
|
||||
jsonError("Database error occurred.");
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$id = filterRequest("id");
|
||||
$columnValues = [];
|
||||
$params = [':id' => $id];
|
||||
|
||||
// الحقول التي تحتاج تشفير
|
||||
$fieldsToEncrypt = [
|
||||
"phone", "email", "gender", "birthdate", "site",
|
||||
"first_name", "last_name", "accountBank", "education",
|
||||
"employmentType", "maritalStatus"
|
||||
];
|
||||
|
||||
// الحقول غير المشفرة
|
||||
$plainFields = ["status", "bankCode", "updated_at"];
|
||||
|
||||
foreach ($_POST as $key => $value) {
|
||||
$filtered = filterRequest($key);
|
||||
|
||||
if ($key === "password") {
|
||||
// هاش لكلمة المرور
|
||||
$hashed = password_hash($filtered, PASSWORD_DEFAULT);
|
||||
$columnValues[] = "`password` = :password";
|
||||
$params[':password'] = $hashed;
|
||||
} elseif (in_array($key, $fieldsToEncrypt)) {
|
||||
$encrypted = $encryptionHelper->encryptData($filtered);
|
||||
$columnValues[] = "`$key` = :$key";
|
||||
$params[":$key"] = $encrypted;
|
||||
} elseif (in_array($key, $plainFields)) {
|
||||
$columnValues[] = "`$key` = :$key";
|
||||
$params[":$key"] = $filtered;
|
||||
}
|
||||
}
|
||||
|
||||
// بناء جملة التحديث
|
||||
if (empty($columnValues)) {
|
||||
jsonError("No data provided to update.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$setClause = implode(", ", $columnValues);
|
||||
$sql = "UPDATE `driver` SET $setClause WHERE `id` = :id";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
jsonSuccess(null, "Driver data updated successfully");
|
||||
} else {
|
||||
jsonError("Failed to update driver data");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// Sanitize and validate input
|
||||
$driverId = filterRequest("driverId");
|
||||
|
||||
// SQL query to check if a gift already exists for the driver (unclaimed)
|
||||
$checkSql = "SELECT COUNT(*) FROM driver_gifts WHERE driver_id = :driverId -- AND is_claimed = 0";
|
||||
|
||||
try {
|
||||
$checkStmt = $con->prepare($checkSql);
|
||||
$checkStmt->bindParam(':driverId', $driverId, PDO::PARAM_INT);
|
||||
$checkStmt->execute();
|
||||
$giftExists = $checkStmt->fetchColumn();
|
||||
|
||||
if ($giftExists > 0) {
|
||||
jsonError("Gift already exists for this driver");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Insert a new claimed gift
|
||||
$sql = "INSERT INTO driver_gifts (driver_id, gift_description, is_claimed)
|
||||
VALUES (:driverId, 'new account 300 le', 1)";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':driverId', $driverId, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
jsonSuccess(null, "Gift data saved successfully");
|
||||
} else {
|
||||
jsonError("Failed to save gift data");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Database Error: " . $e->getMessage());
|
||||
jsonError("An error occurred while saving the data");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// استقبال معرف السائق
|
||||
$id = filterRequest("id");
|
||||
|
||||
// استقبال بيانات شام كاش من التطبيق
|
||||
$accountBank = filterRequest("accountBank"); // الاسم (مثال: siro)
|
||||
$bankCode = filterRequest("bankCode"); // الكود الطويل (مثال: 80f23afe...)
|
||||
|
||||
// التحقق من وصول البيانات المطلوبة
|
||||
if ($id && $accountBank && $bankCode) {
|
||||
|
||||
try {
|
||||
// 1. تشفير اسم الحساب (حسب القواعد في السكربت السابق accountBank مشفر)
|
||||
$encryptedAccountBank = $encryptionHelper->encryptData($accountBank);
|
||||
|
||||
// 2. كود المحفظة يبقى كما هو (حسب القواعد bankCode غير مشفر)
|
||||
$plainBankCode = $encryptionHelper->encryptData($bankCode);
|
||||
|
||||
// 3. جملة التحديث
|
||||
$stmt = $con->prepare("UPDATE `driver` SET `accountBank` = ?, `bankCode` = ? WHERE `id` = ?");
|
||||
|
||||
$stmt->execute(array($encryptedAccountBank, $plainBankCode, $id));
|
||||
|
||||
// التحقق من نجاح العملية
|
||||
// rowCount > 0 يعني تم التحديث، أحياناً يعطي 0 إذا كانت البيانات هي نفسها لم تتغير
|
||||
// لذا نرسل نجاح في كلتا الحالتين طالما لم يحدث Error
|
||||
jsonSuccess(null, "ShamCash info updated successfully");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// في حال وجود خطأ في قاعدة البيانات
|
||||
error_log("[updateShamCashDriver] " . $e->getMessage());
|
||||
jsonError("Database Error");
|
||||
}
|
||||
|
||||
} else {
|
||||
jsonError("Missing required fields: id, accountBank, or bankCode");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
// File: upload_serial_document.php
|
||||
// يرفع صورة وثيقة إلى مسار خاص (خارج الويب العام) ويُرجع Signed URL مؤقّت.
|
||||
|
||||
// بيئتك
|
||||
require_once __DIR__ . '/../../connect.php'; // يجب أن يوفّر: $con (اختياري) + printSuccess/printFailure + filterRequest
|
||||
|
||||
// --------- إعدادات ---------
|
||||
const MAX_FILE_MB = 5;
|
||||
const ALLOWED_MIMES = ['image/jpeg','image/png','image/webp']; // فقط صور
|
||||
const UPLOAD_ROOT = __DIR__ . "/../../private_uploads"; // مجلد خاص (غير عام)
|
||||
$SIGN_SECRET = getenv('SECRET_KEY_HMAC'); // غيّرها واقرأها من .env
|
||||
$host = getenv('APP_DOMAIN');
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
|
||||
define('PUBLIC_BASE', "$protocol://$host/siro");
|
||||
const SIGNED_TTL_SEC = 172800; // 2 days = 60*60*24
|
||||
|
||||
// أنشئ مجلد الرفع إن لم يكن موجودًا
|
||||
if (!is_dir(UPLOAD_ROOT)) { @mkdir(UPLOAD_ROOT, 0700, true); }
|
||||
|
||||
// Log entry
|
||||
uploadLog("🚀 [uploadSyrianDocs.php] Document upload script started.");
|
||||
|
||||
// (اختياري) هيدرز أمان
|
||||
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
$hmacHeader = $_SERVER['HTTP_X_HMAC_AUTH'] ?? '';
|
||||
// TODO: تحقّق حسب منطقك إن أردت فرض المصادقة هنا.
|
||||
|
||||
// --------- حقول مطلوبة من Flutter عبر filterRequest ---------
|
||||
$driverId = filterRequest('driver_id');
|
||||
$docType = filterRequest('doc_type');
|
||||
$purpose = filterRequest('purpose'); // اختياري
|
||||
|
||||
uploadLog("📥 Request params: driver_id=$driverId, doc_type=$docType");
|
||||
|
||||
if (empty($driverId) || empty($docType)) {
|
||||
uploadLog("❌ Missing driver_id or doc_type params.", 'ERROR');
|
||||
jsonError("driver_id and doc_type are required.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// اسمح فقط بقيم محددة للوثائق
|
||||
$allowedDocTypes = [
|
||||
'driver_license_front',
|
||||
'driver_license_back',
|
||||
'car_license_front',
|
||||
'car_license_back',
|
||||
];
|
||||
if (!in_array($docType, $allowedDocTypes, true)) {
|
||||
uploadLog("❌ Invalid doc_type value: $docType", 'ERROR');
|
||||
jsonError("Invalid doc_type.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --------- التحقق من الملف ---------
|
||||
if (isset($_FILES['file'])) {
|
||||
uploadLog('$_FILES[\'file\'] metadata', 'INFO', [
|
||||
'name' => $_FILES['file']['name'] ?? 'unknown',
|
||||
'type' => $_FILES['file']['type'] ?? 'unknown',
|
||||
'size' => $_FILES['file']['size'] ?? 0,
|
||||
'upload_error_code' => $_FILES['file']['error'] ?? UPLOAD_ERR_OK
|
||||
]);
|
||||
} else {
|
||||
uploadLog("No 'file' payload was sent in the request.", 'WARNING');
|
||||
}
|
||||
|
||||
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||
$err = $_FILES['file']['error'] ?? 'missing_file';
|
||||
uploadLog("❌ File upload validation failed. Code: $err", 'ERROR');
|
||||
jsonError("No file uploaded or upload error.");
|
||||
exit;
|
||||
}
|
||||
$tmpPath = $_FILES['file']['tmp_name'];
|
||||
$origName = $_FILES['file']['name'] ?? 'upload.bin';
|
||||
$size = filesize($tmpPath);
|
||||
if ($size === false || $size <= 0) {
|
||||
jsonError("Invalid file size."); exit;
|
||||
}
|
||||
if ($size > MAX_FILE_MB * 1024 * 1024) {
|
||||
jsonError("File too large. Max " . MAX_FILE_MB . " MB."); exit;
|
||||
}
|
||||
|
||||
// MIME دقيق
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($tmpPath) ?: 'application/octet-stream';
|
||||
if (!in_array($mime, ALLOWED_MIMES, true)) {
|
||||
jsonError("Unsupported file type: $mime"); exit;
|
||||
}
|
||||
|
||||
// لاحقة الامتداد
|
||||
$extMap = [
|
||||
'image/jpeg' => '.jpg',
|
||||
'image/png' => '.png',
|
||||
'image/webp' => '.webp',
|
||||
];
|
||||
$ext = $extMap[$mime];
|
||||
|
||||
// --------- توليد مسار حتمي بدون تاريخ ---------
|
||||
// تنظيف driver_id لاسم ملف آمن
|
||||
$driverIdSafe = preg_replace('/[^A-Za-z0-9_\-]/', '_', $driverId);
|
||||
// شجرة مجلدات ثابتة من hash(driver_id) لتوزيع الملفات
|
||||
$h = hash('sha1', $driverIdSafe);
|
||||
$subdir = substr($h, 0, 2) . '/' . substr($h, 2, 2);
|
||||
$destDir = UPLOAD_ROOT . '/' . $subdir;
|
||||
if (!is_dir($destDir)) { @mkdir($destDir, 0700, true); }
|
||||
|
||||
// الاسم النهائي بدون تاريخ
|
||||
$serverName = "{$driverIdSafe}__{$docType}{$ext}";
|
||||
$destPath = $destDir . '/' . $serverName;
|
||||
|
||||
// استبدال أي نسخة قديمة عن قصد (overwrite) - مع حماية ضد path traversal
|
||||
$resolvedDest = realpath($destPath) ?: $destPath;
|
||||
$resolvedRoot = realpath(UPLOAD_ROOT) ?: UPLOAD_ROOT;
|
||||
|
||||
if (is_file($destPath) && str_starts_with($resolvedDest, $resolvedRoot)) {
|
||||
@unlink($destPath);
|
||||
}
|
||||
|
||||
// نقل الملف
|
||||
if (!move_uploaded_file($tmpPath, $destPath)) {
|
||||
jsonError("Failed to save the uploaded file.");
|
||||
exit;
|
||||
}
|
||||
@chmod($destPath, 0600);
|
||||
|
||||
// --------- Signed URL ---------
|
||||
// سنضمّن driver_id و doc_type و ext في الرابط والتوقيع.
|
||||
// ext بدون النقطة
|
||||
$extShort = ltrim($ext, '.');
|
||||
$expires = time() + SIGNED_TTL_SEC;
|
||||
|
||||
// الرسالة الموقّعة: driver_id:doc_type:ext:expires
|
||||
$message = $driverIdSafe . ':' . $docType . ':' . $extShort . ':' . $expires;
|
||||
$signature = hash_hmac('sha256', $message, SIGN_SECRET);
|
||||
|
||||
// رابط القراءة عبر البوابة الآمنة فقط
|
||||
// ملاحظة: لا نُرجع المسار الحقيقي، فقط معطيات موقّعة
|
||||
$fileUrl = PUBLIC_BASE . "/secure_image.php"
|
||||
. "?driver_id={$driverIdSafe}"
|
||||
. "&doc_type={$docType}"
|
||||
. "&ext={$extShort}"
|
||||
. "&expires={$expires}"
|
||||
. "&signature={$signature}";
|
||||
|
||||
// --------- استجابة ---------
|
||||
uploadLog("✅ Document upload succeeded. URL: $fileUrl");
|
||||
printSuccess([
|
||||
"status" => "success",
|
||||
"success_file" => true,
|
||||
"file_url" => $fileUrl,
|
||||
"file_name" => $serverName, // الاسم الفعلي المحفوظ
|
||||
"driver_id" => $driverIdSafe,
|
||||
"doc_type" => $docType,
|
||||
"mime_type" => $mime,
|
||||
"size_bytes" => $size,
|
||||
"expires_at" => date('c', $expires)
|
||||
]);
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// auth/syria/uploadImage.php
|
||||
// رفع صور وثائق السائق (هوية، رخصة، صورة شخصية، ...)
|
||||
// يخزّنها في مجلدات حسب الدولة: auth/uploads/{country}/
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
uploadLog("🚀 [uploadImage.php] Document upload started.");
|
||||
|
||||
// --------- قراءة الحقول ---------
|
||||
$driverID = filterRequest('driverID');
|
||||
$imageType = filterRequest('imageType');
|
||||
$country = filterRequest('country');
|
||||
|
||||
if (empty($imageType)) {
|
||||
jsonError('imageType is required.');
|
||||
}
|
||||
|
||||
// --------- تحديد الدولة ---------
|
||||
$country = strtolower(trim($country ?: ''));
|
||||
if (empty($country)) {
|
||||
if (!empty($driverID)) {
|
||||
try {
|
||||
$stmt = $con->prepare("SELECT country FROM drivers WHERE id = ?");
|
||||
$stmt->execute([$driverID]);
|
||||
$country = strtolower(trim((string)$stmt->fetchColumn()));
|
||||
} catch (Exception $e) {
|
||||
$country = '';
|
||||
}
|
||||
}
|
||||
if (empty($country)) {
|
||||
$country = 'jordan';
|
||||
}
|
||||
}
|
||||
if (!in_array($country, ['syria', 'jordan', 'egypt'])) {
|
||||
$country = 'jordan';
|
||||
}
|
||||
|
||||
// --------- بادئة اسم الملف ---------
|
||||
$prefix = !empty($driverID) ? $driverID : 'unregistered';
|
||||
|
||||
uploadLog("📥 Params: driverID=" . ($driverID ?: 'null') . ", imageType=$imageType, country=$country");
|
||||
|
||||
// --------- رفع الملف ---------
|
||||
$targetDir = __DIR__ . "/../../auth/uploads/{$country}/";
|
||||
$result = uploadImageSecure('image', $targetDir, $prefix . '_' . $imageType);
|
||||
|
||||
if (!$result['success']) {
|
||||
uploadLog("❌ Upload failed: {$result['error']}", 'ERROR', [
|
||||
'driverID' => $driverID,
|
||||
'imageType' => $imageType,
|
||||
'country' => $country,
|
||||
]);
|
||||
jsonError($result['error']);
|
||||
}
|
||||
|
||||
// --------- إزالة العشوائية من اسم الملف ---------
|
||||
// الاسم يكون فقط: {driverID}_{imageType}.jpg (بدون random hex)
|
||||
$ext = pathinfo($result['filename'], PATHINFO_EXTENSION);
|
||||
$simpleName = $prefix . '_' . $imageType . '.' . $ext;
|
||||
$simplePath = rtrim($targetDir, '/') . '/' . $simpleName;
|
||||
|
||||
if (file_exists($simplePath)) {
|
||||
unlink($simplePath); // overwrite
|
||||
}
|
||||
rename($result['path'], $simplePath);
|
||||
|
||||
$result['filename'] = $simpleName;
|
||||
$result['path'] = $simplePath;
|
||||
|
||||
// --------- بناء الرابط العام ---------
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? (getenv('APP_DOMAIN') ?: 'api.siromove.com');
|
||||
|
||||
$basePath = rtrim(dirname(dirname(dirname($_SERVER['SCRIPT_NAME']))), '/');
|
||||
$url = "$protocol://$host{$basePath}/auth/uploads/{$country}/{$result['filename']}";
|
||||
|
||||
uploadLog("✅ Uploaded: {$result['path']} -> $url", 'INFO', [
|
||||
'driverID' => ($driverID ?: 'null'),
|
||||
'imageType' => $imageType,
|
||||
'country' => $country,
|
||||
]);
|
||||
|
||||
jsonSuccess([
|
||||
'url' => $url,
|
||||
'file_link' => $url,
|
||||
'filename' => $result['filename'],
|
||||
'driverID' => ($driverID ?: ''),
|
||||
'imageType' => $imageType,
|
||||
'country' => $country,
|
||||
]);
|
||||
Reference in New Issue
Block a user