first commit
This commit is contained in:
5
backend/.gitignore
vendored
Normal file
5
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.DS_Store
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
.gemini/
|
||||||
|
portrate_captain_image/
|
||||||
0
backend/Admin/AdminCaptain/add.php
Normal file
0
backend/Admin/AdminCaptain/add.php
Normal file
0
backend/Admin/AdminCaptain/delete.php
Normal file
0
backend/Admin/AdminCaptain/delete.php
Normal file
0
backend/Admin/AdminCaptain/error_log
Normal file
0
backend/Admin/AdminCaptain/error_log
Normal file
75
backend/Admin/AdminCaptain/get.php
Normal file
75
backend/Admin/AdminCaptain/get.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
`driver`.`id`,
|
||||||
|
`driver`.`phone`,
|
||||||
|
`driver`.`email`,
|
||||||
|
`driver`.`gender`,
|
||||||
|
`driver`.`status`,
|
||||||
|
`driver`.`birthdate`,
|
||||||
|
`driver`.`site`,
|
||||||
|
`driver`.`first_name`,
|
||||||
|
`driver`.`last_name`,
|
||||||
|
`driver`.`employmentType`,
|
||||||
|
`driver`.`maritalStatus`,
|
||||||
|
`driver`.`created_at`,
|
||||||
|
`driver`.`updated_at`,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`driver`.`id`) FROM `driver`
|
||||||
|
) AS countPassenger,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10, 2))
|
||||||
|
FROM `ratingPassenger`
|
||||||
|
WHERE `ratingPassenger`.`driverID` = `driver`.`id`
|
||||||
|
) AS ratingPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ratingPassenger` WHERE `driverID` = `driver`.`id`
|
||||||
|
) AS countDriverRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `canecl` WHERE `driverID` = `driver`.`id`
|
||||||
|
) AS countPassengerCancel,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10, 2))
|
||||||
|
FROM `ratingDriver`
|
||||||
|
WHERE `driver_id` = `driver`.`id`
|
||||||
|
) AS passengerAverageRating,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ratingDriver` WHERE `driver_id` = `driver`.`id`
|
||||||
|
) AS countPassengerRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ride` WHERE `driver_id` = `driver`.`id`
|
||||||
|
) AS countPassengerRide,
|
||||||
|
(
|
||||||
|
SELECT `token`
|
||||||
|
FROM `driverToken`
|
||||||
|
WHERE `captain_id` = `driver`.`id`
|
||||||
|
LIMIT 1
|
||||||
|
) AS passengerToken
|
||||||
|
FROM `driver`
|
||||||
|
ORDER BY passengerAverageRating DESC
|
||||||
|
LIMIT 10";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك تشفير الحقول الحساسة
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||||
|
$row['email'] = $encryptionHelper->decryptData($row['email']);
|
||||||
|
$row['gender'] = $encryptionHelper->decryptData($row['gender']);
|
||||||
|
$row['birthdate'] = $encryptionHelper->decryptData($row['birthdate']);
|
||||||
|
$row['site'] = $encryptionHelper->decryptData($row['site']);
|
||||||
|
$row['first_name'] = $encryptionHelper->decryptData($row['first_name']);
|
||||||
|
$row['last_name'] = $encryptionHelper->decryptData($row['last_name']);
|
||||||
|
$row['employmentType'] = $encryptionHelper->decryptData($row['employmentType']);
|
||||||
|
$row['maritalStatus'] = $encryptionHelper->decryptData($row['maritalStatus']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($result) > 0) {
|
||||||
|
jsonSuccess($result);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$driver_id = filterRequest("driver_id");
|
||||||
|
$driverEmail = $encryptionHelper->encryptData(filterRequest("driverEmail"));
|
||||||
|
$driverPhone = $encryptionHelper->encryptData(filterRequest("driverPhone"));
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
`driver`.`id`,
|
||||||
|
`driver`.`phone`,
|
||||||
|
`driver`.`email`,
|
||||||
|
`driver`.`gender`,
|
||||||
|
`driver`.`status`,
|
||||||
|
`driver`.`birthdate`,
|
||||||
|
`driver`.`site`,
|
||||||
|
`driver`.`first_name`,
|
||||||
|
`driver`.`last_name`,
|
||||||
|
`driver`.`education`,
|
||||||
|
`driver`.`employmentType`,
|
||||||
|
`driver`.`maritalStatus`,
|
||||||
|
`driver`.`created_at`,
|
||||||
|
`driver`.`updated_at`,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `driver`
|
||||||
|
) AS countPassenger,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10, 2))
|
||||||
|
FROM `ratingPassenger`
|
||||||
|
WHERE `ratingPassenger`.`driverID` = `driver`.`id`
|
||||||
|
) AS ratingPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ratingPassenger` WHERE `driverID` = `driver`.`id`
|
||||||
|
) AS countDriverRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `canecl` WHERE `driverID` = `driver`.`id`
|
||||||
|
) AS countPassengerCancel,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10, 2))
|
||||||
|
FROM `ratingDriver`
|
||||||
|
WHERE `driver_id` = `driver`.`id`
|
||||||
|
) AS passengerAverageRating,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ratingDriver` WHERE `driver_id` = `driver`.`id`
|
||||||
|
) AS countPassengerRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ride` WHERE `driver_id` = `driver`.`id`
|
||||||
|
) AS countPassengerRide,
|
||||||
|
(
|
||||||
|
SELECT `token`
|
||||||
|
FROM `driverToken`
|
||||||
|
WHERE `captain_id` = `driver`.`id`
|
||||||
|
LIMIT 1
|
||||||
|
) AS passengerToken
|
||||||
|
FROM `driver`
|
||||||
|
WHERE `driver`.`email` = :email OR `driver`.`phone` = :phone OR `driver`.`id` = :id
|
||||||
|
ORDER BY passengerAverageRating DESC
|
||||||
|
LIMIT 10
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->bindParam(":email", $driverEmail);
|
||||||
|
$stmt->bindParam(":phone", $driverPhone);
|
||||||
|
$stmt->bindParam(":id", $driver_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك تشفير الحقول الحساسة
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||||
|
$row['email'] = $encryptionHelper->decryptData($row['email']);
|
||||||
|
$row['gender'] = $encryptionHelper->decryptData($row['gender']);
|
||||||
|
$row['birthdate'] = $encryptionHelper->decryptData($row['birthdate']);
|
||||||
|
$row['site'] = $encryptionHelper->decryptData($row['site']);
|
||||||
|
$row['first_name'] = $encryptionHelper->decryptData($row['first_name']);
|
||||||
|
$row['last_name'] = $encryptionHelper->decryptData($row['last_name']);
|
||||||
|
$row['education'] = $encryptionHelper->decryptData($row['education']);
|
||||||
|
$row['employmentType'] = $encryptionHelper->decryptData($row['employmentType']);
|
||||||
|
$row['maritalStatus'] = $encryptionHelper->decryptData($row['maritalStatus']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess($result);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
87
backend/Admin/AdminCaptain/getCaptainDetailsById.php
Normal file
87
backend/Admin/AdminCaptain/getCaptainDetailsById.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
// تشفير driver_id قبل استخدامه في SQL
|
||||||
|
$driver_id = filterRequest("driver_id");
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
`driver`.`id`,
|
||||||
|
`driver`.`phone`,
|
||||||
|
`driver`.`email`,
|
||||||
|
`driver`.`gender`,
|
||||||
|
`driver`.`status`,
|
||||||
|
`driver`.`birthdate`,
|
||||||
|
`driver`.`site`,
|
||||||
|
`driver`.`first_name`,
|
||||||
|
`driver`.`last_name`,
|
||||||
|
`driver`.`education`,
|
||||||
|
`driver`.`employmentType`,
|
||||||
|
`driver`.`maritalStatus`,
|
||||||
|
`driver`.`created_at`,
|
||||||
|
`driver`.`updated_at`,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `driver`
|
||||||
|
) AS countPassenger,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10, 2))
|
||||||
|
FROM `ratingPassenger`
|
||||||
|
WHERE `ratingPassenger`.`driverID` = `driver`.`id`
|
||||||
|
) AS ratingPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ratingPassenger`
|
||||||
|
WHERE `ratingPassenger`.`driverID` = `driver`.`id`
|
||||||
|
) AS countDriverRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `canecl`
|
||||||
|
WHERE `canecl`.`driverID` = `driver`.`id`
|
||||||
|
) AS countPassengerCancel,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10, 2))
|
||||||
|
FROM `ratingDriver`
|
||||||
|
WHERE `ratingDriver`.`driver_id` = `driver`.`id`
|
||||||
|
) AS passengerAverageRating,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ratingDriver`
|
||||||
|
WHERE `ratingDriver`.`driver_id` = `driver`.`id`
|
||||||
|
) AS countPassengerRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM `ride`
|
||||||
|
WHERE `ride`.`driver_id` = `driver`.`id`
|
||||||
|
) AS countPassengerRide,
|
||||||
|
(
|
||||||
|
SELECT `token`
|
||||||
|
FROM `driverToken`
|
||||||
|
WHERE `driverToken`.`captain_id` = `driver`.`id`
|
||||||
|
LIMIT 1
|
||||||
|
) AS passengerToken
|
||||||
|
FROM `driver`
|
||||||
|
WHERE `driver`.`id` = :driver_id
|
||||||
|
ORDER BY passengerAverageRating DESC
|
||||||
|
LIMIT 10";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->bindParam(':driver_id', $driver_id);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك تشفير الحقول الحساسة بعد الجلب
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||||
|
$row['email'] = $encryptionHelper->decryptData($row['email']);
|
||||||
|
$row['gender'] = $encryptionHelper->decryptData($row['gender']);
|
||||||
|
$row['birthdate'] = $encryptionHelper->decryptData($row['birthdate']);
|
||||||
|
$row['site'] = $encryptionHelper->decryptData($row['site']);
|
||||||
|
$row['first_name'] = $encryptionHelper->decryptData($row['first_name']);
|
||||||
|
$row['last_name'] = $encryptionHelper->decryptData($row['last_name']);
|
||||||
|
$row['education'] = $encryptionHelper->decryptData($row['education']);
|
||||||
|
$row['employmentType'] = $encryptionHelper->decryptData($row['employmentType']);
|
||||||
|
$row['maritalStatus'] = $encryptionHelper->decryptData($row['maritalStatus']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess($result);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
33
backend/Admin/AdminCaptain/getDriversPhonesAndTokens.php
Executable file
33
backend/Admin/AdminCaptain/getDriversPhonesAndTokens.php
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
d.phone,
|
||||||
|
d.id,
|
||||||
|
d.name_arabic,
|
||||||
|
dt.token
|
||||||
|
FROM
|
||||||
|
`driver` d
|
||||||
|
LEFT JOIN driverToken dt ON
|
||||||
|
dt.captain_id = d.id
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك التشفير للحقول الحساسة
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||||
|
if (!empty($row['token'])) {
|
||||||
|
$row['token'] = $encryptionHelper->decryptData($row['token']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess($result);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
0
backend/Admin/AdminCaptain/update.php
Normal file
0
backend/Admin/AdminCaptain/update.php
Normal file
79
backend/Admin/AdminRide/get.php
Normal file
79
backend/Admin/AdminRide/get.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
(
|
||||||
|
SELECT TIME_FORMAT(SEC_TO_TIME(AVG(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))), '%Hh %im')
|
||||||
|
FROM ride
|
||||||
|
WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL
|
||||||
|
) AS driver_avg_duration,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM (
|
||||||
|
SELECT COUNT(driver_id) FROM ride GROUP BY driver_id
|
||||||
|
) AS sub
|
||||||
|
) AS num_Driver,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM ride
|
||||||
|
) AS total_rides,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM ride WHERE status = 'waiting'
|
||||||
|
) AS ongoing_rides,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM ride WHERE status = 'Finished'
|
||||||
|
) AS completed_rides,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM ride WHERE status = 'cancelled'
|
||||||
|
) AS cancelled_rides,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT TIME_FORMAT(SEC_TO_TIME(MAX(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))), '%Hh %im')
|
||||||
|
FROM ride
|
||||||
|
WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL
|
||||||
|
) AS longest_duration,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT ROUND(SUM(distance), 2) FROM ride
|
||||||
|
) AS total_distance,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT ROUND(AVG(distance), 2) FROM ride
|
||||||
|
) AS average_distance,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT ROUND(MAX(distance), 2) FROM ride
|
||||||
|
) AS longest_distance,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT ROUND(SUM(price_for_driver), 2) FROM ride
|
||||||
|
) AS total_driver_earnings,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT ROUND(SUM(price_for_passenger), 2) FROM ride
|
||||||
|
) AS total_company_earnings,
|
||||||
|
|
||||||
|
(
|
||||||
|
SELECT ROUND(
|
||||||
|
(SELECT SUM(price_for_passenger) FROM ride) /
|
||||||
|
NULLIF((SELECT SUM(price_for_driver) FROM ride), 0),
|
||||||
|
2
|
||||||
|
)
|
||||||
|
) AS companyPercent
|
||||||
|
|
||||||
|
FROM dual
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess($result);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
52
backend/Admin/AdminRide/getRidesPerMonth.php
Normal file
52
backend/Admin/AdminRide/getRidesPerMonth.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$currentYear = date('Y');
|
||||||
|
$currentMonth = date('m');
|
||||||
|
|
||||||
|
// SQL to get daily ride counts
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
YEAR(date) AS year,
|
||||||
|
MONTH(date) AS month,
|
||||||
|
DAY(date) AS day,
|
||||||
|
COUNT(*) AS rides_count
|
||||||
|
FROM
|
||||||
|
ride
|
||||||
|
GROUP BY
|
||||||
|
YEAR(date),
|
||||||
|
MONTH(date),
|
||||||
|
DAY(date)
|
||||||
|
ORDER BY
|
||||||
|
YEAR(date),
|
||||||
|
MONTH(date),
|
||||||
|
DAY(date)
|
||||||
|
";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$dailyRides = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// SQL to get current month's total ride count
|
||||||
|
$sqlMonth = "
|
||||||
|
SELECT COUNT(*) AS current_month_rides_count
|
||||||
|
FROM ride
|
||||||
|
WHERE MONTH(date) = :currentMonth AND YEAR(date) = :currentYear
|
||||||
|
";
|
||||||
|
$stmtMonth = $con->prepare($sqlMonth);
|
||||||
|
$stmtMonth->bindParam(':currentMonth', $currentMonth);
|
||||||
|
$stmtMonth->bindParam(':currentYear', $currentYear);
|
||||||
|
$stmtMonth->execute();
|
||||||
|
$monthRides = $stmtMonth->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Append current month total to each row (if needed)
|
||||||
|
foreach ($dailyRides as &$row) {
|
||||||
|
$row['current_month_rides_count'] = $monthRides['current_month_rides_count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return result
|
||||||
|
if ($dailyRides) {
|
||||||
|
jsonSuccess($dailyRides);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
91
backend/Admin/Staff/add.php
Normal file
91
backend/Admin/Staff/add.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/Staff/add.php
|
||||||
|
* إضافة موظف جديد (أدمن أو خدمة عملاء) مع تشفير البيانات وحفظ بصمة الجهاز
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// التحقق من الصلاحيات: فقط المشرفين يمكنهم الإضافة
|
||||||
|
// إذا لم يكن هناك أي مدير في النظام، نسمح// تم تعطيل التحقق للسماح بإعادة التهيئة في مرحلة التطوير
|
||||||
|
// $count = $con->query("SELECT COUNT(*) FROM adminUser")->fetchColumn();
|
||||||
|
// if ($count > 0) die("Access Denied: Admin already initialized.");
|
||||||
|
// $auth = JwtService::authenticate($redis);
|
||||||
|
// if ($auth['role'] !== 'super_admin' && $auth['role'] !== 'admin') {
|
||||||
|
// jsonError("Unauthorized. Only Admins can add staff.");
|
||||||
|
// exit;
|
||||||
|
// }
|
||||||
|
|
||||||
|
$name = filterRequest("name");
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
$email = filterRequest("email");
|
||||||
|
$password = filterRequest("password");
|
||||||
|
$role = filterRequest("role"); // 'admin' or 'service'
|
||||||
|
$fingerprint = filterRequest("fingerprint") ?: '';
|
||||||
|
$gender = filterRequest("gender") ?? 'Male';
|
||||||
|
$birthdate = filterRequest("birthdate") ?? date('Y-m-d');
|
||||||
|
$site = filterRequest("site") ?? 'main';
|
||||||
|
|
||||||
|
if (empty($name) || empty($password) || empty($role)) {
|
||||||
|
jsonError("Missing required fields (name, password, role).");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// تشفير البيانات الحساسة
|
||||||
|
$encName = $encryptionHelper->encryptData($name);
|
||||||
|
$encPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
$encEmail = $encryptionHelper->encryptData($email);
|
||||||
|
|
||||||
|
// تشفير البصمة وهش البصمة (إذا تم إرسالها)
|
||||||
|
$encFp = $fingerprint ? $encryptionHelper->encryptData($fingerprint) : '';
|
||||||
|
$fpHash = $fingerprint ? hash('sha256', $fingerprint) : '';
|
||||||
|
$uniqueId = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
if ($role === 'admin') {
|
||||||
|
// الإضافة لجدول المديرين
|
||||||
|
$sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, password, role, created_at)
|
||||||
|
VALUES (:id, :fp, :fp_hash, :name, :pass, :role, NOW())";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $uniqueId,
|
||||||
|
':fp' => $encFp,
|
||||||
|
':fp_hash' => $fpHash,
|
||||||
|
':name' => $encName,
|
||||||
|
':pass' => $hashedPassword,
|
||||||
|
':role' => $role
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// الإضافة لجدول المستخدمين (خدمة العملاء)
|
||||||
|
// أضفنا site و last_name (كقيمة افتراضية فارغة إذا لم تتوفر)
|
||||||
|
$sql = "INSERT INTO users (id, fingerprint, fingerprint_hash, phone, email, gender, password, birthdate, user_type, first_name, last_name, site, created_at)
|
||||||
|
VALUES (:id, :fp, :fp_hash, :phone, :email, :gender, :pass, :bdate, 'service', :fname, :lname, :site, NOW())";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $uniqueId,
|
||||||
|
':fp' => $encFp,
|
||||||
|
':fp_hash' => $fpHash,
|
||||||
|
':phone' => $encPhone,
|
||||||
|
':email' => $encEmail,
|
||||||
|
':gender' => $gender,
|
||||||
|
':pass' => $hashedPassword,
|
||||||
|
':bdate' => $birthdate,
|
||||||
|
':fname' => $encName,
|
||||||
|
':lname' => '', // last_name is empty for now
|
||||||
|
':site' => $site
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess("Staff member added successfully.");
|
||||||
|
} else {
|
||||||
|
jsonError("Failed to add staff member.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[Staff Add Error] " . $e->getMessage());
|
||||||
|
jsonError("Server error: " . $e->getMessage());
|
||||||
|
}
|
||||||
56
backend/Admin/Staff/setup.php
Normal file
56
backend/Admin/Staff/setup.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/Staff/setup.php
|
||||||
|
* سكربت إعداد المسؤول الأول (Super Admin)
|
||||||
|
* يستخدم لمرة واحدة فقط عندما تكون الجداول فارغة
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// تم تعطيل التحقق للسماح بإعادة التهيئة
|
||||||
|
// $count = $con->query("SELECT COUNT(*) FROM adminUser")->fetchColumn();
|
||||||
|
// if ($count > 0) {
|
||||||
|
// die("Access Denied: Admin already initialized.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
$password = "malDev@2101"; // كلمة المرور المؤقتة
|
||||||
|
$hashedPass = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// قائمة بالمسؤولين الأوائل (بصمات أجهزتك)
|
||||||
|
$admins = [
|
||||||
|
[
|
||||||
|
'name' => 'Hamza (iPhone)',
|
||||||
|
'fp' => 'D386663E-51E1-4322-B1E2-F469C7E58063_iPhone', // مثال بناءً على وصفك (deviceId_model)
|
||||||
|
'role' => 'admin'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Hamza (MacBook)',
|
||||||
|
'fp' => '5449E3D3-E427-50D7-91A6-D86D973DC6E0_Mac15,3', // مثال للماك بوك
|
||||||
|
'role' => 'admin'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con->exec("DELETE FROM adminUser");
|
||||||
|
foreach ($admins as $admin) {
|
||||||
|
$encName = $encryptionHelper->encryptData($admin['name']);
|
||||||
|
$encFp = $encryptionHelper->encryptData($admin['fp']);
|
||||||
|
$fpHash = hash('sha256', $admin['fp']);
|
||||||
|
$uniqueId = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, password, role, created_at)
|
||||||
|
VALUES (:id, :fp, :fp_hash, :name, :pass, :role, NOW())";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $uniqueId,
|
||||||
|
':fp' => $encFp,
|
||||||
|
':fp_hash' => $fpHash,
|
||||||
|
':name' => $encName,
|
||||||
|
':pass' => $hashedPass,
|
||||||
|
':role' => $admin['role']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
echo "<h1>Initialization Successful</h1>";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error: " . $e->getMessage();
|
||||||
|
}
|
||||||
46
backend/Admin/adminUser/add.php
Normal file
46
backend/Admin/adminUser/add.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
|
||||||
|
$deviceNumber = filterRequest("deviceNumber");
|
||||||
|
$name = filterRequest("name");
|
||||||
|
$password = filterRequest("password");
|
||||||
|
$role = filterRequest("role") ?? 'admin';
|
||||||
|
|
||||||
|
if (empty($name) || empty($password)) {
|
||||||
|
jsonError("Name and password are required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// Hash the password for security
|
||||||
|
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
$sql = "INSERT INTO `adminUser`(`id`, `device_number`, `name`, `password`, `role`) VALUES (
|
||||||
|
UUID(),
|
||||||
|
:deviceNumber,
|
||||||
|
:name,
|
||||||
|
:password,
|
||||||
|
:role
|
||||||
|
)";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
':deviceNumber' => $deviceNumber,
|
||||||
|
':name' => $name,
|
||||||
|
':password' => $hashedPassword,
|
||||||
|
':role' => $role
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess("Admin user data saved successfully");
|
||||||
|
} else {
|
||||||
|
jsonError("Failed to save admin user data");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[Admin Add Error] " . $e->getMessage());
|
||||||
|
jsonError("Database error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
86
backend/Admin/adminUser/add_invoice.php
Executable file
86
backend/Admin/adminUser/add_invoice.php
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// عرض كافة الأخطاء
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$driverID = filterRequest("driverID");
|
||||||
|
$invoiceNumber = filterRequest("invoiceNumber");
|
||||||
|
$amount = filterRequest("amount");
|
||||||
|
$date = filterRequest("date");
|
||||||
|
$name = filterRequest("name");
|
||||||
|
|
||||||
|
$linkImage = null;
|
||||||
|
$uploadDate = date("Y-m-d H:i:s");
|
||||||
|
|
||||||
|
// ✅ طباعة بيانات الإدخال للتأكد
|
||||||
|
error_log("[add_invoice.php] 📥 Data received | driverID: $driverID, invoiceNumber: $invoiceNumber, amount: $amount, date: $date");
|
||||||
|
|
||||||
|
// التحقق من وجود ملف الصورة
|
||||||
|
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$image_file = $_FILES['image'];
|
||||||
|
$image_name = $image_file['name'];
|
||||||
|
$image_extension = strtolower(pathinfo($image_name, PATHINFO_EXTENSION));
|
||||||
|
$allowed_extensions = ['jpg', 'jpeg', 'png'];
|
||||||
|
|
||||||
|
if (!in_array($image_extension, $allowed_extensions)) {
|
||||||
|
error_log("[add_invoice.php] ❌ Invalid image extension: .$image_extension");
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid file type.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||||
|
$mime_type = finfo_file($finfo, $image_file['tmp_name']);
|
||||||
|
finfo_close($finfo);
|
||||||
|
|
||||||
|
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg'];
|
||||||
|
if (!in_array($mime_type, $allowed_mime_types)) {
|
||||||
|
error_log("[add_invoice.php] ❌ Invalid MIME type: $mime_type");
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid file type (MIME mismatch).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_filename = $invoiceNumber . "_" . $driverID . '.' . $image_extension;
|
||||||
|
$target_dir = "invoice_images/";
|
||||||
|
$target_file = $target_dir . $new_filename;
|
||||||
|
|
||||||
|
if (!is_dir($target_dir)) {
|
||||||
|
if (!mkdir($target_dir, 0755, true)) {
|
||||||
|
error_log("[add_invoice.php] ❌ Failed to create directory: $target_dir");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!move_uploaded_file($image_file['tmp_name'], $target_file)) {
|
||||||
|
error_log("[add_invoice.php] ❌ Failed to move uploaded file.");
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to upload image.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$linkImage = 'https://intaleq.xyz/intaleq/Admin/adminUser/invoice_images/' . $new_filename;
|
||||||
|
error_log("[add_invoice.php] ✅ Image uploaded successfully: $linkImage");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $con->prepare("INSERT INTO invoice_records (driverID, invoice_number,name, amount, date, image_link, created_at)
|
||||||
|
VALUES (?, ?, ?,?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$driverID, $invoiceNumber,$name, $amount, $date, $linkImage, $uploadDate]);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Invoice data saved.',
|
||||||
|
'image' => $linkImage
|
||||||
|
]);
|
||||||
|
|
||||||
|
error_log("[add_invoice.php] ✅ Invoice saved successfully.");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$errorMsg = $e->getMessage();
|
||||||
|
error_log("[add_invoice.php] 🛑 PDO ERROR: $errorMsg");
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => "Database error: $errorMsg"
|
||||||
|
]);
|
||||||
|
}
|
||||||
0
backend/Admin/adminUser/delete.php
Normal file
0
backend/Admin/adminUser/delete.php
Normal file
0
backend/Admin/adminUser/error_log
Normal file
0
backend/Admin/adminUser/error_log
Normal file
24
backend/Admin/adminUser/get.php
Normal file
24
backend/Admin/adminUser/get.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$device_number = filterRequest("device_number");
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
`adminUser`
|
||||||
|
WHERE
|
||||||
|
`device_number` = '$device_number'";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (count($result) === 1) {
|
||||||
|
// Print the first record as a success message
|
||||||
|
jsonSuccess($result[0]);
|
||||||
|
} else {
|
||||||
|
// Print a failure message
|
||||||
|
jsonError($message = "Failed to retrieve Password or user name incorrect");
|
||||||
|
}
|
||||||
|
?>
|
||||||
BIN
backend/Admin/adminUser/invoice_images/INV-20250729-224_123.jpg
Normal file
BIN
backend/Admin/adminUser/invoice_images/INV-20250729-224_123.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
BIN
backend/Admin/adminUser/invoice_images/INV-20250729-592_123.jpg
Normal file
BIN
backend/Admin/adminUser/invoice_images/INV-20250729-592_123.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
BIN
backend/Admin/adminUser/invoice_images/INV-20250810-859_123.jpg
Normal file
BIN
backend/Admin/adminUser/invoice_images/INV-20250810-859_123.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
BIN
backend/Admin/adminUser/invoice_images/INV-20250812-737_123.jpg
Normal file
BIN
backend/Admin/adminUser/invoice_images/INV-20250812-737_123.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 MiB |
28
backend/Admin/adminUser/invoice_total.php
Executable file
28
backend/Admin/adminUser/invoice_total.php
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
// ✅ استرجاع كل الفواتير من قاعدة البيانات
|
||||||
|
try {
|
||||||
|
$stmt = $con->prepare("SELECT * FROM invoice_records ORDER BY date DESC");
|
||||||
|
$stmt->execute();
|
||||||
|
$invoices = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// ✅ حساب عدد الفواتير ومجموع المبالغ
|
||||||
|
$count = count($invoices);
|
||||||
|
$totalAmount = array_sum(array_column($invoices, 'amount'));
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"status" => "success",
|
||||||
|
"data" => $invoices,
|
||||||
|
"summary" => [
|
||||||
|
"count" => $count,
|
||||||
|
"total" => $totalAmount
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode([
|
||||||
|
"status" => "error",
|
||||||
|
"message" => "Database error: " . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
0
backend/Admin/adminUser/update.php
Normal file
0
backend/Admin/adminUser/update.php
Normal file
48
backend/Admin/auth/approve_admin.php
Normal file
48
backend/Admin/auth/approve_admin.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/auth/approve_admin.php
|
||||||
|
* الموافقة على أو رفض طلبات انضمام المشرفين
|
||||||
|
* مسموح فقط للسوبر أدمن
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
if ($role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Forbidden. Super Admin access required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetId = filterRequest('admin_id');
|
||||||
|
$action = filterRequest('action'); // approved, rejected, suspended
|
||||||
|
|
||||||
|
if (empty($targetId) || empty($action)) {
|
||||||
|
jsonError("Admin ID and action are required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($action, ['approved', 'rejected', 'suspended'])) {
|
||||||
|
jsonError("Invalid action.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
$sql = "UPDATE adminUser SET status = :status, approved_by = :by, approved_at = NOW() WHERE id = :id";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
':status' => $action,
|
||||||
|
':by' => $user_id, // السوبر أدمن الحالي
|
||||||
|
':id' => $targetId
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
printSuccess(null, "Admin status updated to $action.");
|
||||||
|
} else {
|
||||||
|
jsonError("Admin not found or status already updated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[Approve Admin Error] " . $e->getMessage());
|
||||||
|
jsonError("Server Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
74
backend/Admin/auth/debug_login.php
Normal file
74
backend/Admin/auth/debug_login.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/auth/debug_login.php
|
||||||
|
* ملف تشخيصي مؤقت — يُحذف بعد التحقق
|
||||||
|
*/
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', '1');
|
||||||
|
|
||||||
|
$checks = [];
|
||||||
|
|
||||||
|
// 1. التحقق من ملف bootstrap
|
||||||
|
$bootstrapPath = __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
$checks['bootstrap_exists'] = file_exists($bootstrapPath);
|
||||||
|
|
||||||
|
// 2. محاولة تحميل bootstrap مع التقاط الأخطاء
|
||||||
|
try {
|
||||||
|
ob_start();
|
||||||
|
require_once $bootstrapPath;
|
||||||
|
$bootstrapOutput = ob_get_clean();
|
||||||
|
$checks['bootstrap_loaded'] = true;
|
||||||
|
$checks['bootstrap_output'] = $bootstrapOutput ?: '(clean)';
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ob_end_clean();
|
||||||
|
$checks['bootstrap_loaded'] = false;
|
||||||
|
$checks['bootstrap_error'] = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. التحقق من الدوال المطلوبة
|
||||||
|
$checks['filterRequest_exists'] = function_exists('filterRequest');
|
||||||
|
$checks['jsonError_exists'] = function_exists('jsonError');
|
||||||
|
$checks['sendWhatsAppFromServer_exists'] = function_exists('sendWhatsAppFromServer');
|
||||||
|
|
||||||
|
// 4. التحقق من قاعدة البيانات
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
$checks['db_connected'] = true;
|
||||||
|
|
||||||
|
// التحقق من بنية الجدول
|
||||||
|
$stmt = $con->query("DESCRIBE adminUser");
|
||||||
|
$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
$checks['adminUser_columns'] = $columns;
|
||||||
|
|
||||||
|
// هل يوجد جدول token_verification_admin؟
|
||||||
|
$stmt2 = $con->query("SHOW TABLES LIKE 'token_verification_admin'");
|
||||||
|
$checks['token_verification_admin_exists'] = $stmt2->rowCount() > 0;
|
||||||
|
|
||||||
|
if (!$checks['token_verification_admin_exists']) {
|
||||||
|
$checks['CRITICAL'] = 'Table token_verification_admin does NOT exist! This is why login fails.';
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$checks['db_connected'] = false;
|
||||||
|
$checks['db_error'] = $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. التحقق من PHP error log الأخير
|
||||||
|
$logPath = '/home/intaleq-api/logs/php_errors.log';
|
||||||
|
if (file_exists($logPath)) {
|
||||||
|
$lines = file($logPath);
|
||||||
|
$checks['last_5_errors'] = array_map('trim', array_slice($lines, -5));
|
||||||
|
} else {
|
||||||
|
$logPath2 = __DIR__ . '/../../logs/php_errors.log';
|
||||||
|
if (file_exists($logPath2)) {
|
||||||
|
$lines = file($logPath2);
|
||||||
|
$checks['last_5_errors'] = array_map('trim', array_slice($lines, -5));
|
||||||
|
} else {
|
||||||
|
$checks['error_log'] = 'Log file not found';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. نسخة PHP
|
||||||
|
$checks['php_version'] = PHP_VERSION;
|
||||||
|
|
||||||
|
echo json_encode($checks, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
33
backend/Admin/auth/list_pending.php
Normal file
33
backend/Admin/auth/list_pending.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/auth/list_pending.php
|
||||||
|
* عرض قائمة المشرفين الذين ينتظرون الموافقة
|
||||||
|
* مسموح فقط للسوبر أدمن
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
// التحقق من الصلاحيات
|
||||||
|
if ($role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Forbidden. Super Admin access required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
$stmt = $con->prepare("SELECT id, name, phone, created_at FROM adminUser WHERE status = 'pending' ORDER BY created_at DESC");
|
||||||
|
$stmt->execute();
|
||||||
|
$pending = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك تشفير الأسماء
|
||||||
|
foreach ($pending as &$admin) {
|
||||||
|
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
printSuccess($pending);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[List Pending Admins Error] " . $e->getMessage());
|
||||||
|
jsonError("Server Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
120
backend/Admin/auth/login.php
Executable file
120
backend/Admin/auth/login.php
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/auth/login.php
|
||||||
|
* تسجيل دخول المشرفين باستخدام البصمة وكلمة المرور المشفرة
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
require_once __DIR__ . '/../../functions.php';
|
||||||
|
|
||||||
|
$fingerprint = filterRequest('fingerprint');
|
||||||
|
$password = filterRequest('password');
|
||||||
|
$audience = filterRequest('aud') ?? 'admin';
|
||||||
|
$isRenewal = filterRequest('is_renewal') === '1';
|
||||||
|
|
||||||
|
if (empty($fingerprint) || empty($password)) {
|
||||||
|
jsonError("Fingerprint and password are required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// البحث عن المشرف باستخدام بصمة الجهاز (Fingerprint Hash)
|
||||||
|
$fpHash = hash('sha256', $fingerprint);
|
||||||
|
$stmt = $con->prepare("SELECT * FROM adminUser WHERE fingerprint_hash = :fp LIMIT 1");
|
||||||
|
$stmt->execute([':fp' => $fpHash]);
|
||||||
|
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($admin) {
|
||||||
|
// 1. التحقق من حالة الحساب
|
||||||
|
if ($admin['status'] === 'pending') {
|
||||||
|
jsonError("حسابك قيد المراجعة حالياً. يرجى الانتظار للموافقة.");
|
||||||
|
exit;
|
||||||
|
} elseif ($admin['status'] === 'suspended') {
|
||||||
|
jsonError("هذا الحساب معلق. يرجى التواصل مع المدير.");
|
||||||
|
exit;
|
||||||
|
} elseif ($admin['status'] === 'rejected') {
|
||||||
|
jsonError("تم رفض طلب الانضمام لهذا الحساب.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. التحقق من كلمة المرور
|
||||||
|
if (password_verify($password, $admin['password'])) {
|
||||||
|
|
||||||
|
// إذا كان هذا مجرد تجديد للتوكن (إعادة الدخول التلقائي من التطبيق)، فلا داعي لإرسال OTP
|
||||||
|
if ($isRenewal) {
|
||||||
|
$jwtService = new JwtService($redis);
|
||||||
|
$role = $admin['role'] ?? 'admin';
|
||||||
|
|
||||||
|
// إلغاء التوكن القديم إذا وجد في Redis
|
||||||
|
if ($redis) {
|
||||||
|
$oldJti = $redis->get("active_jti:" . $admin['id']);
|
||||||
|
if ($oldJti) {
|
||||||
|
$jwtService->revokeToken($oldJti, 3600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$jwt = $jwtService->generateAccessToken($admin['id'], $role, $audience, $fingerprint);
|
||||||
|
|
||||||
|
// فك تشفير البيانات للعرض
|
||||||
|
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
|
||||||
|
unset($admin['password']);
|
||||||
|
|
||||||
|
printSuccess([
|
||||||
|
"message" => "Login successful",
|
||||||
|
"admin" => $admin,
|
||||||
|
"jwt" => $jwt,
|
||||||
|
"expires_in" => 3600
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. توليد رمز تحقق OTP وإرساله عبر WhatsApp
|
||||||
|
$otp = rand(10000, 99999);
|
||||||
|
$encryptedPhone = $admin['phone'] ?? '';
|
||||||
|
|
||||||
|
if (empty($encryptedPhone)) {
|
||||||
|
jsonError("رقم الهاتف غير مسجل لهذا الحساب. يرجى مراجعة الإدارة.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// فك تشفير رقم الهاتف (مخزن مشفراً في قاعدة البيانات)
|
||||||
|
$phone = $encryptionHelper->decryptData($encryptedPhone);
|
||||||
|
if (!$phone || empty($phone)) {
|
||||||
|
// إذا فشل فك التشفير، قد يكون الرقم مخزناً بدون تشفير
|
||||||
|
$phone = $encryptedPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
$messageBody = "رمز التحقق الخاص بك للدخول إلى لوحة الإدارة هو: $otp";
|
||||||
|
$success = sendWhatsAppFromServer($phone, $messageBody);
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
// حفظ الرمز مشفراً في قاعدة البيانات (وحفظ رقم الهاتف مشفراً أيضاً)
|
||||||
|
$encryptedOtp = $encryptionHelper->encryptData((string)$otp);
|
||||||
|
|
||||||
|
$stmt = $con->prepare("INSERT INTO token_verification_admin (phone_number, token, expiration_time)
|
||||||
|
VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 10 MINUTE))
|
||||||
|
ON DUPLICATE KEY UPDATE token = VALUES(token), expiration_time = VALUES(expiration_time)");
|
||||||
|
$stmt->execute([$encryptedPhone, $encryptedOtp]);
|
||||||
|
|
||||||
|
// إخفاء جزء من الرقم في الاستجابة للأمان
|
||||||
|
$maskedPhone = substr($phone, 0, 4) . '****' . substr($phone, -3);
|
||||||
|
|
||||||
|
printSuccess([
|
||||||
|
"status" => "otp_required",
|
||||||
|
"message" => "تم إرسال رمز التحقق إلى WhatsApp الخاص بك.",
|
||||||
|
"phone" => $maskedPhone
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
jsonError("فشل في إرسال رمز التحقق عبر WhatsApp.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jsonError("كلمة المرور غير صحيحة.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jsonError("الجهاز غير مسجل كمشرف.");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[Admin Login Error] " . $e->getMessage());
|
||||||
|
jsonError("خطأ في السيرفر: " . $e->getMessage());
|
||||||
|
}
|
||||||
93
backend/Admin/auth/loginWallet.php
Normal file
93
backend/Admin/auth/loginWallet.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/auth/loginWallet.php
|
||||||
|
* توليد توكن خاص بسيرفر المحفظة (Wallet SSO)
|
||||||
|
* يتم توقيعه بالمفتاح المشترك (SECRET_KEY_PAY)
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
|
||||||
|
// التحقق من الجلسة الحالية للأدمن
|
||||||
|
$jwtService = new JwtService($redis ?? null);
|
||||||
|
$admin = $jwtService->authenticate();
|
||||||
|
|
||||||
|
error_log("[Wallet_SSO] Authenticated Admin ID: " . ($admin->user_id ?? 'N/A') . " | Role: " . ($admin->role ?? 'N/A'));
|
||||||
|
|
||||||
|
if ($admin->role !== 'admin' && $admin->role !== 'super_admin') {
|
||||||
|
jsonError("Unauthorized. Admin access required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// جلب المفتاح المشترك لسيرفر المحفظة
|
||||||
|
$payKeyPath = '/home/intaleq-api/.secret_key_pay';
|
||||||
|
$payKey = file_exists($payKeyPath) ? trim(file_get_contents($payKeyPath)) : getenv('SECRET_KEY_PAY');
|
||||||
|
|
||||||
|
if (empty($payKey)) {
|
||||||
|
$payKey = trim(@file_get_contents('/home/intaleq-api/.secret_key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($payKey)) {
|
||||||
|
jsonError("Internal configuration error: Shared secret key missing.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$issuer = 'Tripz-Wallet';
|
||||||
|
$audience = 'Tripz-Wallet';
|
||||||
|
$hmacSecret = getenv('SECRET_KEY_HMAC') ?: '';
|
||||||
|
|
||||||
|
$ttl = 600; // 10 دقائق
|
||||||
|
$iat = time();
|
||||||
|
$exp = $iat + $ttl;
|
||||||
|
$jti = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
// محتوى التوكن (Payload)
|
||||||
|
$payload = [
|
||||||
|
'iss' => $issuer,
|
||||||
|
'aud' => $audience,
|
||||||
|
'user_id' => $admin->user_id,
|
||||||
|
'role' => 'admin', // نرسل 'admin' للمحفظة لضمان التوافق مع برمجياتها القديمة
|
||||||
|
'iat' => $iat,
|
||||||
|
'exp' => $exp,
|
||||||
|
'jti' => $jti
|
||||||
|
];
|
||||||
|
|
||||||
|
// إلغاء التوكن القديم إذا وجد في Redis
|
||||||
|
if ($redis) {
|
||||||
|
$oldJtiKey = "wallet_jti:" . $admin->user_id;
|
||||||
|
$oldJti = $redis->get($oldJtiKey);
|
||||||
|
if ($oldJti) {
|
||||||
|
// إضافة التوكن القديم للقائمة السوداء
|
||||||
|
$redis->setex("jwt:blacklist:$oldJti", $ttl + 60, '1');
|
||||||
|
}
|
||||||
|
// تخزين الـ JTI الجديد
|
||||||
|
$redis->setex($oldJtiKey, $ttl, $jti);
|
||||||
|
}
|
||||||
|
|
||||||
|
// إضافة بصمة الجهاز للتوكن لزيادة الأمان
|
||||||
|
$fpHeader = $_SERVER['HTTP_X_DEVICE_FP'] ?? null;
|
||||||
|
$fpPepper = getenv('FP_PEPPER');
|
||||||
|
if ($fpHeader && $fpPepper) {
|
||||||
|
$payload['fingerPrint'] = hash('sha256', $fpHeader . $fpPepper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// توليد التوكن
|
||||||
|
$jwt = JWT::encode($payload, $payKey, 'HS256');
|
||||||
|
|
||||||
|
// حساب الـ HMAC Hash المطلوب لسيرفر المحفظة
|
||||||
|
$hmacHash = hash_hmac('sha256', (string)$admin->user_id, $hmacSecret);
|
||||||
|
|
||||||
|
printSuccess([
|
||||||
|
"status" => "success",
|
||||||
|
"jwt" => $jwt,
|
||||||
|
"hmac" => $hmacHash,
|
||||||
|
"expires_in" => $ttl
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[Admin Wallet SSO Error] " . $e->getMessage());
|
||||||
|
jsonError("Server Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
28
backend/Admin/auth/migrate_db.php
Normal file
28
backend/Admin/auth/migrate_db.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// Check if columns already exist to avoid errors
|
||||||
|
$check = $con->query("SHOW COLUMNS FROM adminUser LIKE 'status'");
|
||||||
|
if ($check->rowCount() == 0) {
|
||||||
|
$sql = "ALTER TABLE adminUser
|
||||||
|
ADD COLUMN status ENUM('pending', 'approved', 'suspended', 'rejected') NOT NULL DEFAULT 'pending' AFTER role,
|
||||||
|
ADD COLUMN phone VARCHAR(50) DEFAULT NULL AFTER name,
|
||||||
|
ADD COLUMN email VARCHAR(255) DEFAULT NULL AFTER phone,
|
||||||
|
ADD COLUMN approved_by VARCHAR(64) DEFAULT NULL AFTER status,
|
||||||
|
ADD COLUMN approved_at DATETIME DEFAULT NULL AFTER approved_by";
|
||||||
|
|
||||||
|
$con->exec($sql);
|
||||||
|
|
||||||
|
// Update existing admins to approved and super_admin
|
||||||
|
$con->exec("UPDATE adminUser SET status = 'approved', role = 'super_admin' WHERE id IS NOT NULL");
|
||||||
|
|
||||||
|
echo json_encode(["status" => "success", "message" => "Migration completed successfully."]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(["status" => "success", "message" => "Columns already exist."]);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(["status" => "error", "message" => $e->getMessage()]);
|
||||||
|
}
|
||||||
128
backend/Admin/auth/migration_cryptography.php
Normal file
128
backend/Admin/auth/migration_cryptography.php
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?php
|
||||||
|
// ============================================================
|
||||||
|
// Admin/auth/migration_cryptography.php
|
||||||
|
// سكريبت لترحيل التشفير القديم (CBC) إلى التشفير الجديد (AES-256-GCM)
|
||||||
|
// يمكن تشغيله عبر الـ CLI أو المتصفح (بصلاحيات مسؤول).
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
echo "Starting Cryptography Migration to AES-256-GCM...\n";
|
||||||
|
ob_flush(); flush();
|
||||||
|
|
||||||
|
$tables = [
|
||||||
|
'driver' => [
|
||||||
|
'phone', 'email', 'gender', 'birthdate', 'site',
|
||||||
|
'first_name', 'last_name', 'accountBank', 'education',
|
||||||
|
'employmentType', 'maritalStatus', 'national_number',
|
||||||
|
'name_arabic', 'address'
|
||||||
|
],
|
||||||
|
'passengers' => [
|
||||||
|
'phone', 'email', 'gender', 'birthdate',
|
||||||
|
'first_name', 'last_name', 'token'
|
||||||
|
],
|
||||||
|
'CarRegistration' => [
|
||||||
|
'vin', 'car_plate', 'owner', 'address'
|
||||||
|
],
|
||||||
|
'carPlateEdit' => [
|
||||||
|
'carPlate', 'owner'
|
||||||
|
],
|
||||||
|
'phone_verification' => [
|
||||||
|
'phone_number'
|
||||||
|
],
|
||||||
|
'phone_verification_passenger' => [
|
||||||
|
'phone_number'
|
||||||
|
],
|
||||||
|
'driverToken' => [
|
||||||
|
'token'
|
||||||
|
],
|
||||||
|
'passengerToken' => [
|
||||||
|
'token'
|
||||||
|
],
|
||||||
|
'mishwari' => [
|
||||||
|
'phone', 'gender', 'name', 'name_english', 'car_plate', 'token', 'education', 'national_number', 'age'
|
||||||
|
],
|
||||||
|
'rate_app' => [
|
||||||
|
'email', 'phone'
|
||||||
|
],
|
||||||
|
'admins' => [
|
||||||
|
'name', 'phone', 'email', 'fp'
|
||||||
|
],
|
||||||
|
'driver_assurance' => [
|
||||||
|
'assured', 'health_insurance_provider'
|
||||||
|
],
|
||||||
|
'blacklist_drivers' => [
|
||||||
|
'phone'
|
||||||
|
],
|
||||||
|
'blacklist_passengers' => [
|
||||||
|
'phone'
|
||||||
|
],
|
||||||
|
'feedBack' => [
|
||||||
|
'feedBack'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$totalUpdated = 0;
|
||||||
|
|
||||||
|
foreach ($tables as $table => $columns) {
|
||||||
|
echo "Processing table: $table ...\n";
|
||||||
|
ob_flush(); flush();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "SELECT `id`, `" . implode("`, `", $columns) . "` FROM `$table`";
|
||||||
|
$stmt = $con->query($sql);
|
||||||
|
if (!$stmt) {
|
||||||
|
echo "Skipped $table (Not found or missing columns).\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Skipped $table due to error: " . $e->getMessage() . "\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tableUpdatedCount = 0;
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$id = $row['id'];
|
||||||
|
$needsUpdate = false;
|
||||||
|
$updateValues = [];
|
||||||
|
$params = [':id' => $id];
|
||||||
|
|
||||||
|
foreach ($columns as $col) {
|
||||||
|
$value = $row[$col];
|
||||||
|
|
||||||
|
// تحقق إذا كان الحقل يحتوي على قيمة وإذا لم يكن مشفر بالنظام الجديد
|
||||||
|
if (!empty($value) && strpos($value, 'GCM:') !== 0) {
|
||||||
|
// محاولة فك التشفير القديم (CBC)
|
||||||
|
try {
|
||||||
|
$decrypted = $encryptionHelper->decryptData($value);
|
||||||
|
if ($decrypted !== false && $decrypted !== '') {
|
||||||
|
// إعادة التشفير (سيستخدم GCM الآن)
|
||||||
|
$newEncrypted = $encryptionHelper->encryptData($decrypted);
|
||||||
|
$updateValues[] = "`$col` = :$col";
|
||||||
|
$params[":$col"] = $newEncrypted;
|
||||||
|
$needsUpdate = true;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Failed to migrate $col for ID $id in $table: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($needsUpdate) {
|
||||||
|
$setClause = implode(", ", $updateValues);
|
||||||
|
$updateSql = "UPDATE `$table` SET $setClause WHERE `id` = :id";
|
||||||
|
$updateStmt = $con->prepare($updateSql);
|
||||||
|
$updateStmt->execute($params);
|
||||||
|
$tableUpdatedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Finished $table. Updated rows: $tableUpdatedCount\n";
|
||||||
|
$totalUpdated += $tableUpdatedCount;
|
||||||
|
ob_flush(); flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Migration completed! Total rows updated: $totalUpdated\n";
|
||||||
|
?>
|
||||||
59
backend/Admin/auth/register.php
Normal file
59
backend/Admin/auth/register.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/auth/register.php
|
||||||
|
* التسجيل الذاتي للمشرفين - الحساب يكون بحالة pending بانتظار موافقة السوبر أدمن
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
|
||||||
|
$name = filterRequest('name');
|
||||||
|
$phone = filterRequest('phone');
|
||||||
|
$password = filterRequest('password');
|
||||||
|
$fingerprint = filterRequest('fingerprint');
|
||||||
|
|
||||||
|
if (empty($name) || empty($phone) || empty($password) || empty($fingerprint)) {
|
||||||
|
jsonError("All fields are required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// 1. التحقق من عدم وجود الحساب مسبقاً (عن طريق الهاتف أو البصمة)
|
||||||
|
$fpHash = hash('sha256', $fingerprint);
|
||||||
|
$check = $con->prepare("SELECT id FROM adminUser WHERE phone = ? OR fingerprint_hash = ? LIMIT 1");
|
||||||
|
$check->execute([$phone, $fpHash]);
|
||||||
|
|
||||||
|
if ($check->rowCount() > 0) {
|
||||||
|
jsonError("هذا الحساب أو الجهاز مسجل مسبقاً.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. تجهيز البيانات
|
||||||
|
$id = bin2hex(random_bytes(16));
|
||||||
|
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$encName = $encryptionHelper->encryptData($name);
|
||||||
|
$encFp = $encryptionHelper->encryptData($fingerprint);
|
||||||
|
|
||||||
|
// 3. الإدخال في قاعدة البيانات (الحالة الافتراضية هي pending)
|
||||||
|
$sql = "INSERT INTO adminUser (id, name, phone, password, fingerprint, fingerprint_hash, role, status, created_at)
|
||||||
|
VALUES (:id, :name, :phone, :pass, :fp, :fp_hash, 'admin', 'pending', NOW())";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $id,
|
||||||
|
':name' => $encName,
|
||||||
|
':phone' => $phone,
|
||||||
|
':pass' => $hashedPassword,
|
||||||
|
':fp' => $encFp,
|
||||||
|
':fp_hash' => $fpHash
|
||||||
|
]);
|
||||||
|
|
||||||
|
printSuccess([
|
||||||
|
"status" => "pending",
|
||||||
|
"message" => "تم تقديم طلب التسجيل بنجاح. يرجى انتظار موافقة الإدارة."
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[Admin Register Error] " . $e->getMessage());
|
||||||
|
jsonError("خطأ في السيرفر: " . $e->getMessage());
|
||||||
|
}
|
||||||
56
backend/Admin/auth/send_otp_admin.php
Executable file
56
backend/Admin/auth/send_otp_admin.php
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
// send_otp_admin.php — إرسال رمز التحقق لمسؤول عبر WhatsApp
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
error_log("--- [send_otp_admin] Script started ---");
|
||||||
|
|
||||||
|
// جلب الرقم من الطلب
|
||||||
|
$receiver = filterRequest("receiver");
|
||||||
|
//error_log("[send_otp_admin] Received phone number: " . var_export($receiver, true));
|
||||||
|
|
||||||
|
if (!$receiver) {
|
||||||
|
// error_log("[send_otp_admin] Missing phone number");
|
||||||
|
jsonError("رقم الهاتف مفقود.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// قراءة الأرقام المصرح بها من ENV
|
||||||
|
$allowedPhones = explode(',', getenv('ADMIN_PHONE_NUMBERS'));
|
||||||
|
//error_log("[send_otp_admin] Allowed phones: " . implode(', ', $allowedPhones));
|
||||||
|
|
||||||
|
if (!in_array($receiver, $allowedPhones)) {
|
||||||
|
error_log("[send_otp_admin] Unauthorized phone number attempted: $receiver");
|
||||||
|
jsonError("رقم الهاتف غير مصرح له.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// توليد رمز تحقق عشوائي
|
||||||
|
$otp = rand(10000, 99999);
|
||||||
|
$messageBody = "رمز التحقق الخاص بك للدخول إلى لوحة الإدارة هو: $otp";
|
||||||
|
//error_log("[send_otp_admin] Generated OTP: $otp for $receiver");
|
||||||
|
|
||||||
|
// إرسال الرسالة عبر WhatsApp
|
||||||
|
$success = sendWhatsAppFromServer($receiver, $messageBody);
|
||||||
|
error_log("[send_otp_admin] WhatsApp sending result: " . ($success ? "success" : "failure"));
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
try {
|
||||||
|
$stmt = $con->prepare("INSERT INTO token_verification_admin (phone_number, token, expiration_time)
|
||||||
|
VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE))
|
||||||
|
ON DUPLICATE KEY UPDATE token = VALUES(token), expiration_time = VALUES(expiration_time)");
|
||||||
|
$stmt->execute([$receiver, $otp]);
|
||||||
|
// error_log("[send_otp_admin] OTP saved to database successfully for $receiver");
|
||||||
|
|
||||||
|
jsonSuccess(null, "OTP sent successfully.");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// error_log("[send_otp_admin] Database error: " . $e->getMessage());
|
||||||
|
jsonError("حدث خطأ في حفظ الرمز.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// error_log("[send_otp_admin] Failed to send WhatsApp message to $receiver");
|
||||||
|
jsonError("فشل في إرسال الرمز عبر WhatsApp.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//error_log("--- [send_otp_admin] Script ended ---");
|
||||||
|
?>
|
||||||
83
backend/Admin/auth/verify_login.php
Normal file
83
backend/Admin/auth/verify_login.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/auth/verify_login.php
|
||||||
|
* الخطوة الثانية من تسجيل الدخول: التحقق من الـ OTP وإصدار التوكن النهائي
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
require_once __DIR__ . '/../../functions.php';
|
||||||
|
|
||||||
|
$otp = filterRequest('otp');
|
||||||
|
$fingerprint = filterRequest('fingerprint'); // مطلوب لربط التوكن بالجهاز
|
||||||
|
$audience = filterRequest('aud') ?? 'admin';
|
||||||
|
|
||||||
|
if (empty($otp) || empty($fingerprint)) {
|
||||||
|
jsonError("OTP and fingerprint are required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// 1. جلب بيانات المسؤول عبر البصمة (مصدر موثوق وغير مشفر)
|
||||||
|
$fpHash = hash('sha256', $fingerprint);
|
||||||
|
$stmt = $con->prepare("SELECT * FROM adminUser WHERE fingerprint_hash = :fp LIMIT 1");
|
||||||
|
$stmt->execute([':fp' => $fpHash]);
|
||||||
|
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$admin) {
|
||||||
|
jsonError("المسؤول غير موجود أو البصمة غير مطابقة.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. رقم الهاتف المشفر (للاستخدام في جدول OTP)
|
||||||
|
$encryptedPhone = $admin['phone'] ?? '';
|
||||||
|
|
||||||
|
// فك تشفيره لو احتجنا إرساله أو عرضه، لكن هنا نحن نحتاج المشفر للبحث
|
||||||
|
// $phone = $encryptionHelper->decryptData($encryptedPhone);
|
||||||
|
|
||||||
|
// تشفير الرمز (OTP) القادم من التطبيق للمقارنة
|
||||||
|
$encryptedOtp = $encryptionHelper->encryptData((string)$otp);
|
||||||
|
|
||||||
|
// 3. التحقق من الـ OTP (باستخدام القيم المشفرة)
|
||||||
|
$stmt = $con->prepare("SELECT * FROM token_verification_admin
|
||||||
|
WHERE phone_number = ? AND token = ?
|
||||||
|
AND expiration_time >= NOW()");
|
||||||
|
$stmt->execute([$encryptedPhone, $encryptedOtp]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() === 0) {
|
||||||
|
jsonError("رمز التحقق غير صالح أو منتهي الصلاحية.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// حذف الرمز بعد استخدامه لمرة واحدة (باستخدام الرقم المشفر)
|
||||||
|
$con->prepare("DELETE FROM token_verification_admin WHERE phone_number = ?")->execute([$encryptedPhone]);
|
||||||
|
|
||||||
|
// 4. إصدار التوكن النهائي
|
||||||
|
$jwtService = new JwtService($redis);
|
||||||
|
$role = $admin['role'] ?? 'admin';
|
||||||
|
|
||||||
|
// إلغاء التوكن القديم إذا وجد في Redis (Token Revocation)
|
||||||
|
if ($redis) {
|
||||||
|
$oldJti = $redis->get("active_jti:" . $admin['id']);
|
||||||
|
if ($oldJti) {
|
||||||
|
$jwtService->revokeToken($oldJti, 3600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$jwt = $jwtService->generateAccessToken($admin['id'], $role, $audience, $fingerprint);
|
||||||
|
|
||||||
|
// فك تشفير البيانات للعرض
|
||||||
|
$admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
|
||||||
|
unset($admin['password']);
|
||||||
|
|
||||||
|
printSuccess([
|
||||||
|
"message" => "Login successful",
|
||||||
|
"admin" => $admin,
|
||||||
|
"jwt" => $jwt,
|
||||||
|
"expires_in" => 3600
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("[Admin Verify OTP Error] " . $e->getMessage());
|
||||||
|
jsonError("خطأ في السيرفر: " . $e->getMessage());
|
||||||
|
}
|
||||||
46
backend/Admin/auth/verify_otp_admin.php
Executable file
46
backend/Admin/auth/verify_otp_admin.php
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$phone = filterRequest("phone_number");
|
||||||
|
$otp = filterRequest("otp");
|
||||||
|
$deviceNumber = filterRequest("device_number");
|
||||||
|
|
||||||
|
if (empty($phone) || empty($otp)) {
|
||||||
|
jsonError("رقم الهاتف أو رمز التحقق مفقود.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// التحقق من رمز التحقق (OTP)
|
||||||
|
$stmt = $con->prepare("SELECT * FROM token_verification_admin
|
||||||
|
WHERE phone_number = ? AND token = ?
|
||||||
|
AND expiration_time >= NOW()");
|
||||||
|
$stmt->execute([$phone, $otp]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// ✅ تحقق ناجح - ننتقل إلى إدخال أو تحديث سجل adminUser
|
||||||
|
|
||||||
|
// تحقق إن كان المستخدم موجود مسبقًا
|
||||||
|
$checkAdmin = $con->prepare("SELECT * FROM adminUser WHERE name = ?");
|
||||||
|
$checkAdmin->execute([$phone]);
|
||||||
|
|
||||||
|
$now = date("Y-m-d H:i:s");
|
||||||
|
|
||||||
|
if ($checkAdmin->rowCount() > 0) {
|
||||||
|
// المستخدم موجود ✅ تحديث device_number و updated_at
|
||||||
|
$update = $con->prepare("UPDATE adminUser
|
||||||
|
SET device_number = ?, updated_at = ?
|
||||||
|
WHERE name = ?");
|
||||||
|
$update->execute([$deviceNumber, $now, $phone]);
|
||||||
|
jsonSuccess(["message" => "verified and updated existing admin"]);
|
||||||
|
} else {
|
||||||
|
// المستخدم غير موجود ✅ إدخال جديد
|
||||||
|
$insert = $con->prepare("INSERT INTO adminUser (device_number, name, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?)");
|
||||||
|
$insert->execute([$deviceNumber, $phone, $now, $now]);
|
||||||
|
jsonSuccess(["message" => "verified and new admin created"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ❌ رمز التحقق غير صالح
|
||||||
|
jsonError("رمز التحقق غير صالح أو منتهي.");
|
||||||
|
}
|
||||||
73
backend/Admin/dashbord.php
Normal file
73
backend/Admin/dashbord.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// التحقق من الصلاحيات: مسموح فقط للأدمن والسوبر أدمن
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized access. Admin role required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
-- العدادات العامة
|
||||||
|
(SELECT COUNT(*) FROM passengers) AS countPassengers,
|
||||||
|
(SELECT COUNT(*) FROM driver) AS countDriver,
|
||||||
|
(SELECT COUNT(*) FROM ride) AS countRide,
|
||||||
|
|
||||||
|
-- إحصائيات الشهر الحالي
|
||||||
|
(SELECT COUNT(*) FROM passengers WHERE created_at BETWEEN DATE_FORMAT(CURDATE(), '%Y-%m-01') AND LAST_DAY(CURDATE())) AS countPassengersThisMonth,
|
||||||
|
(SELECT COUNT(*) FROM driver WHERE created_at BETWEEN DATE_FORMAT(CURDATE(), '%Y-%m-01') AND LAST_DAY(CURDATE())) AS countDriverThisMonth,
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE created_at BETWEEN DATE_FORMAT(CURDATE(), '%Y-%m-01') AND LAST_DAY(CURDATE())) AS countRideThisMonth,
|
||||||
|
(SELECT COUNT(*) FROM CarRegistration WHERE created_at BETWEEN DATE_FORMAT(CURDATE(), '%Y-%m-01') AND LAST_DAY(CURDATE())) AS countCarRegistrationThisMonth,
|
||||||
|
|
||||||
|
-- شكاوى
|
||||||
|
(SELECT COUNT(*) FROM complaint WHERE date_filed BETWEEN DATE_FORMAT(CURDATE(), '%Y-%m-01') AND LAST_DAY(CURDATE())) AS countComplaintThisMonth,
|
||||||
|
(SELECT COUNT(*) FROM complaint WHERE date_filed BETWEEN DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) AND DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 6 DAY)) AS countComplaintThisWeek,
|
||||||
|
(SELECT COUNT(*) FROM complaint WHERE DATE(date_filed) = CURDATE()) AS countComplaintToday,
|
||||||
|
|
||||||
|
-- المحافظ والتحويلات
|
||||||
|
|
||||||
|
-- إحصائيات وقت ومسافة الرحلات
|
||||||
|
(SELECT TIME_FORMAT(SEC_TO_TIME(AVG(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))), '%Hh %im') FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL) AS driver_avg_duration,
|
||||||
|
(SELECT MAX(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))) FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL) AS longest_duration,
|
||||||
|
(SELECT ROUND(SUM(distance),2) FROM ride) AS total_distance,
|
||||||
|
(SELECT ROUND(AVG(distance),2) FROM ride) AS average_distance,
|
||||||
|
(SELECT ROUND(MAX(distance),2) FROM ride) AS longest_distance,
|
||||||
|
|
||||||
|
-- أرباح السائق والشركة
|
||||||
|
(SELECT SUM(price_for_driver) FROM ride WHERE status = 'Finished') AS total_driver_earnings,
|
||||||
|
(SELECT ROUND(AVG(price_for_passenger),2) FROM ride) AS avg_passenger_price,
|
||||||
|
|
||||||
|
-- توزيع الرحلات حسب الوقت
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE HOUR(created_at) BETWEEN 6 AND 11) AS morning_ride_count,
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE HOUR(created_at) BETWEEN 12 AND 17) AS evening_ride_count,
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE HOUR(created_at) BETWEEN 18 AND 23 OR HOUR(created_at) BETWEEN 0 AND 5) AS night_ride_count,
|
||||||
|
|
||||||
|
-- أنواع الرحلات
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE carType = 'Comfort') AS comfort,
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE carType = 'Speed') AS speed,
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE carType = 'Lady') AS lady,
|
||||||
|
|
||||||
|
-- حالة الرحلات
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE status = 'wait') AS ongoing_rides,
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE status = 'Finished') AS completed_rides,
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE status = 'cancel') AS cancelled_rides,
|
||||||
|
|
||||||
|
-- عدد السائقين الفريدين
|
||||||
|
(SELECT COUNT(*) FROM (SELECT driver_id FROM ride GROUP BY driver_id) AS sub) AS num_Driver,
|
||||||
|
|
||||||
|
-- التحويلات البنكية
|
||||||
|
(SELECT COUNT(*) FROM payments WHERE payment_method = 'TransferFrom') AS transfer_from_count
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
jsonSuccess($result);
|
||||||
|
} else {
|
||||||
|
jsonError("No dashboard data found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
13
backend/Admin/debug/check_driver_phones.php
Normal file
13
backend/Admin/debug/check_driver_phones.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'connect.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $con->query("SELECT phone FROM driver LIMIT 10");
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
echo "Raw: " . $row['phone'] . " | Decrypted: " . $encryptionHelper->decryptData($row['phone']) . "\n";
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo $e->getMessage();
|
||||||
|
}
|
||||||
|
?>
|
||||||
11
backend/Admin/debug/check_users_cols.php
Normal file
11
backend/Admin/debug/check_users_cols.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'connect.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $con->query("DESCRIBE users");
|
||||||
|
$cols = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
echo json_encode($cols, JSON_PRETTY_PRINT);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo $e->getMessage();
|
||||||
|
}
|
||||||
|
?>
|
||||||
23
backend/Admin/debug/debug_phone.php
Normal file
23
backend/Admin/debug/debug_phone.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/connect.php';
|
||||||
|
|
||||||
|
$searchPhone = '0992952235';
|
||||||
|
echo "Searching for: $searchPhone\n";
|
||||||
|
|
||||||
|
$variants = [$searchPhone, '963' . substr($searchPhone, 1), '+963' . substr($searchPhone, 1)];
|
||||||
|
|
||||||
|
foreach ($variants as $v) {
|
||||||
|
echo "Checking variant: $v\n";
|
||||||
|
$enc = $encryptionHelper->encryptData($v);
|
||||||
|
|
||||||
|
$stmt = $con->prepare("SELECT id, phone, first_name FROM driver WHERE phone = ? OR phone = ?");
|
||||||
|
$stmt->execute([$v, $enc]);
|
||||||
|
$res = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($res) {
|
||||||
|
echo "FOUND! ID: {$res['id']}, Name: {$res['first_name']}, Phone in DB: {$res['phone']}\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "NOT FOUND in driver table.\n";
|
||||||
57
backend/Admin/debug/env_test.php
Normal file
57
backend/Admin/debug/env_test.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
// env_test.php - أداة مخصصة لاختبار جميع متغيرات البيئة
|
||||||
|
require_once __DIR__ . '/core/bootstrap.php'; // لتحميل الـ .env
|
||||||
|
|
||||||
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
|
|
||||||
|
echo "=== فحص متغيرات البيئة (Environment Variables) ===\n\n";
|
||||||
|
|
||||||
|
$keysToCheck = [
|
||||||
|
'PASSENGER_SOCKET_URL',
|
||||||
|
'LOCATION_SOCKET_URL',
|
||||||
|
'INTERNAL_SOCKET_KEY_PATH',
|
||||||
|
'SECRET_KEY_PAY_PATH',
|
||||||
|
'SECRET_KEY_HMAC',
|
||||||
|
'allowed1',
|
||||||
|
'allowed2',
|
||||||
|
'passwordnewpassenger',
|
||||||
|
'FP_PEPPER'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($keysToCheck as $key) {
|
||||||
|
$val = getenv($key);
|
||||||
|
if ($val !== false && $val !== '') {
|
||||||
|
// إخفاء جزء من القيم الحساسة مثل كلمات المرور
|
||||||
|
if (strpos(strtolower($key), 'password') !== false || strpos(strtolower($key), 'secret') !== false || strpos(strtolower($key), 'hmac') !== false) {
|
||||||
|
$hiddenVal = substr($val, 0, 3) . '***' . substr($val, -3);
|
||||||
|
echo "[OK] $key = $hiddenVal\n";
|
||||||
|
} else {
|
||||||
|
echo "[OK] $key = $val\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "[ERROR] $key = (مفقود أو فارغ!)\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n\n=== فحص الملفات المباشرة ===\n\n";
|
||||||
|
|
||||||
|
$filesToCheck = [
|
||||||
|
'/home/intaleq-api/.internal_socket_key',
|
||||||
|
'/home/intaleq-api/.secret_key_pay'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($filesToCheck as $file) {
|
||||||
|
if (file_exists($file)) {
|
||||||
|
$content = trim(file_get_contents($file));
|
||||||
|
if (!empty($content)) {
|
||||||
|
$hidden = substr($content, 0, 3) . '***' . substr($content, -3);
|
||||||
|
echo "[OK] File ($file) exists and has content: $hidden\n";
|
||||||
|
} else {
|
||||||
|
echo "[WARNING] File ($file) exists but is EMPTY!\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "[ERROR] File ($file) DOES NOT EXIST!\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n=== انتهى الفحص ===\n";
|
||||||
78
backend/Admin/debug/ggg.php
Normal file
78
backend/Admin/debug/ggg.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
include 'connect.php';
|
||||||
|
|
||||||
|
// نضمن أن الرد دائماً JSON
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// 1) قراءة الـ body كـ JSON (من Flutter)
|
||||||
|
$raw = file_get_contents('php://input');
|
||||||
|
$data = json_decode($raw, true);
|
||||||
|
|
||||||
|
if (!is_array($data)) {
|
||||||
|
// fallback لو أرسلت form-data أو x-www-form-urlencoded
|
||||||
|
$data = $_POST;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) التحقق من رقم هاتف الأدمن المصرّح له
|
||||||
|
|
||||||
|
// قراءة الأرقام المسموح لها من الـ ENV
|
||||||
|
$phonesRaw = getenv('ADMIN_PHONE_NUMBERS') ?: '';
|
||||||
|
$ALLOWED_TOOL_PHONES = array_values(
|
||||||
|
array_filter(
|
||||||
|
array_map(function ($p) {
|
||||||
|
// إزالة أي رموز غير رقمية (مسافات، +، - إلخ)
|
||||||
|
return preg_replace('/\D+/', '', $p);
|
||||||
|
}, explode(',', $phonesRaw))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// رقم الهاتف القادم من Flutter (parameter جديد)
|
||||||
|
$adminPhoneParam = isset($data['admin_phone'])
|
||||||
|
? preg_replace('/\D+/', '', $data['admin_phone'])
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// إذا لم يُرسل رقم أو لم يكن ضمن القائمة → منع الوصول
|
||||||
|
if ($adminPhoneParam === '' || !in_array($adminPhoneParam, $ALLOWED_TOOL_PHONES, true)) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Access denied for this admin phone.',
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) التحقق من بقية المدخلات (action + text)
|
||||||
|
$action = $data['action'] ?? '';
|
||||||
|
$text = trim($data['text'] ?? '');
|
||||||
|
|
||||||
|
if ($text === '' || ($action !== 'encrypt' && $action !== 'decrypt')) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid input: need action=encrypt|decrypt and non-empty text.',
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) تنفيذ التشفير / الفك
|
||||||
|
try {
|
||||||
|
// require_once __DIR__ . '/encrypt_decrypt.php';
|
||||||
|
|
||||||
|
if ($action === 'encrypt') {
|
||||||
|
$result = $encryptionHelper->encryptData($text);
|
||||||
|
} else { // decrypt
|
||||||
|
$result = $encryptionHelper->decryptData($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'action' => $action,
|
||||||
|
'result' => (string) $result,
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Operation failed.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
23
backend/Admin/debug/scratch_db_check.php
Normal file
23
backend/Admin/debug/scratch_db_check.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'connect.php';
|
||||||
|
|
||||||
|
echo "--- ADMIN TABLE ---\n";
|
||||||
|
try {
|
||||||
|
$stmt = $con->prepare("SELECT id, name, role FROM admin");
|
||||||
|
$stmt->execute();
|
||||||
|
$admins = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
print_r($admins);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n--- DATABASES ---\n";
|
||||||
|
try {
|
||||||
|
$stmt = $con->prepare("SHOW DATABASES");
|
||||||
|
$stmt->execute();
|
||||||
|
$dbs = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
print_r($dbs);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
|
?>
|
||||||
2
backend/Admin/debug/scratch_log_path.php
Normal file
2
backend/Admin/debug/scratch_log_path.php
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?php
|
||||||
|
echo ini_get('error_log');
|
||||||
13
backend/Admin/debug/scratch_test_find.php
Normal file
13
backend/Admin/debug/scratch_test_find.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
require_once __DIR__ . '/../functions.php';
|
||||||
|
|
||||||
|
$con = Database::get('main');
|
||||||
|
$lat = 32.11171;
|
||||||
|
$lng = 36.06737;
|
||||||
|
$carType = 'Fixed Price';
|
||||||
|
|
||||||
|
echo "Testing findBestDrivers...\n";
|
||||||
|
$drivers = findBestDrivers($con, $lat, $lng, $carType);
|
||||||
|
print_r($drivers);
|
||||||
|
echo "Done.\n";
|
||||||
10
backend/Admin/debug/scratch_test_redis.php
Normal file
10
backend/Admin/debug/scratch_test_redis.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
$redis = getRedis(); // or however it's connected in bootstrap
|
||||||
|
if (!$redis) {
|
||||||
|
echo "No redis\n"; exit;
|
||||||
|
}
|
||||||
|
$redis->geoadd('geo:rides:waiting', 36.0, 32.0, 'test_ride');
|
||||||
|
$res = $redis->georadius('geo:rides:waiting', 36.0, 32.0, 10, 'km', ['WITHDIST' => true]);
|
||||||
|
print_r($res);
|
||||||
|
echo json_encode($res) . "\n";
|
||||||
41
backend/Admin/driver/deleteCaptain.php
Executable file
41
backend/Admin/driver/deleteCaptain.php
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$driver_id = filterRequest("driver_id");
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
$reason = filterRequest("reason"); // يمكن أن يأتي من البارامتر أو نخليه افتراضي
|
||||||
|
|
||||||
|
if (empty($driver_id) || empty($phone)) {
|
||||||
|
jsonError("Driver ID and phone are required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// تشفير رقم الهاتف
|
||||||
|
$encPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
|
||||||
|
// حذف السائق من جدول driver
|
||||||
|
$sqlDel = "DELETE FROM driver WHERE id = :id";
|
||||||
|
$stmtDel = $con->prepare($sqlDel);
|
||||||
|
$stmtDel->bindParam(':id', $driver_id, PDO::PARAM_INT);
|
||||||
|
$stmtDel->execute();
|
||||||
|
|
||||||
|
if ($stmtDel->rowCount() > 0) {
|
||||||
|
// إضافة بيانات السائق المحذوف إلى البلاك ليست
|
||||||
|
$sqlInsert = "INSERT INTO blacklist_driver (driver_id, phone, reason)
|
||||||
|
VALUES (:driver_id, :phone, :reason)";
|
||||||
|
$stmtInsert = $con->prepare($sqlInsert);
|
||||||
|
$stmtInsert->execute([
|
||||||
|
'driver_id' => $driver_id,
|
||||||
|
'phone' => $encPhone,
|
||||||
|
'reason' => !empty($reason) ? $reason : "Deleted & blacklisted by admin"
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonSuccess(null, "Driver deleted and blacklisted successfully.");
|
||||||
|
} else {
|
||||||
|
jsonError("No driver found with the provided ID.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
jsonError("Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
30
backend/Admin/driver/deleteRecord.php
Executable file
30
backend/Admin/driver/deleteRecord.php
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$driver_id = filterRequest("driver_id");
|
||||||
|
|
||||||
|
// Prepare the DELETE query
|
||||||
|
$sql = "DELETE FROM `car_locations` WHERE driver_id = :driver_id";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
|
||||||
|
// Bind the driver_id parameter
|
||||||
|
$stmt->bindParam(':driver_id', $driver_id, PDO::PARAM_STR);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Execute the query
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// Success response
|
||||||
|
jsonSuccess(null, "Record(s) deleted successfully.");
|
||||||
|
} else {
|
||||||
|
// Failure response: no records found to delete
|
||||||
|
jsonError("No records found for the provided driver ID.");
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Handle any SQL errors
|
||||||
|
jsonError("Error deleting records: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
55
backend/Admin/driver/find_driver_by_phone.php
Executable file
55
backend/Admin/driver/find_driver_by_phone.php
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
|
||||||
|
if (empty($phone)) {
|
||||||
|
jsonError("Phone number is required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// تشفير الرقم المدخل للبحث
|
||||||
|
$encPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
|
||||||
|
// احضار كل الأعمدة باستثناء كلمة المرور
|
||||||
|
$sql = "SELECT *
|
||||||
|
FROM driver
|
||||||
|
WHERE phone = :phone
|
||||||
|
LIMIT 1";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([':phone' => $encPhone]);
|
||||||
|
|
||||||
|
$driver = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($driver) {
|
||||||
|
// ✅ الحقول المشفرة اللي لازم تنفك:
|
||||||
|
$encryptedFields = [
|
||||||
|
'phone',
|
||||||
|
'email',
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'national_number',
|
||||||
|
'address','gender','site',
|
||||||
|
'birthdate',
|
||||||
|
'name_arabic',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($encryptedFields as $field) {
|
||||||
|
if (!empty($driver[$field])) {
|
||||||
|
$driver[$field] = $encryptionHelper->decryptData($driver[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ احذف كلمة المرور من النتيجة
|
||||||
|
unset($driver['password']);
|
||||||
|
|
||||||
|
jsonSuccess($driver);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
jsonError("No driver found with this phone.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
jsonError("Error searching driver: " . $e->getMessage());
|
||||||
|
}
|
||||||
48
backend/Admin/driver/getBestDriver.php
Executable file
48
backend/Admin/driver/getBestDriver.php
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
COUNT(`car_locations`.driver_id) AS driver_count,
|
||||||
|
driver.id,
|
||||||
|
driver.phone,
|
||||||
|
driver.name_arabic,
|
||||||
|
MAX(dt.token) AS token
|
||||||
|
FROM
|
||||||
|
`car_locations`
|
||||||
|
LEFT JOIN driver ON driver.id = car_locations.driver_id
|
||||||
|
LEFT JOIN driverToken dt ON dt.captain_id = driver.id
|
||||||
|
WHERE
|
||||||
|
`car_locations`.created_at > TIMESTAMP(DATE_SUB(NOW(), INTERVAL 7 DAY))
|
||||||
|
GROUP BY
|
||||||
|
driver.id
|
||||||
|
ORDER BY
|
||||||
|
driver_count DESC
|
||||||
|
LIMIT 19;
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك التشفير للحقول الحساسة
|
||||||
|
foreach ($rows as &$row) {
|
||||||
|
if (!empty($row['phone'])) {
|
||||||
|
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||||
|
}
|
||||||
|
if (!empty($row['name_arabic'])) {
|
||||||
|
$row['name_arabic'] = $encryptionHelper->decryptData($row['name_arabic']);
|
||||||
|
}
|
||||||
|
if (!empty($row['token'])) {
|
||||||
|
$row['token'] = $encryptionHelper->decryptData($row['token']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonSuccess($rows);
|
||||||
|
} else {
|
||||||
|
jsonError($message = "No recent driver location activity found");
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
71
backend/Admin/driver/getDriverGiftPayment.php
Executable file
71
backend/Admin/driver/getDriverGiftPayment.php
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
|
||||||
|
// تنظيف الرقم من أي مسافات أو رموز زائدة
|
||||||
|
$phone = preg_replace('/[^0-9]/', '', $phone);
|
||||||
|
|
||||||
|
// احتمالات الرقم (بالصفر الدولي أو بدونه)
|
||||||
|
$phoneVariants = [];
|
||||||
|
$phoneVariants[] = $phone; // كما هو (مثلاً 0992952235)
|
||||||
|
|
||||||
|
if (str_starts_with($phone, '0')) {
|
||||||
|
$phoneVariants[] = '963' . substr($phone, 1); // تحويل 09 إلى 9639
|
||||||
|
} elseif (str_starts_with($phone, '963')) {
|
||||||
|
$phoneVariants[] = '0' . substr($phone, 3); // تحويل 9639 إلى 09
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt each variant to see if any match the encrypted column
|
||||||
|
$encVariants = [];
|
||||||
|
foreach ($phoneVariants as $v) {
|
||||||
|
$encVariants[] = $encryptionHelper->encryptData($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("[GIFT_CHECK] Received Phone: " . $phone);
|
||||||
|
error_log("[GIFT_CHECK] Variants: " . implode(', ', $phoneVariants));
|
||||||
|
|
||||||
|
// بناء استعلام يبحث عن كل الاحتمالات (المشفرة وغير المشفرة)
|
||||||
|
$placeholders = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($encVariants as $i => $ev) {
|
||||||
|
$placeholders[] = "phone = :enc$i";
|
||||||
|
$params[":enc$i"] = $ev;
|
||||||
|
}
|
||||||
|
foreach ($phoneVariants as $i => $pv) {
|
||||||
|
$placeholders[] = "phone = :raw$i";
|
||||||
|
$params[":raw$i"] = $pv;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM `driver` WHERE " . implode(" OR ", $placeholders);
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
foreach ($params as $key => $val) {
|
||||||
|
$stmt->bindValue($key, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Decrypt sensitive fields
|
||||||
|
foreach ($rows as &$row) {
|
||||||
|
if (!empty($row['phone'])) {
|
||||||
|
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||||
|
}
|
||||||
|
if (!empty($row['name_arabic'])) {
|
||||||
|
$row['name_arabic'] = $encryptionHelper->decryptData($row['name_arabic']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonSuccess($rows);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
jsonError("No recent driver location activity found");
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
27
backend/Admin/driver/remove_from_blacklist.php
Executable file
27
backend/Admin/driver/remove_from_blacklist.php
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
|
||||||
|
if (empty($phone)) {
|
||||||
|
jsonError("Phone number is required.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// تشفير الرقم للمطابقة مع المخزن
|
||||||
|
$encPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
|
||||||
|
$sql = "DELETE FROM blacklist_driver WHERE phone = :phone";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([':phone' => $encPhone]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess(null, "Driver removed from blacklist successfully.");
|
||||||
|
} else {
|
||||||
|
jsonError("No driver found in blacklist with this phone.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
jsonError("Error removing from blacklist: " . $e->getMessage());
|
||||||
|
}
|
||||||
31
backend/Admin/driver/updateDriverFromAdmin.php
Executable file
31
backend/Admin/driver/updateDriverFromAdmin.php
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$driver_id = filterRequest("id");
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
|
||||||
|
// تشفير رقم الهاتف
|
||||||
|
$encphone = $encryptionHelper->encryptData($phone);
|
||||||
|
|
||||||
|
$sql = "UPDATE `driver` SET `phone` = :encphone WHERE `id` = :id";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
|
||||||
|
// Bind values
|
||||||
|
$stmt->bindParam(':encphone', $encphone, PDO::PARAM_STR);
|
||||||
|
$stmt->bindParam(':id', $driver_id, PDO::PARAM_STR);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// تم التحديث بنجاح
|
||||||
|
logAudit($con, $user_id, "تعديل رقم هاتف سائق", "driver", $driver_id, ["phone" => $phone]);
|
||||||
|
jsonSuccess(null, "Phone updated successfully.");
|
||||||
|
} else {
|
||||||
|
// لم يتم العثور على أي سجل للتحديث
|
||||||
|
jsonError("No records updated. Please check the driver ID.");
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
jsonError("Error updating record: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
31
backend/Admin/employee/add.php
Executable file
31
backend/Admin/employee/add.php
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
// Get the data from the request
|
||||||
|
$name = filterRequest("name");
|
||||||
|
$education = filterRequest("education");
|
||||||
|
$site = filterRequest("site");
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
$status = filterRequest("status");
|
||||||
|
$id=filterRequest("id");
|
||||||
|
|
||||||
|
// Set the current timestamp for the 'created_at' field
|
||||||
|
$created_at = date("Y-m-d H:i:s");
|
||||||
|
|
||||||
|
// Prepare the SQL insert query using parameterized statements to avoid SQL injection
|
||||||
|
$sql = "INSERT INTO `employee` (`id`,`name`, `education`, `site`, `phone`, `created_at`, `status`)
|
||||||
|
VALUES (?,?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
|
// Prepare and execute the statement
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([$id, $name, $education, $site, $phone, $created_at, $status]);
|
||||||
|
|
||||||
|
// Check if the query successfully inserted the record
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// If a row was inserted, print success
|
||||||
|
jsonSuccess($message = "Employee record added successfully");
|
||||||
|
} else {
|
||||||
|
// If no rows were inserted, print failure
|
||||||
|
jsonError($message = "Failed to add employee record");
|
||||||
|
}
|
||||||
|
?>
|
||||||
29
backend/Admin/employee/get.php
Executable file
29
backend/Admin/employee/get.php
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
// Prepare the SQL query to select all records from the employee table with a limit of 10
|
||||||
|
$sql = "SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
`employee` e
|
||||||
|
ORDER BY
|
||||||
|
e.created_at
|
||||||
|
DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
// Prepare and execute the statement
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
// Fetch all records as an associative array
|
||||||
|
$employee_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Check if any records were retrieved
|
||||||
|
if ($employee_data) {
|
||||||
|
// If records were found, print the data as JSON
|
||||||
|
jsonSuccess($data = $employee_data);
|
||||||
|
} else {
|
||||||
|
// If no records were found, print a failure message
|
||||||
|
jsonError($message = "No employee records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
18
backend/Admin/error/error_list_last20.php
Executable file
18
backend/Admin/error/error_list_last20.php
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
// File: /v1/admin/error/error_list_last20.php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "SELECT `id`, `error`, `userId`, `userType`, `phone`, `created_at`, `device`, `details`, `status`
|
||||||
|
FROM `error`
|
||||||
|
ORDER BY `created_at` DESC
|
||||||
|
LIMIT 20";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
jsonSuccess($rows);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("error_list_last20.php: " . $e->getMessage());
|
||||||
|
jsonError($message = "Failed to fetch last 20 errors");
|
||||||
|
}
|
||||||
32
backend/Admin/error/error_search_by_phone.php
Executable file
32
backend/Admin/error/error_search_by_phone.php
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
// File: /v1/admin/error/error_search_by_phone.php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
if ($phone === false || $phone === null || trim($phone) === "") {
|
||||||
|
jsonError("Phone is required");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// في حال مخزّن الهاتف مشفّر، طبق نفس دالتك للتشفير هنا:
|
||||||
|
// $enc_phone = $encryptionHelper->encryptData(trim($phone));
|
||||||
|
// ثم بدّل الحقل في WHERE إلى phone = :ph
|
||||||
|
$sql = "SELECT `id`, `error`, `userId`, `userType`, `phone`, `created_at`, `device`, `details`, `status`
|
||||||
|
FROM `error`
|
||||||
|
WHERE `phone` = :ph OR `phone` LIKE :phLike
|
||||||
|
ORDER BY `created_at` DESC
|
||||||
|
LIMIT 20";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute([
|
||||||
|
":ph" => trim($phone),
|
||||||
|
":phLike" => '%' . trim($phone) . '%', // يسمح بجزء من الرقم إن أردت
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
jsonSuccess($rows);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("error_search_by_phone.php: " . $e->getMessage());
|
||||||
|
jsonError($message = "Failed to search errors by phone");
|
||||||
|
}
|
||||||
40
backend/Admin/errorApp.php
Executable file
40
backend/Admin/errorApp.php
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// استلام البيانات من الطلب
|
||||||
|
$error = filterRequest("error");
|
||||||
|
$userId = filterRequest("userId");
|
||||||
|
$userType = filterRequest("userType");
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
$device = filterRequest("device");
|
||||||
|
$details = filterRequest("details");
|
||||||
|
|
||||||
|
// تسجيل الخطأ في ملف logs/app.log للمتابعة السريعة
|
||||||
|
$logMsg = "[$userType ID: $userId] Error: $error | Where: $device | Details: $details";
|
||||||
|
appLog($logMsg, "APP_ERROR");
|
||||||
|
|
||||||
|
// جملة SQL لإدخال البيانات، مع إضافة الحقل الجديد
|
||||||
|
// لاحظ أننا لا نرسل حقل 'status' لأنه سيأخذ القيمة الافتراضية 'new' تلقائياً في قاعدة البيانات
|
||||||
|
$sql = "INSERT INTO `error` (`error`, `userId`, `userType`, `phone`, `device`, `details`)
|
||||||
|
VALUES (:error, :userId, :userType, :phone, :device, :details)";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
|
||||||
|
// ربط المتغيرات بالقيم
|
||||||
|
$stmt->bindParam(':error', $error);
|
||||||
|
$stmt->bindParam(':userId', $userId);
|
||||||
|
$stmt->bindParam(':userType', $userType);
|
||||||
|
$stmt->bindParam(':phone', $phone);
|
||||||
|
$stmt->bindParam(':device', $device);
|
||||||
|
$stmt->bindParam(':details', $details); // <-- ربط المتغير الجديد
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// طباعة رسالة نجاح مع تفاصيل الخطأ لسهولة التتبع في الكونسول
|
||||||
|
jsonSuccess($error);
|
||||||
|
} else {
|
||||||
|
// طباعة رسالة فشل
|
||||||
|
jsonError("Failed to save error data");
|
||||||
|
}
|
||||||
|
?>
|
||||||
0
backend/Admin/error_log
Normal file
0
backend/Admin/error_log
Normal file
37
backend/Admin/facebook.php
Executable file
37
backend/Admin/facebook.php
Executable file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'vendor/autoload.php'; // Include the Composer autoloader
|
||||||
|
|
||||||
|
use Facebook\Facebook;
|
||||||
|
|
||||||
|
$appId = '$appId'; // Replace with your App ID
|
||||||
|
$appSecret = '$appSecret'; // Replace with your App Secret
|
||||||
|
$accessToken = '$accessToken'; // Replace with the token you want to debug
|
||||||
|
|
||||||
|
$fb = new Facebook([
|
||||||
|
'app_id' => $appId,
|
||||||
|
'app_secret' => $appSecret,
|
||||||
|
'default_graph_version' => 'v16.0', // Adjust based on your API version
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Generate the app token
|
||||||
|
$appToken = $appId . '|' . $appSecret;
|
||||||
|
|
||||||
|
// Debug the token
|
||||||
|
$response = $fb->get('/debug_token?input_token=' . $accessToken, $appToken);
|
||||||
|
$tokenData = $response->getDecodedBody();
|
||||||
|
|
||||||
|
// Display the token details
|
||||||
|
echo "Token Data:\n";
|
||||||
|
print_r($tokenData);
|
||||||
|
|
||||||
|
if (isset($tokenData['data']['expires_at'])) {
|
||||||
|
echo "Expires At: " . date('Y-m-d H:i:s', $tokenData['data']['expires_at']) . "\n";
|
||||||
|
} else {
|
||||||
|
echo "The token does not have an expiration time.\n";
|
||||||
|
}
|
||||||
|
} catch (Facebook\Exceptions\FacebookResponseException $e) {
|
||||||
|
echo 'Graph API Error: ' . $e->getMessage();
|
||||||
|
} catch (Facebook\Exceptions\FacebookSDKException $e) {
|
||||||
|
echo 'SDK Error: ' . $e->getMessage();
|
||||||
|
}
|
||||||
100
backend/Admin/getPassengerDetails.php
Normal file
100
backend/Admin/getPassengerDetails.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
`passengers`.`id`,
|
||||||
|
`passengers`.`phone`,
|
||||||
|
`passengers`.`email`,
|
||||||
|
`passengers`.`gender`,
|
||||||
|
`passengers`.`status`,
|
||||||
|
`passengers`.`birthdate`,
|
||||||
|
`passengers`.`site`,
|
||||||
|
`passengers`.`first_name`,
|
||||||
|
`passengers`.`last_name`,
|
||||||
|
`passengers`.`sosPhone`,
|
||||||
|
`passengers`.`education`,
|
||||||
|
`passengers`.`employmentType`,
|
||||||
|
`passengers`.`maritalStatus`,
|
||||||
|
`passengers`.`created_at`,
|
||||||
|
`passengers`.`updated_at`,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`id`) FROM `passengers`
|
||||||
|
) AS countPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`id`) FROM `feedBack`
|
||||||
|
) AS countFeedback,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10,2))
|
||||||
|
FROM `ratingPassenger`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS ratingPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`driverID`)
|
||||||
|
FROM `ratingPassenger`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS countDriverRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`passengerID`)
|
||||||
|
FROM `canecl`
|
||||||
|
WHERE `passengerID` = `passengers`.`id`
|
||||||
|
) AS countPassengerCancel,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10,2))
|
||||||
|
FROM `ratingDriver`
|
||||||
|
WHERE `passenger_iD` = `passengers`.`id`
|
||||||
|
) AS passengerAverageRating,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`driver_id`)
|
||||||
|
FROM `ratingDriver`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS countPassengerRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`ride`.`passenger_id`)
|
||||||
|
FROM `ride`
|
||||||
|
WHERE `ride`.`passenger_id` = `passengers`.`id`
|
||||||
|
) AS countPassengerRide,
|
||||||
|
(
|
||||||
|
SELECT `token`
|
||||||
|
FROM `tokens`
|
||||||
|
WHERE `tokens`.`passengerID` = `passengers`.`id`
|
||||||
|
) AS passengerToken
|
||||||
|
FROM
|
||||||
|
`passengers`
|
||||||
|
GROUP BY
|
||||||
|
`passengers`.`id`
|
||||||
|
ORDER BY
|
||||||
|
countPassengerRide DESC
|
||||||
|
LIMIT 10";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// ✅ فك التشفير للحقول الحساسة
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
$fieldsToDecrypt = [
|
||||||
|
"phone", "email", "gender", "birthdate", "site",
|
||||||
|
"first_name", "last_name", "sosPhone",
|
||||||
|
"education", "employmentType", "maritalStatus", "passengerToken"
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($fieldsToDecrypt as $field) {
|
||||||
|
if (isset($row[$field]) && $row[$field] !== null) {
|
||||||
|
$decrypted = $encryptionHelper->decryptData($row[$field]);
|
||||||
|
if ($decrypted !== false) {
|
||||||
|
$row[$field] = $decrypted;
|
||||||
|
} else {
|
||||||
|
// سجل أو تجاهل القيم التي فشل فك تشفيرها
|
||||||
|
$row[$field] = null; // أو احتفظ بالقيمة المشفرة
|
||||||
|
error_log("Failed to decrypt field '$field' for passenger ID: " . $row['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess($data = $result);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
96
backend/Admin/getPassengerDetailsByPassengerID.php
Normal file
96
backend/Admin/getPassengerDetailsByPassengerID.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
$passengerID = filterRequest("passengerID");
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
`passengers`.`id`,
|
||||||
|
`passengers`.`phone`,
|
||||||
|
`passengers`.`email`,
|
||||||
|
`passengers`.`gender`,
|
||||||
|
`passengers`.`status`,
|
||||||
|
`passengers`.`birthdate`,
|
||||||
|
`passengers`.`site`,
|
||||||
|
`passengers`.`first_name`,
|
||||||
|
`passengers`.`last_name`,
|
||||||
|
`passengers`.`sosPhone`,
|
||||||
|
`passengers`.`education`,
|
||||||
|
`passengers`.`employmentType`,
|
||||||
|
`passengers`.`maritalStatus`,
|
||||||
|
`passengers`.`created_at`,
|
||||||
|
`passengers`.`updated_at`,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`id`) FROM `passengers`
|
||||||
|
) AS countPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`id`) FROM `feedBack`
|
||||||
|
) AS countFeedback,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10,2))
|
||||||
|
FROM `ratingPassenger`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS ratingPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`driverID`)
|
||||||
|
FROM `ratingPassenger`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS countDriverRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`passengerID`)
|
||||||
|
FROM `canecl`
|
||||||
|
WHERE `passengerID` = `passengers`.`id`
|
||||||
|
) AS countPassengerCancel,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10,2))
|
||||||
|
FROM `ratingDriver`
|
||||||
|
WHERE `passenger_iD` = `passengers`.`id`
|
||||||
|
) AS passengerAverageRating,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`driver_id`)
|
||||||
|
FROM `ratingDriver`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS countPassengerRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`ride`.`passenger_id`)
|
||||||
|
FROM `ride`
|
||||||
|
WHERE `ride`.`passenger_id` = `passengers`.`id`
|
||||||
|
) AS countPassengerRide,
|
||||||
|
(
|
||||||
|
SELECT `token`
|
||||||
|
FROM `tokens`
|
||||||
|
WHERE `tokens`.`passengerID` = `passengers`.`id`
|
||||||
|
) AS passengerToken
|
||||||
|
FROM
|
||||||
|
`passengers`
|
||||||
|
WHERE
|
||||||
|
passengers.id = '$passengerID'
|
||||||
|
GROUP BY
|
||||||
|
`passengers`.`id`
|
||||||
|
ORDER BY
|
||||||
|
countPassengerRide DESC";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// ✅ فك تشفير الحقول الحساسة
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
$fieldsToDecrypt = [
|
||||||
|
"phone", "email", "gender", "birthdate", "site",
|
||||||
|
"first_name", "last_name", "sosPhone",
|
||||||
|
"education", "employmentType", "maritalStatus", "passengerToken"
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($fieldsToDecrypt as $field) {
|
||||||
|
if (isset($row[$field]) && $row[$field] !== null) {
|
||||||
|
$row[$field] = $encryptionHelper->decryptData($row[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess($result);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
91
backend/Admin/getPassengerbyEmail.php
Normal file
91
backend/Admin/getPassengerbyEmail.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
$passengerEmail = $encryptionHelper->encryptData(filterRequest("passengerEmail"));
|
||||||
|
$passengerId = filterRequest("passengerId");
|
||||||
|
$passengerphone = $encryptionHelper->encryptData(filterRequest("passengerphone"));
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
`passengers`.`id`,
|
||||||
|
`passengers`.`phone`,
|
||||||
|
`passengers`.`email`,
|
||||||
|
`passengers`.`gender`,
|
||||||
|
`passengers`.`status`,
|
||||||
|
`passengers`.`birthdate`,
|
||||||
|
`passengers`.`site`,
|
||||||
|
`passengers`.`first_name`,
|
||||||
|
`passengers`.`last_name`,
|
||||||
|
`passengers`.`sosPhone`,
|
||||||
|
`passengers`.`education`,
|
||||||
|
`passengers`.`employmentType`,
|
||||||
|
`passengers`.`maritalStatus`,
|
||||||
|
`passengers`.`created_at`,
|
||||||
|
`passengers`.`updated_at`,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`id`) FROM `passengers`
|
||||||
|
) AS countPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`id`) FROM `feedBack`
|
||||||
|
) AS countFeedback,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10, 2)) FROM `ratingPassenger`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS ratingPassenger,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`driverID`) FROM `ratingPassenger`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS countDriverRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`passengerID`) FROM `canecl`
|
||||||
|
WHERE `passengerID` = `passengers`.`id`
|
||||||
|
) AS countPassengerCancel,
|
||||||
|
(
|
||||||
|
SELECT CAST(AVG(`rating`) AS DECIMAL(10, 2)) FROM `ratingDriver`
|
||||||
|
WHERE `passenger_iD` = `passengers`.`id`
|
||||||
|
) AS passengerAverageRating,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`driver_id`) FROM `ratingDriver`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS countPassengerRate,
|
||||||
|
(
|
||||||
|
SELECT COUNT(`passenger_id`) FROM `ride`
|
||||||
|
WHERE `passenger_id` = `passengers`.`id`
|
||||||
|
) AS countPassengerRide,
|
||||||
|
(
|
||||||
|
SELECT `token` FROM `tokens`
|
||||||
|
WHERE `passengerID` = `passengers`.`id`
|
||||||
|
) AS passengerToken
|
||||||
|
FROM
|
||||||
|
`passengers`
|
||||||
|
WHERE
|
||||||
|
passengers.email = :email OR passengers.phone = :phone OR passengers.id = :id
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->bindParam(":email", $passengerEmail);
|
||||||
|
$stmt->bindParam(":phone", $passengerphone);
|
||||||
|
$stmt->bindParam(":id", $passengerId);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك التشفير للحقول الحساسة
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
$fieldsToDecrypt = [
|
||||||
|
"phone", "email", "gender", "birthdate", "site",
|
||||||
|
"first_name", "last_name", "sosPhone",
|
||||||
|
"education", "employmentType", "maritalStatus"
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($fieldsToDecrypt as $field) {
|
||||||
|
if (isset($row[$field])) {
|
||||||
|
$row[$field] = $encryptionHelper->decryptData($row[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess($data = $result);
|
||||||
|
} else {
|
||||||
|
jsonError("No records found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
48
backend/Admin/getVisaForEachDriver.php
Normal file
48
backend/Admin/getVisaForEachDriver.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// جلب البيانات
|
||||||
|
$sql = "SELECT
|
||||||
|
COUNT(DISTINCT driverID) AS driver_count,
|
||||||
|
`payments`.driverID,
|
||||||
|
COALESCE(SUM(amount), 0) AS total_amount,
|
||||||
|
`driver`.`phone`,
|
||||||
|
`driver`.`name_arabic`,
|
||||||
|
`driver`.`accountBank`,
|
||||||
|
`driver`.`bankCode`,
|
||||||
|
`driver`.`email`
|
||||||
|
FROM
|
||||||
|
payments
|
||||||
|
LEFT JOIN `driver` ON `driver`.`id` = payments.driverID
|
||||||
|
WHERE
|
||||||
|
isGiven = 'waiting' AND payment_method IN(
|
||||||
|
'visa-in', 'visa', 'visaRide', 'TransferFrom',
|
||||||
|
'payout', 'TransferTo', 'payFromSeferToDriver'
|
||||||
|
)
|
||||||
|
AND WEEK(`payments`.created_at) = WEEK(CURRENT_DATE)
|
||||||
|
GROUP BY
|
||||||
|
driverID
|
||||||
|
HAVING
|
||||||
|
COALESCE(SUM(amount), 0) > 0 AND total_amount > 100
|
||||||
|
LIMIT 0, 25";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك التشفير للحقول المطلوبة
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
$fieldsToDecrypt = ['phone', 'email', 'accountBank', 'bankCode', 'name_arabic'];
|
||||||
|
foreach ($fieldsToDecrypt as $field) {
|
||||||
|
if (isset($row[$field]) && $row[$field] !== null) {
|
||||||
|
$row[$field] = $encryptionHelper->decryptData($row[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
jsonSuccess($result);
|
||||||
|
} else {
|
||||||
|
jsonError("No wallet record found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
53
backend/Admin/ggg.php
Normal file
53
backend/Admin/ggg.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
// ============================================================
|
||||||
|
// Admin/ggg.php
|
||||||
|
// أداة تشفير وفك تشفير للمشرفين
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
|
||||||
|
// نضمن أن الرد دائماً JSON
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// 1) قراءة الـ body كـ JSON أو POST
|
||||||
|
$action = filterRequest('action');
|
||||||
|
$text = filterRequest('text');
|
||||||
|
$adminPhoneParam = filterRequest('admin_phone');
|
||||||
|
|
||||||
|
// 2) التحقق من رقم هاتف الأدمن المصرّح له
|
||||||
|
$phonesRaw = getenv('ADMIN_PHONE_NUMBERS') ?: '';
|
||||||
|
$ALLOWED_TOOL_PHONES = array_values(
|
||||||
|
array_filter(
|
||||||
|
array_map(function ($p) {
|
||||||
|
return preg_replace('/\D+/', '', $p);
|
||||||
|
}, explode(',', $phonesRaw))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$adminPhoneParam = $adminPhoneParam ? preg_replace('/\D+/', '', $adminPhoneParam) : '';
|
||||||
|
|
||||||
|
if ($adminPhoneParam === '' || !in_array($adminPhoneParam, $ALLOWED_TOOL_PHONES, true)) {
|
||||||
|
securityLog("Unauthorized encrypt/decrypt attempt", ['phone' => $adminPhoneParam]);
|
||||||
|
jsonError('Access denied for this admin phone.', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($text) || ($action !== 'encrypt' && $action !== 'decrypt')) {
|
||||||
|
jsonError('Invalid input: need action=encrypt|decrypt and non-empty text.', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) تنفيذ التشفير / الفك (التوافق مع CBC الحالي)
|
||||||
|
try {
|
||||||
|
if ($action === 'encrypt') {
|
||||||
|
$result = $encryptionHelper->encryptData($text);
|
||||||
|
} else { // decrypt
|
||||||
|
$result = $encryptionHelper->decryptData($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonSuccess([
|
||||||
|
'action' => $action,
|
||||||
|
'result' => (string) $result,
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
securityLog("Encryption tool failed", ['error' => $e->getMessage()]);
|
||||||
|
jsonError('Operation failed.', 500);
|
||||||
|
}
|
||||||
77
backend/Admin/jwtService.php
Executable file
77
backend/Admin/jwtService.php
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
// ============================================================
|
||||||
|
// Admin/jwtService.php (Customer Service Login)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
header("Access-Control-Allow-Origin: https://intaleqapp.com");
|
||||||
|
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rate Limiting ───────────────────────────────────────────
|
||||||
|
$limiter = new RateLimiter($redis);
|
||||||
|
$limiter->enforce(RateLimiter::identifier(), 'login');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$email = filterRequest('email') ?? '';
|
||||||
|
$password = filterRequest('password') ?? '';
|
||||||
|
$audience = filterRequest('aud') ?? '';
|
||||||
|
|
||||||
|
$allowed1 = getenv('allowedService1');
|
||||||
|
$allowed2 = getenv('allowedService2');
|
||||||
|
$allowedAudiences = array_values(array_filter([$allowed1, $allowed2]));
|
||||||
|
|
||||||
|
if (empty($email) || empty($password) || empty($audience)) {
|
||||||
|
jsonError('Email and password are required.', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($audience, $allowedAudiences, true)) {
|
||||||
|
jsonError('Invalid audience', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// استخدام user table ويفضل استخدام password_hash لاحقا مثل admin_users
|
||||||
|
$stmt = $con->prepare("SELECT `id`, `password`, `email` FROM `users` WHERE email = :email LIMIT 1");
|
||||||
|
$stmt->execute([':email' => $email]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
$startTime = microtime(true);
|
||||||
|
|
||||||
|
// دعم password_verify مع البقاء على التوافق مع كلمات السر القديمة (Plain Text)
|
||||||
|
if ($user && (password_verify($password, $user['password']) || $user['password'] === $password)) {
|
||||||
|
|
||||||
|
$limiter->reset(RateLimiter::identifier(), 'login');
|
||||||
|
|
||||||
|
$jwtService = new JwtService($redis);
|
||||||
|
$jwt = $jwtService->generateAccessToken($user['id'], 'service', $audience);
|
||||||
|
$refresh = $jwtService->generateRefreshToken($user['id']);
|
||||||
|
|
||||||
|
jsonSuccess([
|
||||||
|
'jwt' => $jwt,
|
||||||
|
'refresh_token' => $refresh['token'],
|
||||||
|
'expires_in' => 900 // أو 6600 كما كان في الكود الأصلي
|
||||||
|
]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$elapsed = microtime(true) - $startTime;
|
||||||
|
if ($elapsed < 0.1) usleep((int)((0.1 - $elapsed) * 1000000));
|
||||||
|
|
||||||
|
securityLog("Service login failed", ['email' => $email]);
|
||||||
|
jsonError('Invalid email or password', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
securityLog("Service Login PDO Error", ['msg' => $e->getMessage()]);
|
||||||
|
jsonError('Login failed: Database error', 500);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
securityLog("Service Login Error", ['msg' => $e->getMessage()]);
|
||||||
|
jsonError('Login failed: Server error', 500);
|
||||||
|
}
|
||||||
52
backend/Admin/passenger/admin_delete_and_blacklist_passenger.php
Executable file
52
backend/Admin/passenger/admin_delete_and_blacklist_passenger.php
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
function normalize_phone($s) { return preg_replace('/\D+/', '', (string)$s); }
|
||||||
|
|
||||||
|
$id = filterRequest("id"); // أو
|
||||||
|
$phone = filterRequest("phone"); // أحدهما مطلوب
|
||||||
|
$reason= filterRequest("reason"); // اختياري
|
||||||
|
$exp = filterRequest("expires_at"); // اختياري Y-m-d H:i:s
|
||||||
|
|
||||||
|
if (empty($id) && empty($phone)) { jsonError("Provide id or phone"); exit; }
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con->beginTransaction();
|
||||||
|
|
||||||
|
// احضر السجل
|
||||||
|
if (!empty($id)) {
|
||||||
|
$sel = $con->prepare("SELECT id, phone FROM passengers WHERE id = :id LIMIT 1");
|
||||||
|
$sel->execute(['id' => $id]);
|
||||||
|
} else {
|
||||||
|
$sel = $con->prepare("SELECT id, phone FROM passengers WHERE phone = :ph LIMIT 1");
|
||||||
|
$sel->execute(['ph' => $phone]);
|
||||||
|
}
|
||||||
|
$p = $sel->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$p) { throw new Exception("Passenger not found"); }
|
||||||
|
|
||||||
|
$phRaw = $p['phone'];
|
||||||
|
$phNorm= normalize_phone($phRaw);
|
||||||
|
|
||||||
|
// أدخِل/حدّث في البلاك ليست
|
||||||
|
$ins = $con->prepare("
|
||||||
|
INSERT INTO passenger_blacklist (phone, phone_normalized, reason, expires_at)
|
||||||
|
VALUES (:ph, :phn, :r, :exp)
|
||||||
|
ON DUPLICATE KEY UPDATE reason = VALUES(reason), expires_at = VALUES(expires_at)
|
||||||
|
");
|
||||||
|
$ins->execute([
|
||||||
|
'ph' => $phRaw,
|
||||||
|
'phn' => $phNorm,
|
||||||
|
'r' => $reason ?: 'Deleted & blacklisted',
|
||||||
|
'exp' => $exp ?: null
|
||||||
|
]);
|
||||||
|
|
||||||
|
// حذف فعلي
|
||||||
|
$del = $con->prepare("DELETE FROM passengers WHERE id = :id");
|
||||||
|
$del->execute(['id' => $p['id']]);
|
||||||
|
|
||||||
|
$con->commit();
|
||||||
|
jsonSuccess(null, "Passenger deleted and blacklisted");
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$con->rollBack();
|
||||||
|
jsonError("Failed: ".$e->getMessage());
|
||||||
|
}
|
||||||
14
backend/Admin/passenger/admin_unblacklist.php
Executable file
14
backend/Admin/passenger/admin_unblacklist.php
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
function normalize_phone($s) { return preg_replace('/\D+/', '', (string)$s); }
|
||||||
|
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
if (empty($phone)) { jsonError("phone is required"); exit; }
|
||||||
|
|
||||||
|
$phn = normalize_phone($phone);
|
||||||
|
$stmt = $con->prepare("DELETE FROM passenger_blacklist WHERE phone_normalized = :phn");
|
||||||
|
$stmt->execute(['phn' => $phn]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) { jsonSuccess(null, "Removed from blacklist"); }
|
||||||
|
else { jsonError("Phone was not blacklisted"); }
|
||||||
50
backend/Admin/passenger/admin_update_passenger.php
Executable file
50
backend/Admin/passenger/admin_update_passenger.php
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$id = filterRequest("id"); // مفضّل
|
||||||
|
|
||||||
|
$first_name = filterRequest("first_name");
|
||||||
|
$last_name = filterRequest("last_name");
|
||||||
|
$new_phone = filterRequest("phone");
|
||||||
|
|
||||||
|
if (empty($id) ) { jsonError("Provide id or phone_lookup"); exit; }
|
||||||
|
if ($first_name === null && $last_name === null && $new_phone === null) {
|
||||||
|
jsonError("Nothing to update"); exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sets = [];
|
||||||
|
$params = [];
|
||||||
|
$new_phone = $encryptionHelper->encryptData($new_phone);
|
||||||
|
$first_name = $encryptionHelper->encryptData($first_name);
|
||||||
|
$last_name = $encryptionHelper->encryptData($last_name);
|
||||||
|
|
||||||
|
$enc_norm = $encryptionHelper->encryptData($norm);
|
||||||
|
if ($first_name !== null) { $sets[] = "first_name = :first_name"; $params['first_name'] = trim($first_name); }
|
||||||
|
if ($last_name !== null) { $sets[] = "last_name = :last_name"; $params['last_name'] = trim($last_name); }
|
||||||
|
if ($new_phone !== null) {
|
||||||
|
$sets[] = "phone = :phone";
|
||||||
|
$params['phone'] = trim($new_phone);
|
||||||
|
|
||||||
|
// منع تكرار الهاتف على راكب آخر
|
||||||
|
$q = $con->prepare("SELECT id FROM passengers WHERE phone = :ph LIMIT 1");
|
||||||
|
$q->execute(['ph' => $params['phone']]);
|
||||||
|
$row = $q->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row) {
|
||||||
|
if (!empty($id) && $row['id'] != $id) { jsonError("Phone already used by another passenger"); exit; }
|
||||||
|
if (empty($id) && $row['id'] != $phoneLookup) { jsonError("Phone already used by another passenger"); exit; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$whereSql = "";
|
||||||
|
$whereParams = [];
|
||||||
|
if (!empty($id)) { $whereSql = "id = :pid"; $whereParams['pid'] = $id; }
|
||||||
|
else { $whereSql = "phone = :plk"; $whereParams['plk'] = $phoneLookup; }
|
||||||
|
|
||||||
|
$sql = "UPDATE passengers SET ".implode(", ", $sets).", updated_at = CURRENT_TIMESTAMP WHERE $whereSql";
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$ok = $stmt->execute(array_merge($params, $whereParams));
|
||||||
|
|
||||||
|
if ($ok && $stmt->rowCount() > 0) { jsonSuccess(null, "Passenger updated"); }
|
||||||
|
else { jsonError("No change or passenger not found"); }
|
||||||
103
backend/Admin/rides/admin_get_rides_by_phone.php
Executable file
103
backend/Admin/rides/admin_get_rides_by_phone.php
Executable file
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$phone = filterRequest('phone');
|
||||||
|
if (!$phone) {
|
||||||
|
error_log("[get_last_ride] Missing phone parameter");
|
||||||
|
jsonError("Phone is required");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw = $phone;
|
||||||
|
|
||||||
|
// شَفِّر قبل الاستعلام
|
||||||
|
$enc_raw = $encryptionHelper->encryptData($raw);
|
||||||
|
|
||||||
|
try {
|
||||||
|
error_log("[get_last_ride] Searching passenger with phone=$raw");
|
||||||
|
|
||||||
|
// 1) ابحث عن الراكب بالهاتف المشفّر
|
||||||
|
$selP = $con->prepare("
|
||||||
|
SELECT id, first_name, last_name, phone
|
||||||
|
FROM passengers
|
||||||
|
WHERE phone =:enc_raw
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$selP->execute(['enc_raw' => $enc_raw]);
|
||||||
|
$passenger = $selP->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$passenger) {
|
||||||
|
error_log("[get_last_ride] Passenger not found (phone=$raw)");
|
||||||
|
jsonError('Passenger not found for provided phone');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("[get_last_ride] Passenger found id=" . $passenger['id']);
|
||||||
|
|
||||||
|
// 2) آخر رحلة لهذا الراكب
|
||||||
|
$rideStmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.start_location,
|
||||||
|
r.end_location,
|
||||||
|
r.date,
|
||||||
|
r.time,
|
||||||
|
r.endtime,
|
||||||
|
r.status,
|
||||||
|
r.paymentMethod,
|
||||||
|
r.carType,
|
||||||
|
r.price,
|
||||||
|
r.price_for_driver,
|
||||||
|
r.price_for_passenger,
|
||||||
|
r.distance,
|
||||||
|
r.driver_id,
|
||||||
|
r.passenger_id,
|
||||||
|
r.created_at,
|
||||||
|
r.updated_at,
|
||||||
|
r.DriverIsGoingToPassenger,
|
||||||
|
r.rideTimeStart,
|
||||||
|
r.rideTimeFinish,
|
||||||
|
d.first_name AS driver_first_name,
|
||||||
|
d.last_name AS driver_last_name
|
||||||
|
FROM ride r
|
||||||
|
LEFT JOIN driver d ON d.id = r.driver_id
|
||||||
|
WHERE r.passenger_id = :pid
|
||||||
|
ORDER BY r.created_at DESC, r.id DESC
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$rideStmt->execute(['pid' => $passenger['id']]);
|
||||||
|
$ride = $rideStmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$ride) {
|
||||||
|
error_log("[get_last_ride] No rides found for passenger_id=" . $passenger['id']);
|
||||||
|
jsonError('No rides found for this passenger');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("[get_last_ride] Found ride id=" . $ride['id'] . " for passenger_id=" . $passenger['id']);
|
||||||
|
|
||||||
|
// فك التشفير
|
||||||
|
$passenger['first_name'] = $encryptionHelper->decryptData($passenger['first_name']);
|
||||||
|
$passenger['last_name'] = $encryptionHelper->decryptData($passenger['last_name']);
|
||||||
|
$passenger['phone'] = $encryptionHelper->decryptData($passenger['phone']);
|
||||||
|
$ride['driver_first_name'] = $encryptionHelper->decryptData($ride['driver_first_name']);
|
||||||
|
$ride['driver_last_name'] = $encryptionHelper->decryptData($ride['driver_last_name']);
|
||||||
|
|
||||||
|
// 3) اطبع النتيجة
|
||||||
|
$response = [
|
||||||
|
'passenger' => [
|
||||||
|
'id' => $passenger['id'],
|
||||||
|
'first_name' => $passenger['first_name'],
|
||||||
|
'last_name' => $passenger['last_name'],
|
||||||
|
'phone' => $passenger['phone'],
|
||||||
|
],
|
||||||
|
'ride' => $ride
|
||||||
|
];
|
||||||
|
|
||||||
|
error_log("[get_last_ride] Success response for passenger_id=" . $passenger['id']);
|
||||||
|
jsonSuccess($response);
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("[get_last_ride] Exception: " . $e->getMessage());
|
||||||
|
jsonError("Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
87
backend/Admin/rides/admin_update_ride_status.php
Executable file
87
backend/Admin/rides/admin_update_ride_status.php
Executable file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$rideId = filterRequest('id');
|
||||||
|
$status = filterRequest('status');
|
||||||
|
$reason = filterRequest('reason'); // اختياري
|
||||||
|
|
||||||
|
if (empty($rideId) || empty($status)) {
|
||||||
|
jsonError("id and status are required");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* whitelist للحالات المسموحة – عدّل حسب نظامك */
|
||||||
|
$allowed = [
|
||||||
|
'Pending', 'Accepted', 'EnRoute', 'Arrived',
|
||||||
|
'Started', 'Completed', 'Canceled'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!in_array($status, $allowed, true)) {
|
||||||
|
jsonError("Invalid status");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con->beginTransaction();
|
||||||
|
|
||||||
|
// إن أردت ختم وقت النهاية تلقائيًا عند الإكمال
|
||||||
|
if ($status === 'Completed') {
|
||||||
|
$sql = "UPDATE ride
|
||||||
|
SET status = :st, rideTimeFinish = IFNULL(rideTimeFinish, NOW()), updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = :id";
|
||||||
|
} else {
|
||||||
|
$sql = "UPDATE ride
|
||||||
|
SET status = :st, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = :id";
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$ok = $stmt->execute(['st' => $status, 'id' => $rideId]);
|
||||||
|
|
||||||
|
if (!$ok || $stmt->rowCount() === 0) {
|
||||||
|
$con->rollBack();
|
||||||
|
jsonError("Ride not found or no change");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// أعِدّ بيانات الرحلة المحدّثة (للتحديث الفوري في الواجهة)
|
||||||
|
$fetch = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.start_location,
|
||||||
|
r.end_location,
|
||||||
|
r.date,
|
||||||
|
r.time,
|
||||||
|
r.endtime,
|
||||||
|
r.status,
|
||||||
|
r.paymentMethod,
|
||||||
|
r.carType,
|
||||||
|
r.price,
|
||||||
|
r.price_for_driver,
|
||||||
|
r.price_for_passenger,
|
||||||
|
r.distance,
|
||||||
|
r.driver_id,
|
||||||
|
r.passenger_id,
|
||||||
|
r.created_at,
|
||||||
|
r.updated_at,
|
||||||
|
r.DriverIsGoingToPassenger,
|
||||||
|
r.rideTimeStart,
|
||||||
|
r.rideTimeFinish,
|
||||||
|
d.first_name AS driver_first_name,
|
||||||
|
d.last_name AS driver_last_name
|
||||||
|
FROM ride r
|
||||||
|
LEFT JOIN driver d ON d.id = r.driver_id
|
||||||
|
WHERE r.id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$fetch->execute(['id' => $rideId]);
|
||||||
|
$ride = $fetch->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$con->commit();
|
||||||
|
jsonSuccess(['ride' => $ride, 'message' => 'Status updated']);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($con->inTransaction()) $con->rollBack();
|
||||||
|
jsonError("Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
50
backend/Admin/rides/get_driver_live_pos.php
Executable file
50
backend/Admin/rides/get_driver_live_pos.php
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
// =================================================================
|
||||||
|
// ملف: get_driver_live_pos.php
|
||||||
|
// الوظيفة: جلب الموقع اللحظي لسائق محدد (بناءً على ID)
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php'; // تأكد أن هذا الملف يحتوي على $con_tracking
|
||||||
|
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. استقبال معرف السائق
|
||||||
|
$driver_id = filterRequest("driver_id");
|
||||||
|
|
||||||
|
if (!$driver_id) {
|
||||||
|
jsonError("driver_id is required");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. الاستعلام من قاعدة بيانات التتبع (car_locations)
|
||||||
|
// نجلب أحدث إحداثيات تم تسجيلها لهذا السائق
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
heading,
|
||||||
|
speed,
|
||||||
|
updated_at
|
||||||
|
FROM car_locations
|
||||||
|
WHERE driver_id = ?
|
||||||
|
ORDER BY updated_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $con_tracking->prepare($sql);
|
||||||
|
$stmt->execute([$driver_id]);
|
||||||
|
$data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($data) {
|
||||||
|
jsonSuccess($data);
|
||||||
|
} else {
|
||||||
|
// السائق ليس له موقع مسجل (ربما لم يشغل التطبيق بعد)
|
||||||
|
jsonError("No location found for this driver");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
jsonError("Database Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
108
backend/Admin/rides/get_rides_by_status.php
Executable file
108
backend/Admin/rides/get_rides_by_status.php
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$statusFilter = filterRequest("status");
|
||||||
|
// القيم المتوقعة من التطبيق: 'All', 'Begin', 'New', 'Completed', 'Canceled'
|
||||||
|
if (!$statusFilter) $statusFilter = "Begin";
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
$whereClause = "";
|
||||||
|
|
||||||
|
// --- منطق ترجمة الحالات (Mapping Logic) ---
|
||||||
|
switch ($statusFilter) {
|
||||||
|
case 'All':
|
||||||
|
$whereClause = ""; // لا يوجد شرط، اجلب الكل
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Begin':
|
||||||
|
// قد تكون الرحلة بدأت أو وصل السائق
|
||||||
|
$whereClause = "WHERE r.status IN ('Begin','Apply','Applied')";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'New':
|
||||||
|
$whereClause = "WHERE r.status = 'New'";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Completed':
|
||||||
|
// في قاعدة البيانات الحالة اسمها Finished
|
||||||
|
$whereClause = "WHERE r.status = 'Finished'";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Canceled':
|
||||||
|
// نجمع كل حالات الإلغاء الممكنة
|
||||||
|
$whereClause = "WHERE r.status IN ('Cancel', 'CancelFromDriverAfterApply', 'TimeOut')";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// في حال تم إرسال حالة محددة غير المذكورين
|
||||||
|
$whereClause = "WHERE r.status = ?";
|
||||||
|
$params[] = $statusFilter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- الاستعلام ---
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
r.*,
|
||||||
|
-- بيانات السائق
|
||||||
|
d.first_name as d_fname, d.last_name as d_lname, d.phone as d_phone, d.id as driver_real_id,
|
||||||
|
-- إحصائيات السائق (نحسب المكتمل والملغي بشكل أدق)
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE driver_id = d.id AND status = 'Finished') as d_completed,
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE driver_id = d.id AND status LIKE 'Cancel%') as d_canceled,
|
||||||
|
|
||||||
|
-- بيانات الراكب
|
||||||
|
p.first_name as p_fname, p.last_name as p_lname, p.phone as p_phone,
|
||||||
|
-- إحصائيات الراكب
|
||||||
|
(SELECT COUNT(*) FROM ride WHERE passenger_id = p.id AND status = 'Finished') as p_completed,
|
||||||
|
|
||||||
|
-- سبب الإلغاء
|
||||||
|
-- نحاول جلبه من جدول driver_orders (ملاحظات السائق)
|
||||||
|
-- نستخدم COALESCE لجلب 'لا يوجد سبب' إذا كانت القيمة فارغة
|
||||||
|
COALESCE(
|
||||||
|
(SELECT notes FROM driver_orders WHERE order_id = r.id LIMIT 1),
|
||||||
|
'لا يوجد سبب مسجل'
|
||||||
|
) as cancel_reason
|
||||||
|
|
||||||
|
FROM ride r
|
||||||
|
LEFT JOIN driver d ON r.driver_id = d.id
|
||||||
|
LEFT JOIN passengers p ON r.passenger_id = p.id
|
||||||
|
$whereClause
|
||||||
|
ORDER BY r.id DESC
|
||||||
|
LIMIT 100
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$rides = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
foreach ($rides as $row) {
|
||||||
|
// فك التشفير
|
||||||
|
try { $row['d_fname'] = $encryptionHelper->decryptData($row['d_fname']); } catch(Exception $e){}
|
||||||
|
try { $row['d_lname'] = $encryptionHelper->decryptData($row['d_lname']); } catch(Exception $e){}
|
||||||
|
try { $row['d_phone'] = $encryptionHelper->decryptData($row['d_phone']); } catch(Exception $e){}
|
||||||
|
|
||||||
|
try { $row['p_fname'] = $encryptionHelper->decryptData($row['p_fname']); } catch(Exception $e){}
|
||||||
|
try { $row['p_lname'] = $encryptionHelper->decryptData($row['p_lname']); } catch(Exception $e){}
|
||||||
|
try { $row['p_phone'] = $encryptionHelper->decryptData($row['p_phone']); } catch(Exception $e){}
|
||||||
|
|
||||||
|
$row['driver_full_name'] = trim($row['d_fname'] . ' ' . $row['d_lname']);
|
||||||
|
$row['passenger_full_name'] = trim($row['p_fname'] . ' ' . $row['p_lname']);
|
||||||
|
|
||||||
|
if(empty($row['driver_full_name'])) $row['driver_full_name'] = "Unknown Driver";
|
||||||
|
if(empty($row['passenger_full_name'])) $row['passenger_full_name'] = "Unknown Passenger";
|
||||||
|
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonSuccess($data);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
jsonError("Database Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
150
backend/Admin/rides/monitorRide.php
Executable file
150
backend/Admin/rides/monitorRide.php
Executable file
@@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
// 1. Log the start of the request
|
||||||
|
$phone = filterRequest("phone");
|
||||||
|
error_log("[MONITOR_RIDE] ---------------- START REQUEST ----------------");
|
||||||
|
error_log("[MONITOR_RIDE] 1. Received Phone: " . $phone);
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// 1) البحث عن الهاتف أولاً في جدول السائق ثم جدول الراكب
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
|
||||||
|
$encPhone = $encryptionHelper->encryptData($phone);
|
||||||
|
error_log("[MONITOR_RIDE] 2. Encrypted Phone: " . $encPhone);
|
||||||
|
|
||||||
|
// Check Driver Table
|
||||||
|
$driverQuery = $con->prepare("SELECT id AS driverID FROM driver WHERE phone = :phone LIMIT 1");
|
||||||
|
$driverQuery->execute([':phone' => $encPhone]);
|
||||||
|
$driver = $driverQuery->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Check Passenger Table
|
||||||
|
$customerQuery = $con->prepare("SELECT id AS customerID FROM passengers WHERE phone = :phone LIMIT 1");
|
||||||
|
$customerQuery->execute([':phone' => $encPhone]);
|
||||||
|
$customer = $customerQuery->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
|
||||||
|
// حدد نوع المستخدم
|
||||||
|
$userType = '';
|
||||||
|
$driverID = null;
|
||||||
|
$customerID = null;
|
||||||
|
|
||||||
|
if ($driver) {
|
||||||
|
$userType = 'driver';
|
||||||
|
$driverID = $driver['driverID'];
|
||||||
|
error_log("[MONITOR_RIDE] 3. User Found: Type = DRIVER, ID = " . $driverID);
|
||||||
|
} elseif ($customer) {
|
||||||
|
$userType = 'customer';
|
||||||
|
$customerID = $customer['customerID'];
|
||||||
|
error_log("[MONITOR_RIDE] 3. User Found: Type = CUSTOMER, ID = " . $customerID);
|
||||||
|
} else {
|
||||||
|
error_log("[MONITOR_RIDE] 3. FAILURE: Phone number not found in Driver or Passenger tables.");
|
||||||
|
jsonError("رقم الهاتف غير موجود في النظام.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// 2) جلب آخر رحلة حالتها "بدأت" بناءً على نوع المستخدم
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if ($userType == 'driver') {
|
||||||
|
error_log("[MONITOR_RIDE] 4. Searching for active ride for Driver ID: " . $driverID);
|
||||||
|
$rideQuery = $con->prepare("
|
||||||
|
SELECT * FROM ride
|
||||||
|
WHERE driver_id = :driverID AND status = 'Begin'
|
||||||
|
ORDER BY id DESC LIMIT 1
|
||||||
|
");
|
||||||
|
$rideQuery->execute([':driverID' => $driverID]);
|
||||||
|
} else {
|
||||||
|
error_log("[MONITOR_RIDE] 4. Searching for active ride for Customer ID: " . $customerID);
|
||||||
|
$rideQuery = $con->prepare("
|
||||||
|
SELECT * FROM ride
|
||||||
|
WHERE passenger_id = :customerID AND status = 'Begin'
|
||||||
|
ORDER BY id DESC LIMIT 1
|
||||||
|
");
|
||||||
|
$rideQuery->execute([':customerID' => $customerID]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ride = $rideQuery->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$ride) {
|
||||||
|
error_log("[MONITOR_RIDE] 4. FAILURE: No ride with status 'Begin' found.");
|
||||||
|
jsonError("لا توجد رحلة بدأت لهذا المستخدم.");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
error_log("[MONITOR_RIDE] 4. SUCCESS: Active Ride Found. Ride ID: " . $ride['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// 3) جلب معلومات السائق من الرحلة
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// FIX 1: Safe assignment of driver ID (checking driverID vs driver_id)
|
||||||
|
$rideDriverID = $ride['driverID'] ?? $ride['driver_id'];
|
||||||
|
|
||||||
|
error_log("[MONITOR_RIDE] 5. Fetching info for Driver ID from Ride: " . $rideDriverID);
|
||||||
|
|
||||||
|
// FIX 2: Select first_name and last_name instead of fullname
|
||||||
|
$driverInfoQuery = $con->prepare("
|
||||||
|
SELECT id, first_name, last_name, phone
|
||||||
|
FROM driver
|
||||||
|
WHERE id = :driverID
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$driverInfoQuery->execute([':driverID' => $rideDriverID]);
|
||||||
|
$driverInfo = $driverInfoQuery->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($driverInfo) {
|
||||||
|
// فك التشفير للهاتف
|
||||||
|
$driverInfo['phone'] = $encryptionHelper->decryptData($driverInfo['phone']);
|
||||||
|
|
||||||
|
// FIX 4: Decrypt First Name and Last Name
|
||||||
|
$driverInfo['first_name'] = $encryptionHelper->decryptData($driverInfo['first_name']);
|
||||||
|
$driverInfo['last_name'] = $encryptionHelper->decryptData($driverInfo['last_name']);
|
||||||
|
|
||||||
|
// Construct fullname for the response
|
||||||
|
$fullName = $driverInfo['first_name'] . " " . $driverInfo['last_name'];
|
||||||
|
$driverInfo['fullname'] = $fullName;
|
||||||
|
|
||||||
|
error_log("[MONITOR_RIDE] 5. Driver Info Found: " . $fullName);
|
||||||
|
} else {
|
||||||
|
error_log("[MONITOR_RIDE] 5. WARNING: Driver info not found for ID " . $rideDriverID);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// 4) جلب آخر موقع للسائق من جدول driver_location بشرط الحالة ON
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
|
||||||
|
error_log("[MONITOR_RIDE] 6. Querying Tracking DB for Driver ID: " . $rideDriverID);
|
||||||
|
|
||||||
|
// FIX 3: Changed ORDER BY id DESC to ORDER BY updated_at DESC
|
||||||
|
$locationQuery = $con_tracking->prepare("
|
||||||
|
SELECT latitude, longitude, speed, heading, updated_at
|
||||||
|
FROM car_locations
|
||||||
|
WHERE driver_id = :driverID AND status = 'ON'
|
||||||
|
ORDER BY updated_at DESC LIMIT 1
|
||||||
|
");
|
||||||
|
$locationQuery->execute([':driverID' => $rideDriverID]);
|
||||||
|
$location = $locationQuery->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($location) {
|
||||||
|
error_log("[MONITOR_RIDE] 6. Location Found: Lat=" . $location['latitude'] . " Lng=" . $location['longitude'] . " Updated=" . $location['updated_at']);
|
||||||
|
} else {
|
||||||
|
error_log("[MONITOR_RIDE] 6. WARNING: No live location found (status=ON) or list empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// 5) تجهيز البيانات للرد
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
"ride_details" => $ride,
|
||||||
|
"driver_details" => $driverInfo,
|
||||||
|
"driver_location" => $location ?: "No live location"
|
||||||
|
];
|
||||||
|
|
||||||
|
error_log("[MONITOR_RIDE] 7. Sending Success Response.");
|
||||||
|
jsonSuccess($response);
|
||||||
|
|
||||||
|
?>
|
||||||
87
backend/Admin/sendEmailToDrivertransaction.php
Normal file
87
backend/Admin/sendEmailToDrivertransaction.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
// File: send_payment_received_email.php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
$driverID = filterRequest('driverID');
|
||||||
|
$totalAmount = filterRequest('total_amount');
|
||||||
|
$driverPhone = filterRequest('phone');
|
||||||
|
$driverArabicName = filterRequest('name_arabic');
|
||||||
|
$accountBank = filterRequest('accountBank');
|
||||||
|
$driverEmail = filterRequest('email');
|
||||||
|
|
||||||
|
// لغة الإيميل (تلقائي إنجليزي حالياً، يمكن تعيينها لاحقًا حسب المستخدم)
|
||||||
|
$language = 'en';
|
||||||
|
|
||||||
|
// عنوان واسم التطبيق الرسمي
|
||||||
|
$appName = "tripz"; // الاسم الجديد مع حرف "Z"
|
||||||
|
$domain = "https://tripz-egypt.com";
|
||||||
|
|
||||||
|
// محتوى الإيميل - باللغة الإنجليزية
|
||||||
|
$bodyEmail = "<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; color: #333; background: #f9f9f9; padding: 20px; }
|
||||||
|
.container { background: #fff; padding: 30px; border-radius: 8px; max-width: 600px; margin: auto; }
|
||||||
|
h1 { color: #007bff; }
|
||||||
|
p { font-size: 16px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class='container'>
|
||||||
|
<img src='$domain/assets/logo.png' alt='$appName Logo' style='width: 150px; margin: 20px auto; display: block;'>
|
||||||
|
<h1>Payment Sent - $appName</h1>
|
||||||
|
<p>Thank you for being a valued driver on the $appName platform.</p>
|
||||||
|
<p>We have sent a payment of <strong>$totalAmount EGP</strong> to your account <strong>$accountBank</strong>.</p>
|
||||||
|
<p>Please note that it may take a few days for your bank to process this transaction.</p>
|
||||||
|
<p>We appreciate your efforts and are proud to have you on board with $appName.</p>
|
||||||
|
<p style='margin-top: 40px;'>Regards,<br><strong>tripz Team</strong></p>
|
||||||
|
<p style='font-size: 12px; color: #888;'>tripz, Egypt | $domain</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>";
|
||||||
|
|
||||||
|
// محتوى الإيميل - باللغة العربية
|
||||||
|
$bodyEmailAr = "<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Cairo', sans-serif; color: #333; background: #f9f9f9; padding: 20px; direction: rtl; }
|
||||||
|
.container { background: #fff; padding: 30px; border-radius: 8px; max-width: 600px; margin: auto; text-align: right; }
|
||||||
|
h1 { color: #007bff; }
|
||||||
|
p { font-size: 16px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class='container'>
|
||||||
|
<img src='$domain/assets/logo.png' alt='$appName' style='width: 150px; margin: 20px auto; display: block;'>
|
||||||
|
<h1>تم إرسال الدفعة - $appName</h1>
|
||||||
|
<p>شكرًا لك لكونك سائقًا مميزًا على منصة $appName.</p>
|
||||||
|
<p>لقد تم إرسال دفعة قدرها <strong>$totalAmount جنيه</strong> إلى حسابك <strong>$accountBank</strong>.</p>
|
||||||
|
<p>يرجى ملاحظة أن عملية التحويل قد تستغرق بضعة أيام حسب إجراءات البنك.</p>
|
||||||
|
<p>نقدّر جهودك ونتطلع إلى استمرار الشراكة معك على تطبيق $appName.</p>
|
||||||
|
<p style='margin-top: 40px;'>مع التحية،<br><strong>فريق $appName</strong></p>
|
||||||
|
<p style='font-size: 12px; color: #888;'>$appName - مصر | $domain</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>";
|
||||||
|
|
||||||
|
// إعدادات الإيميل
|
||||||
|
$supportEmail = 'support@tripz-egypt.com';
|
||||||
|
$headers = "MIME-Version: 1.0\r\n";
|
||||||
|
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
|
||||||
|
$headers .= "From: tripz Egypt <$supportEmail>\r\n";
|
||||||
|
|
||||||
|
// إرسال الإيميل إن وُجد عنوان صالح
|
||||||
|
if (!empty($driverEmail)) {
|
||||||
|
$subject = "Payment Sent - $appName";
|
||||||
|
$message = ($language === 'ar') ? $bodyEmailAr : $bodyEmail;
|
||||||
|
|
||||||
|
if (mail($driverEmail, $subject, $message, $headers)) {
|
||||||
|
jsonSuccess(null, "Email sent successfully to $driverEmail");
|
||||||
|
} else {
|
||||||
|
jsonError("Failed to send email to $driverEmail");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jsonError("Invalid or missing driver email address.");
|
||||||
|
}
|
||||||
|
?>
|
||||||
79
backend/Admin/send_whatsapp_message.php
Executable file
79
backend/Admin/send_whatsapp_message.php
Executable file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
// File: send_whatsapp_message.php
|
||||||
|
// هذا السكربت يرسل رسالة واتساب فقط باستخدام RaseelPlus API
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../connect.php'; // فقط إذا كنت تحتاج للوصول إلى environment
|
||||||
|
|
||||||
|
error_log("--- [send_whatsapp_message.php] Script execution started ---");
|
||||||
|
|
||||||
|
// استقبال المعطيات من POST
|
||||||
|
$receiver = filterRequest("receiver"); // رقم الهاتف
|
||||||
|
$message = filterRequest("message"); // نص الرسالة
|
||||||
|
|
||||||
|
if (empty($receiver) || empty($message)) {
|
||||||
|
error_log("[send_whatsapp_message.php] Error: Missing receiver or message.");
|
||||||
|
jsonError('Phone number and message are required.');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// بيانات Raseel
|
||||||
|
$instanceId = getenv("RASEEL_DRIVER_INSTANCE_ID");
|
||||||
|
$accessToken = getenv("RASEEL_DRIVER_ACCESS_TOKEN");
|
||||||
|
|
||||||
|
// API URL
|
||||||
|
$apiUrl = 'https://raseelplus.com/api/send';
|
||||||
|
|
||||||
|
// تجهيز البيانات للإرسال
|
||||||
|
$payload = [
|
||||||
|
"number" => $receiver,
|
||||||
|
"type" => "text",
|
||||||
|
"message" => $message,
|
||||||
|
"instance_id" => $instanceId,
|
||||||
|
"access_token"=> $accessToken
|
||||||
|
];
|
||||||
|
|
||||||
|
error_log("[send_whatsapp_message.php] Sending payload: " . json_encode($payload));
|
||||||
|
|
||||||
|
// إرسال الطلب
|
||||||
|
$response = callAPI("POST", $apiUrl, json_encode($payload));
|
||||||
|
error_log("[send_whatsapp_message.php] Raw response: " . print_r($response, true));
|
||||||
|
|
||||||
|
// فحص الاستجابة
|
||||||
|
if ($response && !isset($response->error) && (isset($response->status) && $response->status == 'success' || isset($response->message))) {
|
||||||
|
jsonSuccess(null, "Message sent successfully.");
|
||||||
|
} else {
|
||||||
|
$errorMessage = isset($response->message) ? $response->message : "Unknown error.";
|
||||||
|
error_log("[send_whatsapp_message.php] Failed to send: $errorMessage");
|
||||||
|
jsonError("Failed to send message: $errorMessage");
|
||||||
|
}
|
||||||
|
|
||||||
|
// دالة cURL
|
||||||
|
function callAPI($method, $url, $data)
|
||||||
|
{
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt_array($curl, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_ENCODING => "",
|
||||||
|
CURLOPT_MAXREDIRS => 10,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||||
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
CURLOPT_POSTFIELDS => $data,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Content-Type: application/json",
|
||||||
|
"Accept: application/json"
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
$err = curl_error($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
|
||||||
|
if ($err) {
|
||||||
|
error_log("[callAPI] cURL Error: $err");
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return json_decode($response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
43
backend/Admin/v2/analytics/driver_ranking.php
Normal file
43
backend/Admin/v2/analytics/driver_ranking.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/analytics/driver_ranking.php
|
||||||
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized access.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// أفضل 10 كباتن حسب عدد الرحلات المكتملة
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
d.id, d.first_name, d.last_name, d.phone,
|
||||||
|
COUNT(r.id) as completed_rides,
|
||||||
|
SUM(r.price) as total_revenue
|
||||||
|
FROM driver d
|
||||||
|
JOIN ride r ON d.id = r.driver_id
|
||||||
|
WHERE r.status = 'Finished'
|
||||||
|
GROUP BY d.id, d.first_name, d.last_name, d.phone
|
||||||
|
ORDER BY completed_rides DESC
|
||||||
|
LIMIT 10
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$top_drivers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك تشفير الأسماء
|
||||||
|
foreach ($top_drivers as &$driver) {
|
||||||
|
$driver['first_name'] = $encryptionHelper->decryptData($driver['first_name']);
|
||||||
|
$driver['last_name'] = $encryptionHelper->decryptData($driver['last_name']);
|
||||||
|
$driver['phone'] = $encryptionHelper->decryptData($driver['phone']);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $top_drivers
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
58
backend/Admin/v2/analytics/growth.php
Normal file
58
backend/Admin/v2/analytics/growth.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/analytics/growth.php
|
||||||
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized access.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// نمو الركاب لآخر 30 يوم
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT DATE(created_at) as date, COUNT(*) as new_passengers
|
||||||
|
FROM passengers
|
||||||
|
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||||
|
GROUP BY DATE(created_at)
|
||||||
|
ORDER BY date ASC
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$passenger_growth = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// نمو السائقين لآخر 30 يوم
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT DATE(created_at) as date, COUNT(*) as new_drivers
|
||||||
|
FROM driver
|
||||||
|
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||||
|
GROUP BY DATE(created_at)
|
||||||
|
ORDER BY date ASC
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$driver_growth = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// إجمالي الأعداد الحالية
|
||||||
|
$stmt = $con->prepare("SELECT COUNT(*) FROM passengers");
|
||||||
|
$stmt->execute();
|
||||||
|
$total_passengers = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
$stmt = $con->prepare("SELECT COUNT(*) FROM driver");
|
||||||
|
$stmt->execute();
|
||||||
|
$total_drivers = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => [
|
||||||
|
'passenger_daily' => $passenger_growth,
|
||||||
|
'driver_daily' => $driver_growth,
|
||||||
|
'totals' => [
|
||||||
|
'passengers' => (int)$total_passengers,
|
||||||
|
'drivers' => (int)$total_drivers
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
52
backend/Admin/v2/analytics/revenue.php
Normal file
52
backend/Admin/v2/analytics/revenue.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/analytics/revenue.php
|
||||||
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized access.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// إحصائيات الإيرادات لآخر 30 يوم
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
DATE(created_at) as date,
|
||||||
|
SUM(price) as total_revenue,
|
||||||
|
SUM(price - price_for_driver) as company_profit,
|
||||||
|
COUNT(*) as total_rides
|
||||||
|
FROM ride
|
||||||
|
WHERE status = 'Finished'
|
||||||
|
AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||||
|
GROUP BY DATE(created_at)
|
||||||
|
ORDER BY date ASC
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$daily_stats = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// ملخص عام
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
SUM(price) as total_revenue_all,
|
||||||
|
SUM(price - price_for_driver) as total_profit_all,
|
||||||
|
AVG(price) as avg_ride_price
|
||||||
|
FROM ride
|
||||||
|
WHERE status = 'Finished'
|
||||||
|
AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$summary = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => [
|
||||||
|
'daily' => $daily_stats,
|
||||||
|
'summary' => $summary
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
44
backend/Admin/v2/financial/settlements.php
Normal file
44
backend/Admin/v2/financial/settlements.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/financial/settlements.php
|
||||||
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized access.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// جلب السائقين الذين لديهم مستحقات أو مديونية
|
||||||
|
// الحسبة: إجمالي (price_for_driver) من الرحلات المكتملة
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
d.id, d.first_name, d.last_name, d.phone,
|
||||||
|
SUM(r.price_for_driver) as total_earned,
|
||||||
|
COUNT(r.id) as total_rides
|
||||||
|
FROM driver d
|
||||||
|
LEFT JOIN ride r ON d.id = r.driver_id AND r.status = 'Finished'
|
||||||
|
GROUP BY d.id
|
||||||
|
HAVING total_earned > 0
|
||||||
|
ORDER BY total_earned DESC
|
||||||
|
LIMIT 50
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$drivers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك تشفير البيانات
|
||||||
|
foreach ($drivers as &$driver) {
|
||||||
|
$driver['first_name'] = $encryptionHelper->decryptData($driver['first_name']);
|
||||||
|
$driver['last_name'] = $encryptionHelper->decryptData($driver['last_name']);
|
||||||
|
$driver['phone'] = $encryptionHelper->decryptData($driver['phone']);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $drivers
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
34
backend/Admin/v2/financial/stats.php
Normal file
34
backend/Admin/v2/financial/stats.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/financial/stats.php
|
||||||
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized access.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// إحصائيات مالية عامة
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
SUM(price_for_passenger) as total_revenue,
|
||||||
|
SUM(price_for_driver) as total_driver_pay,
|
||||||
|
SUM(price_for_passenger - price_for_driver) as total_platform_commission,
|
||||||
|
(SELECT SUM(amount) FROM payments WHERE payment_method = 'Cash') as cash_payments,
|
||||||
|
(SELECT SUM(amount) FROM payments WHERE payment_method != 'Cash') as digital_payments
|
||||||
|
FROM ride
|
||||||
|
WHERE status = 'Finished'
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $stats
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
102
backend/Admin/v2/quality/blacklist_manager.php
Normal file
102
backend/Admin/v2/quality/blacklist_manager.php
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/quality/blacklist_manager.php
|
||||||
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
// require_once __DIR__ . '/../../../encrypt_decrypt.php';
|
||||||
|
require_once __DIR__ . '/../security/audit_logs_helper.php'; // إذا كان متاحاً، وإلا سننفذ الإدخال مباشرة
|
||||||
|
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
jsonError("Unauthorized", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$action_type = filterRequest('action_type') ?: 'get_all';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action_type === 'get_all') {
|
||||||
|
// جلب قائمة السائقين المحظورين
|
||||||
|
$stmt_drivers = $con->prepare("
|
||||||
|
SELECT id, driver_id, phone, reason, created_at, 'driver' as type
|
||||||
|
FROM blacklist_driver
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
");
|
||||||
|
$stmt_drivers->execute();
|
||||||
|
$blocked_drivers = $stmt_drivers->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// جلب قائمة الركاب المحظورين
|
||||||
|
$stmt_passengers = $con->prepare("
|
||||||
|
SELECT id, phone, phone_normalized, reason, expires_at, created_at, 'passenger' as type
|
||||||
|
FROM passenger_blacklist
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
");
|
||||||
|
$stmt_passengers->execute();
|
||||||
|
$blocked_passengers = $stmt_passengers->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك التشفير عن الأرقام إذا كانت مشفرة
|
||||||
|
foreach ($blocked_drivers as &$bd) {
|
||||||
|
$decrypted_phone = $encryptionHelper->decryptData($bd['phone']);
|
||||||
|
if ($decrypted_phone) $bd['phone'] = $decrypted_phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($blocked_passengers as &$bp) {
|
||||||
|
$decrypted_phone = $encryptionHelper->decryptData($bp['phone']);
|
||||||
|
if ($decrypted_phone) $bp['phone'] = $decrypted_phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonSuccess([
|
||||||
|
'drivers' => $blocked_drivers,
|
||||||
|
'passengers' => $blocked_passengers
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action_type === 'unblock_driver') {
|
||||||
|
$phone = filterRequest('phone');
|
||||||
|
if (!$phone) jsonError("Phone is required");
|
||||||
|
|
||||||
|
$enc_phone = $encryptionHelper->encryptData($phone);
|
||||||
|
|
||||||
|
$stmt = $con->prepare("DELETE FROM blacklist_driver WHERE phone = ? OR phone = ?");
|
||||||
|
$stmt->execute([$phone, $enc_phone]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// تسجيل في الـ Audit Log
|
||||||
|
$log_stmt = $con->prepare("INSERT INTO admin_audit_log (admin_id, admin_phone, action, table_name, entity_type, details) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$log_stmt->execute([
|
||||||
|
$user_id, 'Admin', 'unblock_driver', 'blacklist_driver', 'driver',
|
||||||
|
json_encode(['phone' => $phone, 'action' => 'Unblocked driver'])
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonSuccess(null, "Driver unblocked successfully");
|
||||||
|
} else {
|
||||||
|
jsonError("Driver not found in blacklist");
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action_type === 'unblock_passenger') {
|
||||||
|
$phone_normalized = filterRequest('phone_normalized');
|
||||||
|
if (!$phone_normalized) jsonError("Normalized Phone is required");
|
||||||
|
|
||||||
|
$stmt = $con->prepare("DELETE FROM passenger_blacklist WHERE phone_normalized = ?");
|
||||||
|
$stmt->execute([$phone_normalized]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// تسجيل في الـ Audit Log
|
||||||
|
$log_stmt = $con->prepare("INSERT INTO admin_audit_log (admin_id, admin_phone, action, table_name, entity_type, details) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$log_stmt->execute([
|
||||||
|
$user_id, 'Admin', 'unblock_passenger', 'passenger_blacklist', 'passenger',
|
||||||
|
json_encode(['phone_normalized' => $phone_normalized, 'action' => 'Unblocked passenger'])
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonSuccess(null, "Passenger unblocked successfully");
|
||||||
|
} else {
|
||||||
|
jsonError("Passenger not found in blacklist");
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonError("Invalid action_type", 400);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
jsonError("Blacklist action failed: " . $e->getMessage(), 500);
|
||||||
|
}
|
||||||
|
?>
|
||||||
105
backend/Admin/v2/quality/driver_scorecard.php
Normal file
105
backend/Admin/v2/quality/driver_scorecard.php
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/quality/driver_scorecard.php
|
||||||
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
// require_once __DIR__ . '/../../../encrypt_decrypt.php';
|
||||||
|
|
||||||
|
// التحقق من الصلاحيات
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
jsonError("Unauthorized", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$driver_id = filterRequest('driver_id');
|
||||||
|
if (!$driver_id) {
|
||||||
|
jsonError("Missing driver_id", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$scorecard = [];
|
||||||
|
|
||||||
|
// 1. البيانات الأساسية للسائق
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT id, first_name, last_name, phone, status, created_at, expiry_date
|
||||||
|
FROM driver
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$driver_id]);
|
||||||
|
$driver = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$driver) {
|
||||||
|
jsonError("Driver not found", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// فك التشفير للبيانات الأساسية
|
||||||
|
if (!empty($driver['first_name'])) $driver['first_name'] = $encryptionHelper->decryptData($driver['first_name']) ?: $driver['first_name'];
|
||||||
|
if (!empty($driver['last_name'])) $driver['last_name'] = $encryptionHelper->decryptData($driver['last_name']) ?: $driver['last_name'];
|
||||||
|
if (!empty($driver['phone'])) $driver['phone'] = $encryptionHelper->decryptData($driver['phone']) ?: $driver['phone'];
|
||||||
|
|
||||||
|
$scorecard['basic_info'] = $driver;
|
||||||
|
|
||||||
|
// 2. إحصائيات الرحلات (نسبة الإنجاز والإلغاء)
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_rides,
|
||||||
|
SUM(CASE WHEN status = 'Finished' THEN 1 ELSE 0 END) as completed_rides,
|
||||||
|
SUM(CASE WHEN status = 'cancel' AND cancel_by = 'driver' THEN 1 ELSE 0 END) as driver_cancellations,
|
||||||
|
SUM(CASE WHEN status = 'cancel' AND cancel_by = 'passenger' THEN 1 ELSE 0 END) as passenger_cancellations
|
||||||
|
FROM ride
|
||||||
|
WHERE driver_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$driver_id]);
|
||||||
|
$rides = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// حساب نسبة الإنجاز
|
||||||
|
$total = (int)$rides['total_rides'];
|
||||||
|
$completed = (int)$rides['completed_rides'];
|
||||||
|
$rides['completion_rate'] = $total > 0 ? round(($completed / $total) * 100, 2) : 0;
|
||||||
|
|
||||||
|
$scorecard['rides_stats'] = $rides;
|
||||||
|
|
||||||
|
// 3. التقييمات
|
||||||
|
$stmt = $con->prepare("SELECT IFNULL(AVG(rating_driver), 0) as avg_rating FROM ride WHERE driver_id = ? AND rating_driver > 0");
|
||||||
|
$stmt->execute([$driver_id]);
|
||||||
|
$scorecard['rating'] = round($stmt->fetchColumn(), 2);
|
||||||
|
|
||||||
|
// 4. تحليل السلوك (Behavior)
|
||||||
|
// نستخدم جدول driver_behavior لجمع المتوسطات
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
IFNULL(AVG(behavior_score), 100) as avg_behavior_score,
|
||||||
|
IFNULL(AVG(max_speed), 0) as avg_max_speed,
|
||||||
|
IFNULL(SUM(hard_brakes), 0) as total_hard_brakes,
|
||||||
|
IFNULL(SUM(rapid_accelerations), 0) as total_rapid_accel
|
||||||
|
FROM driver_behavior
|
||||||
|
WHERE driver_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$driver_id]);
|
||||||
|
$scorecard['behavior'] = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 5. الشكاوى (Complaints)
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_complaints,
|
||||||
|
SUM(CASE WHEN statusComplaint = 'Open' THEN 1 ELSE 0 END) as open_complaints,
|
||||||
|
SUM(CASE WHEN statusComplaint = 'Resolved' THEN 1 ELSE 0 END) as resolved_complaints
|
||||||
|
FROM complaint
|
||||||
|
WHERE driver_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$driver_id]);
|
||||||
|
$scorecard['complaints'] = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// 6. تقييم شامل (Overall Score) من 100
|
||||||
|
// وزن التقييم: 40% إنجاز رحلات، 30% تقييم ركاب (محول لـ 100)، 30% سلوك قيادة، وخصم للشكاوى
|
||||||
|
$completion_score = $rides['completion_rate'] * 0.4;
|
||||||
|
$rating_score = ($scorecard['rating'] / 5) * 100 * 0.3;
|
||||||
|
$behavior_score = $scorecard['behavior']['avg_behavior_score'] * 0.3;
|
||||||
|
$complaint_penalty = $scorecard['complaints']['total_complaints'] * 5; // خصم 5 نقاط عن كل شكوى
|
||||||
|
|
||||||
|
$overall = $completion_score + $rating_score + $behavior_score - $complaint_penalty;
|
||||||
|
$scorecard['overall_score'] = max(0, min(100, round($overall, 1)));
|
||||||
|
|
||||||
|
jsonSuccess($scorecard);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
jsonError("Failed to fetch scorecard: " . $e->getMessage(), 500);
|
||||||
|
}
|
||||||
|
?>
|
||||||
62
backend/Admin/v2/realtime_dashboard.php
Normal file
62
backend/Admin/v2/realtime_dashboard.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/realtime_dashboard.php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
// التحقق من الصلاحيات
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized access. Admin role required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. الرحلات النشطة حالياً
|
||||||
|
$stmt = $con->prepare("SELECT COUNT(*) FROM ride WHERE status IN ('wait', 'started', 'arrived')");
|
||||||
|
$stmt->execute();
|
||||||
|
$active_rides = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// 2. السائقون المتصلون حالياً (أونلاين)
|
||||||
|
$stmt = $con->prepare("SELECT COUNT(*) FROM car_locations WHERE status = 'on'");
|
||||||
|
$stmt->execute();
|
||||||
|
$online_drivers = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// 3. إيرادات اليوم
|
||||||
|
$stmt = $con->prepare("SELECT IFNULL(SUM(price_for_passenger), 0) FROM ride WHERE status = 'Finished' AND DATE(created_at) = CURDATE()");
|
||||||
|
$stmt->execute();
|
||||||
|
$revenue_today = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// إيرادات الأمس (للمقارنة)
|
||||||
|
$stmt = $con->prepare("SELECT IFNULL(SUM(price_for_passenger), 0) FROM ride WHERE status = 'Finished' AND DATE(created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)");
|
||||||
|
$stmt->execute();
|
||||||
|
$revenue_yesterday = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// 4. شكاوى جديدة اليوم
|
||||||
|
$stmt = $con->prepare("SELECT COUNT(*) FROM complaint WHERE DATE(date_filed) = CURDATE() AND statusComplaint = 'Open'");
|
||||||
|
$stmt->execute();
|
||||||
|
$new_complaints = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
// 5. رخص تنتهي هذا الشهر
|
||||||
|
$stmt = $con->prepare("SELECT COUNT(*) FROM driver WHERE expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)");
|
||||||
|
$stmt->execute();
|
||||||
|
$expiring_licenses = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
$response['message'] = [
|
||||||
|
'active_rides' => (int)$active_rides,
|
||||||
|
'online_drivers' => (int)$online_drivers,
|
||||||
|
'revenue_today' => (float)$revenue_today,
|
||||||
|
'revenue_yesterday' => (float)$revenue_yesterday,
|
||||||
|
'new_complaints' => (int)$new_complaints,
|
||||||
|
'expiring_licenses' => (int)$expiring_licenses
|
||||||
|
];
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
66
backend/Admin/v2/security/audit_logs.php
Normal file
66
backend/Admin/v2/security/audit_logs.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/security/audit_logs.php
|
||||||
|
|
||||||
|
// ── سجل تتبع ────────────────────────────────────────────
|
||||||
|
$debugFile = __DIR__ . '/../../../logs/audit_debug.txt';
|
||||||
|
$logDir = dirname($debugFile);
|
||||||
|
if (!is_dir($logDir)) @mkdir($logDir, 0777, true);
|
||||||
|
|
||||||
|
@file_put_contents($debugFile, "[" . date('Y-m-d H:i:s') . "] === REQUEST START ===\n", FILE_APPEND);
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
|
||||||
|
@file_put_contents($debugFile, " → connect.php & encryption OK. user_id=$user_id | role=$role\n", FILE_APPEND);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
@file_put_contents($debugFile, " → Loading FAILED: " . $e->getMessage() . "\n", FILE_APPEND);
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'failure', 'message' => 'loading failed: ' . $e->getMessage()]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── فحص الصلاحيات ────────────────────────────────────────
|
||||||
|
if ($role !== 'super_admin' && $role !== 'admin') {
|
||||||
|
@file_put_contents($debugFile, " → BLOCKED: role=$role\n", FILE_APPEND);
|
||||||
|
jsonError("Unauthorized. role=$role", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// استعلام لجلب السجلات مع محاولة جلب الاسم من جدول الموظفين أو جدول المشرفين
|
||||||
|
$stmt = $con->prepare("
|
||||||
|
SELECT
|
||||||
|
l.id, l.admin_id, l.action, l.table_name, l.record_id, l.details, l.created_at,
|
||||||
|
COALESCE(e.name, au.name) as admin_name_raw
|
||||||
|
FROM admin_audit_log l
|
||||||
|
LEFT JOIN employee e ON l.admin_id COLLATE utf8mb4_general_ci = e.id COLLATE utf8mb4_general_ci
|
||||||
|
LEFT JOIN adminUser au ON l.admin_id COLLATE utf8mb4_general_ci = au.id COLLATE utf8mb4_general_ci
|
||||||
|
OR l.admin_id COLLATE utf8mb4_general_ci = au.email COLLATE utf8mb4_general_ci
|
||||||
|
ORDER BY l.created_at DESC
|
||||||
|
LIMIT 100
|
||||||
|
");
|
||||||
|
$stmt->execute();
|
||||||
|
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// معالجة البيانات: فك تشفير الأسماء إذا كانت مشفرة
|
||||||
|
foreach ($logs as &$log) {
|
||||||
|
$rawName = $log['admin_name_raw'];
|
||||||
|
if (!empty($rawName)) {
|
||||||
|
// محاولة فك التشفير
|
||||||
|
$decrypted = $encryptionHelper->decryptData($rawName);
|
||||||
|
$log['admin_name'] = ($decrypted !== false) ? $decrypted : $rawName;
|
||||||
|
} else {
|
||||||
|
$log['admin_name'] = 'أدمن غير معروف';
|
||||||
|
}
|
||||||
|
unset($log['admin_name_raw']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = count($logs);
|
||||||
|
@file_put_contents($debugFile, " → SUCCESS: fetched $count logs\n", FILE_APPEND);
|
||||||
|
|
||||||
|
jsonSuccess($logs);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
@file_put_contents($debugFile, " → QUERY ERROR: " . $e->getMessage() . "\n", FILE_APPEND);
|
||||||
|
jsonError('Query failed: ' . $e->getMessage(), 500);
|
||||||
|
}
|
||||||
|
?>
|
||||||
77
backend/Admin/v2/smart_alerts.php
Normal file
77
backend/Admin/v2/smart_alerts.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
// Admin/v2/smart_alerts.php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
// التحقق من الصلاحيات
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized access. Admin role required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$alerts = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. شكاوى جديدة غير محلولة (مفتوحة)
|
||||||
|
$stmt = $con->prepare("SELECT id, ride_id, complaint_type, date_filed FROM complaint WHERE statusComplaint = 'Open' ORDER BY date_filed DESC LIMIT 10");
|
||||||
|
$stmt->execute();
|
||||||
|
$open_complaints = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach($open_complaints as $c) {
|
||||||
|
$alerts[] = [
|
||||||
|
'type' => 'complaint',
|
||||||
|
'severity' => 'high',
|
||||||
|
'title' => 'شكوى جديدة (' . $c['complaint_type'] . ')',
|
||||||
|
'description' => "يوجد شكوى جديدة للرحلة رقم " . $c['ride_id'] . " تحتاج للمراجعة.",
|
||||||
|
'date' => $c['date_filed'],
|
||||||
|
'action_id' => $c['id']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. رحلات عالقة (في الانتظار لأكثر من 15 دقيقة)
|
||||||
|
$stmt = $con->prepare("SELECT id, created_at FROM ride WHERE status = 'wait' AND created_at < DATE_SUB(NOW(), INTERVAL 15 MINUTE) LIMIT 10");
|
||||||
|
$stmt->execute();
|
||||||
|
$stuck_rides = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach($stuck_rides as $r) {
|
||||||
|
$alerts[] = [
|
||||||
|
'type' => 'ride',
|
||||||
|
'severity' => 'medium',
|
||||||
|
'title' => 'رحلة عالقة قيد الانتظار',
|
||||||
|
'description' => "الرحلة رقم " . $r['id'] . " عالقة في حالة انتظار لأكثر من 15 دقيقة.",
|
||||||
|
'date' => $r['created_at'],
|
||||||
|
'action_id' => $r['id']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. رخص قيادة شارفت على الانتهاء (خلال 15 يوم القادمة)
|
||||||
|
$stmt = $con->prepare("SELECT id, first_name, last_name, phone, expiry_date FROM driver WHERE expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 15 DAY) LIMIT 10");
|
||||||
|
$stmt->execute();
|
||||||
|
$expiring_drivers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach($expiring_drivers as $d) {
|
||||||
|
// فك تشفير البيانات الحساسة
|
||||||
|
$firstName = $encryptionHelper->decryptData($d['first_name']);
|
||||||
|
$lastName = $encryptionHelper->decryptData($d['last_name']);
|
||||||
|
|
||||||
|
$alerts[] = [
|
||||||
|
'type' => 'license',
|
||||||
|
'severity' => 'warning',
|
||||||
|
'title' => 'رخصة كابتن قاربت على الانتهاء',
|
||||||
|
'description' => "رخصة الكابتن " . $firstName . " " . $lastName . " ستنتهي بتاريخ " . $d['expiry_date'] . ".",
|
||||||
|
'date' => date('Y-m-d H:i:s'),
|
||||||
|
'action_id' => $d['id']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ترتيب التنبيهات حسب الأحدث
|
||||||
|
usort($alerts, function($a, $b) {
|
||||||
|
return strtotime($b['date']) - strtotime($a['date']);
|
||||||
|
});
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $alerts
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
31
backend/Admin/view_errors.php
Executable file
31
backend/Admin/view_errors.php
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// استلام 'status' كمتغير اختياري لتصفية النتائج
|
||||||
|
// مثلاً: view_errors.php?status=new سيجلب الأخطاء الجديدة فقط
|
||||||
|
$status = filterRequest("status");
|
||||||
|
|
||||||
|
// إذا تم تحديد status، قم بتصفية النتائج بناءً عليه
|
||||||
|
if (!empty($status)) {
|
||||||
|
$stmt = $con->prepare("SELECT * FROM `error` WHERE `status` = ? ORDER BY `created_at` DESC");
|
||||||
|
$stmt->execute(array($status));
|
||||||
|
} else {
|
||||||
|
// إذا لم يتم تحديد status، قم بجلب جميع الأخطاء
|
||||||
|
$stmt = $con->prepare("SELECT * FROM `error` ORDER BY `created_at` DESC");
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// جلب جميع النتائج
|
||||||
|
$errors = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$count = $stmt->rowCount();
|
||||||
|
|
||||||
|
if ($count > 0) {
|
||||||
|
// إرجاع البيانات كـ JSON مع رسالة نجاح
|
||||||
|
echo json_encode(array("status" => "success", "data" => $errors));
|
||||||
|
} else {
|
||||||
|
// في حال عدم وجود أخطاء، إرجاع رسالة نجاح مع بيانات فارغة
|
||||||
|
echo json_encode(array("status" => "success", "data" => []));
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
0
backend/EgyptDocuments/error_log
Normal file
0
backend/EgyptDocuments/error_log
Normal file
71
backend/EgyptDocuments/uploadEgyptIdBack.php
Normal file
71
backend/EgyptDocuments/uploadEgyptIdBack.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// Get the image file from the request.
|
||||||
|
$image_file = $_FILES['image'];
|
||||||
|
$driverID = filterRequest("driverID");
|
||||||
|
|
||||||
|
// Define allowed extensions
|
||||||
|
$allowed_extensions = ['jpg', 'jpeg', 'png'];
|
||||||
|
|
||||||
|
// Get the image file from the request.
|
||||||
|
$image_file = $_FILES['image'];
|
||||||
|
|
||||||
|
// Check if the image file was uploaded successfully.
|
||||||
|
if ($image_file['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
echo "Image upload failed";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file information
|
||||||
|
$image_name = $image_file['name'];
|
||||||
|
$image_size = $image_file['size'];
|
||||||
|
$image_extension = strtolower(pathinfo($image_name, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
// Validate file extension
|
||||||
|
if (!in_array($image_extension, $allowed_extensions)) {
|
||||||
|
echo "Invalid image format";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate MIME type
|
||||||
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||||
|
$mime_type = finfo_file($finfo, $image_file['tmp_name']);
|
||||||
|
finfo_close($finfo);
|
||||||
|
|
||||||
|
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg'];
|
||||||
|
if (!in_array($mime_type, $allowed_mime_types)) {
|
||||||
|
echo "Invalid image format (MIME mismatch)";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a unique filename using timestamp and random string
|
||||||
|
$new_filename = $driverID . '.' . $image_extension;
|
||||||
|
|
||||||
|
// Set target directory for uploads
|
||||||
|
$target_dir = "card_image/";
|
||||||
|
|
||||||
|
// Construct target file path
|
||||||
|
$target_file = $target_dir . $new_filename;
|
||||||
|
|
||||||
|
// Move the image file to the target location
|
||||||
|
if (!move_uploaded_file($image_file['tmp_name'], $target_file)) {
|
||||||
|
echo json_encode(array('status' => "Failed to save image")); ;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store additional information (modify based on your needs)
|
||||||
|
$image_url = $target_dir . $new_filename; // Update if needed
|
||||||
|
$image_details = [
|
||||||
|
"name" => $image_name,
|
||||||
|
"size" => $image_size,
|
||||||
|
"extension" => $image_extension,
|
||||||
|
"url" => $image_url,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Use the image details for further processing (e.g., display, store in database)
|
||||||
|
// ...
|
||||||
|
|
||||||
|
echo json_encode(array('status' => 'Image uploaded successfully!'));
|
||||||
|
|
||||||
|
?>
|
||||||
71
backend/EgyptDocuments/uploadEgyptidFront.php
Normal file
71
backend/EgyptDocuments/uploadEgyptidFront.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// Get the image file from the request.
|
||||||
|
$image_file = $_FILES['image'];
|
||||||
|
$driverID = filterRequest("driverID");
|
||||||
|
|
||||||
|
// Define allowed extensions
|
||||||
|
$allowed_extensions = ['jpg', 'jpeg', 'png'];
|
||||||
|
|
||||||
|
// Get the image file from the request.
|
||||||
|
$image_file = $_FILES['image'];
|
||||||
|
|
||||||
|
// Check if the image file was uploaded successfully.
|
||||||
|
if ($image_file['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
echo "Image upload failed";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file information
|
||||||
|
$image_name = $image_file['name'];
|
||||||
|
$image_size = $image_file['size'];
|
||||||
|
$image_extension = strtolower(pathinfo($image_name, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
// Validate file extension
|
||||||
|
if (!in_array($image_extension, $allowed_extensions)) {
|
||||||
|
echo "Invalid image format";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate MIME type
|
||||||
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||||
|
$mime_type = finfo_file($finfo, $image_file['tmp_name']);
|
||||||
|
finfo_close($finfo);
|
||||||
|
|
||||||
|
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/jpg'];
|
||||||
|
if (!in_array($mime_type, $allowed_mime_types)) {
|
||||||
|
echo "Invalid image format (MIME mismatch)";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a unique filename using timestamp and random string
|
||||||
|
$new_filename = $driverID . '.' . $image_extension;
|
||||||
|
|
||||||
|
// Set target directory for uploads
|
||||||
|
$target_dir = "egypt/idFront/";
|
||||||
|
|
||||||
|
// Construct target file path
|
||||||
|
$target_file = $target_dir . $new_filename;
|
||||||
|
|
||||||
|
// Move the image file to the target location
|
||||||
|
if (!move_uploaded_file($image_file['tmp_name'], $target_file)) {
|
||||||
|
echo json_encode(array('status' => "Failed to save image")); ;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store additional information (modify based on your needs)
|
||||||
|
$image_url = $target_dir . $new_filename; // Update if needed
|
||||||
|
$image_details = [
|
||||||
|
"name" => $image_name,
|
||||||
|
"size" => $image_size,
|
||||||
|
"extension" => $image_extension,
|
||||||
|
"url" => $image_url,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Use the image details for further processing (e.g., display, store in database)
|
||||||
|
// ...
|
||||||
|
|
||||||
|
echo json_encode(array('status' => 'Image uploaded successfully!'));
|
||||||
|
|
||||||
|
?>
|
||||||
40
backend/aggregate_files.py
Normal file
40
backend/aggregate_files.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PROJECT_DIR = '.'
|
||||||
|
OUTPUT_FILE = 'intaleq_v1_secure_latest.md'
|
||||||
|
EXCLUDED_DIRS = {'.git', 'vendor', 'node_modules', '.gemini'}
|
||||||
|
EXCLUDED_FILES = {OUTPUT_FILE, 'aggregate_files.py'}
|
||||||
|
|
||||||
|
def aggregate_files():
|
||||||
|
with open(OUTPUT_FILE, 'w', encoding='utf-8') as outfile:
|
||||||
|
outfile.write(f'# Intaleq V1 - Secure Latest Version\n\n')
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(PROJECT_DIR):
|
||||||
|
# Prune excluded directories
|
||||||
|
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
if file in EXCLUDED_FILES:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filepath = os.path.join(root, file)
|
||||||
|
rel_path = os.path.relpath(filepath, PROJECT_DIR)
|
||||||
|
|
||||||
|
# We mainly want to include code files
|
||||||
|
if any(file.endswith(ext) for ext in ['.php', '.sql', '.ini', '.json', '.md', '.txt', '.py', '.sh']):
|
||||||
|
try:
|
||||||
|
with open(filepath, 'r', encoding='utf-8', errors='ignore') as infile:
|
||||||
|
content = infile.read()
|
||||||
|
|
||||||
|
outfile.write(f'## File: {rel_path}\n')
|
||||||
|
outfile.write(f'```\n')
|
||||||
|
outfile.write(content)
|
||||||
|
outfile.write(f'\n```\n\n')
|
||||||
|
print(f"Added: {rel_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Could not read {rel_path}: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
aggregate_files()
|
||||||
|
print(f"\nDone! File created: {OUTPUT_FILE}")
|
||||||
0
backend/auth/Tester/error_log
Normal file
0
backend/auth/Tester/error_log
Normal file
29
backend/auth/Tester/getTesterApp.php
Normal file
29
backend/auth/Tester/getTesterApp.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$appPlatform = filterRequest("appPlatform");
|
||||||
|
|
||||||
|
|
||||||
|
$sql = "SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
`testApp`
|
||||||
|
WHERE
|
||||||
|
appPlatform = '$appPlatform'-- AND isTest = 0;";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// Print the retrieved data
|
||||||
|
// echo json_encode($result);
|
||||||
|
jsonSuccess($data = $result);
|
||||||
|
} else {
|
||||||
|
// Print a failure message
|
||||||
|
|
||||||
|
jsonError($message = "No driver order data found");
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
23
backend/auth/Tester/updateTesterApp.php
Normal file
23
backend/auth/Tester/updateTesterApp.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$appPlatform = filterRequest("appPlatform");
|
||||||
|
|
||||||
|
$sql = "UPDATE
|
||||||
|
`testApp`
|
||||||
|
SET
|
||||||
|
`isTest` = '1'
|
||||||
|
WHERE
|
||||||
|
`testApp`.appPlatform = '$appPlatform';";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// Print a success message
|
||||||
|
jsonSuccess($message = "Test data updated successfully");
|
||||||
|
} else {
|
||||||
|
// Print a failure message
|
||||||
|
jsonError($message = "Failed to update driver order data");
|
||||||
|
}
|
||||||
|
?>
|
||||||
35
backend/auth/captin/addCriminalDocuments.php
Normal file
35
backend/auth/captin/addCriminalDocuments.php
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
?>
|
||||||
60
backend/auth/captin/deletecaptainAccounr.php
Normal file
60
backend/auth/captin/deletecaptainAccounr.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?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) {
|
||||||
|
// في حال حدوث خطأ في قاعدة البيانات (مثلاً تكرار الإضافة)
|
||||||
|
jsonError("Database Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
15
backend/auth/captin/error_log
Normal file
15
backend/auth/captin/error_log
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[21-May-2025 12:28:44 Europe/Berlin] PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'driver.education' in 'field list' in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php:43
|
||||||
|
Stack trace:
|
||||||
|
#0 /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php(43): PDO->prepare('SELECT\n driv...')
|
||||||
|
#1 {main}
|
||||||
|
thrown in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php on line 43
|
||||||
|
[21-May-2025 21:09:18 Europe/Berlin] PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'driver.education' in 'field list' in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php:43
|
||||||
|
Stack trace:
|
||||||
|
#0 /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php(43): PDO->prepare('SELECT\n driv...')
|
||||||
|
#1 {main}
|
||||||
|
thrown in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php on line 43
|
||||||
|
[22-May-2025 03:30:03 Europe/Berlin] PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'driver.education' in 'field list' in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php:43
|
||||||
|
Stack trace:
|
||||||
|
#0 /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php(43): PDO->prepare('SELECT\n driv...')
|
||||||
|
#1 {main}
|
||||||
|
thrown in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php on line 43
|
||||||
0
backend/auth/captin/forgetPassword.php
Normal file
0
backend/auth/captin/forgetPassword.php
Normal file
24
backend/auth/captin/getAccount.php
Normal file
24
backend/auth/captin/getAccount.php
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
?>
|
||||||
39
backend/auth/captin/getAllDriverSecure.php
Normal file
39
backend/auth/captin/getAllDriverSecure.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../connect.php';
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
`id`,
|
||||||
|
`phone`,
|
||||||
|
`email`,
|
||||||
|
`gender`,
|
||||||
|
`birthdate`,
|
||||||
|
`first_name`,
|
||||||
|
`last_name`,
|
||||||
|
`sosPhone`
|
||||||
|
FROM
|
||||||
|
`passengers`
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $con->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->rowCount() > 0) {
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// فك تشفير الحقول الحساسة
|
||||||
|
foreach ($rows as &$row) {
|
||||||
|
$row['phone'] = $encryptionHelper->decryptData($row['phone']);
|
||||||
|
$row['email'] = $encryptionHelper->decryptData($row['email']);
|
||||||
|
$row['gender'] = $encryptionHelper->decryptData($row['gender']);
|
||||||
|
$row['birthdate'] = $encryptionHelper->decryptData($row['birthdate']);
|
||||||
|
$row['first_name'] = $encryptionHelper->decryptData($row['first_name']);
|
||||||
|
$row['last_name'] = $encryptionHelper->decryptData($row['last_name']);
|
||||||
|
$row['sosPhone'] = $encryptionHelper->decryptData($row['sosPhone']);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonSuccess($rows);
|
||||||
|
} else {
|
||||||
|
jsonError("No wallet record found");
|
||||||
|
}
|
||||||
|
?>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user