<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../config/app.php';
// Security functions
function sanitizeInput($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
return $data;
}
function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function validateCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
function generateSecureSessionId() {
return bin2hex(random_bytes(32));
}
// Validation functions
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
function validatePhone($phone) {
return preg_match('/^[0-9]{10,15}$/', $phone);
}
function validateNumberPlate($numberPlate) {
// New format: KA + 2 digits + 1–2 uppercase letters + 4 digits (e.g., KA14A2602 or KA14DC0112)
return preg_match('/^KA[0-9]{2}[A-Z]{1,2}[0-9]{4}$/', $numberPlate);
}
function validatePassword($password) {
// SECURITY ENHANCEMENT: Increased minimum length to 16 characters
if (strlen($password) < 16) return false;
// Require uppercase, lowercase, number, special character
if (!preg_match('/[A-Z]/', $password)) return false;
if (!preg_match('/[a-z]/', $password)) return false;
if (!preg_match('/[0-9]/', $password)) return false;
if (!preg_match('/[^A-Za-z0-9]/', $password)) return false;
// SECURITY ENHANCEMENT: Check against expanded common password list
$commonPasswords = [
'password', '123456', 'qwerty', 'admin', 'password123', '123456789',
'letmein', 'welcome', 'monkey', 'dragon', 'master', 'football',
'baseball', 'superman', 'batman', 'trustno1', 'hello', 'shadow',
'michael', 'jordan', 'harley', 'ranger', 'buster', 'thomas',
'tigger', 'robert', 'soccer', 'tiger', 'charlie', 'golf',
'bond007', 'murphy', 'ginger', 'hammer', 'silver', 'golden',
'fire', 'cookie', 'monster', 'spider', 'cooper', 'carlos',
'david', 'steven', 'frank', 'dude', 'samson', 'zebra',
'thx1138', 'matrix', 'coffee', 'scooby', 'froggy', 'orange'
];
if (in_array(strtolower($password), $commonPasswords)) return false;
// SECURITY ENHANCEMENT: Check for sequential characters
if (preg_match('/(.)\1{2,}/', $password)) return false; // No 3+ repeated characters
// SECURITY ENHANCEMENT: Check for keyboard patterns
$keyboardPatterns = ['qwerty', 'asdfgh', 'zxcvbn', '123456', '654321'];
foreach ($keyboardPatterns as $pattern) {
if (stripos($password, $pattern) !== false) return false;
}
// SECURITY ENHANCEMENT: Check for common substitutions
$substitutions = [
'a' => ['@', '4'], 'e' => ['3'], 'i' => ['1', '!'],
'o' => ['0'], 's' => ['$', '5'], 't' => ['7']
];
foreach ($substitutions as $letter => $subs) {
foreach ($subs as $sub) {
$testPassword = str_replace($sub, $letter, strtolower($password));
if (in_array($testPassword, $commonPasswords)) return false;
}
}
return true;
}
// File upload functions
function validateImageFile($file) {
$allowedTypes = explode(',', $_ENV['ALLOWED_IMAGE_TYPES']);
$maxSize = $_ENV['MAX_FILE_SIZE'];
if ($file['error'] !== UPLOAD_ERR_OK) {
return false;
}
if ($file['size'] > $maxSize) {
return false;
}
// SECURITY ENHANCEMENT: Strict MIME type validation
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
// SECURITY FIX: Only allow specific image MIME types
$allowedMimeTypes = [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif'
];
if (!in_array($mimeType, $allowedMimeTypes)) {
logError('Invalid MIME type attempted: ' . $mimeType . ' for file: ' . $file['name']);
return false;
}
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
// SECURITY ENHANCEMENT: Validate both extension and MIME type match
$extensionMimeMap = [
'jpg' => ['image/jpeg', 'image/jpg'],
'jpeg' => ['image/jpeg', 'image/jpg'],
'png' => ['image/png'],
'gif' => ['image/gif']
];
if (!isset($extensionMimeMap[$extension]) || !in_array($mimeType, $extensionMimeMap[$extension])) {
logError('MIME type and extension mismatch: ' . $mimeType . ' vs ' . $extension . ' for file: ' . $file['name']);
return false;
}
// SECURITY ENHANCEMENT: Check file header for image signatures
$fileHandle = fopen($file['tmp_name'], 'rb');
if ($fileHandle) {
$header = fread($fileHandle, 8);
fclose($fileHandle);
$validHeaders = [
'jpg' => "\xFF\xD8\xFF",
'jpeg' => "\xFF\xD8\xFF",
'png' => "\x89PNG\r\n\x1A\n",
'gif' => "GIF87a",
'gif2' => "GIF89a"
];
$headerValid = false;
foreach ($validHeaders as $ext => $signature) {
if (strpos($header, $signature) === 0) {
$headerValid = true;
break;
}
}
if (!$headerValid) {
logError('Invalid file header detected for file: ' . $file['name']);
return false;
}
}
return in_array($extension, $allowedTypes);
}
function uploadFile($file, $directory, $prefix = '') {
$uploadDir = __DIR__ . '/../uploads/' . $directory . '/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$filename = $prefix . '_' . uniqid() . '_' . time() . '.' . $extension;
$filepath = $uploadDir . $filename;
if (move_uploaded_file($file['tmp_name'], $filepath)) {
return $directory . '/' . $filename;
}
return false;
}
// QR Code functions
function generateQRCode($data, $filename) {
try {
// Check if GD extension is available
if (!extension_loaded('gd')) {
// No fallback needed - return false to trigger client-side generation
return false;
}
require_once __DIR__ . '/../vendor/autoload.php';
// Create QR code with new v4.x API
$qrCode = \Endroid\QrCode\QrCode::create(json_encode($data))
->setSize(300)
->setMargin(10);
// Create PNG writer
$writer = new \Endroid\QrCode\Writer\PngWriter();
// Generate QR code
$result = $writer->write($qrCode);
// Ensure uploads directory exists
$uploadDir = __DIR__ . '/../uploads/qr_codes/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
// Write file
$filepath = $uploadDir . $filename . '.png';
$result->saveToFile($filepath);
return 'qr_codes/' . $filename . '.png';
} catch (Exception $e) {
logError('QR Code generation error: ' . $e->getMessage());
return false;
}
}
// Session management functions
function startSecureSession() {
if (session_status() === PHP_SESSION_NONE) {
// SECURITY ENHANCEMENT: Reduced session timeout to 8 hours
ini_set('session.gc_maxlifetime', 8 * 60 * 60);
ini_set('session.cookie_lifetime', 8 * 60 * 60);
ini_set('session.cookie_httponly', 1);
// Only set secure cookie for HTTPS connections
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
ini_set('session.cookie_secure', 1);
}
ini_set('session.use_strict_mode', 1);
// SECURITY ENHANCEMENT: Prevent session fixation
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_samesite', 'Strict');
session_start();
// SECURITY ENHANCEMENT: Regenerate session ID on first start
if (!isset($_SESSION['initialized'])) {
session_regenerate_id(true);
$_SESSION['initialized'] = true;
$_SESSION['created_at'] = time();
$_SESSION['last_activity'] = time();
}
}
}
function regenerateSession() {
session_regenerate_id(true);
}
function destroySession() {
session_destroy();
setcookie(session_name(), '', time() - 3600, '/');
}
function checkSessionExpiry() {
if (!isset($_SESSION['login_time'])) {
return false;
}
// SECURITY ENHANCEMENT: Reduced session lifetime to 8 hours
$sessionLifetime = 8 * 60 * 60; // 8 hours
// SECURITY ENHANCEMENT: Check for idle timeout (30 minutes)
$idleTimeout = 30 * 60; // 30 minutes
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > $idleTimeout) {
logError('Session expired due to inactivity for user: ' . ($_SESSION['user_id'] ?? 'unknown'));
destroySession();
return false;
}
if (time() - $_SESSION['login_time'] > $sessionLifetime) {
logError('Session expired due to time limit for user: ' . ($_SESSION['user_id'] ?? 'unknown'));
destroySession();
return false;
}
// SECURITY ENHANCEMENT: Update last activity
$_SESSION['last_activity'] = time();
return true;
}
function validateDatabaseSession() {
$db = Database::getInstance();
$sessionId = session_id();
$session = $db->fetch(
"SELECT * FROM user_sessions WHERE session_id = ? AND expires_at > NOW()",
[$sessionId]
);
if (!$session) {
destroySession();
return false;
}
return true;
}
// User authentication functions
function loginUser($user, $userType) {
startSecureSession();
regenerateSession();
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_type'] = $userType;
$_SESSION['user_name'] = $user['name'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['login_time'] = time();
// Store session in database
$db = Database::getInstance();
$sessionId = session_id();
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? '';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$expiresAt = date('Y-m-d H:i:s', time() + (24 * 60 * 60)); // 24 hours
$db->query(
"INSERT INTO user_sessions (account_id, user_type, session_id, ip_address, user_agent, expires_at)
VALUES (?, ?, ?, ?, ?, ?)",
[$user['id'], $userType, $sessionId, $ipAddress, $userAgent, $expiresAt]
);
}
function isLoggedIn() {
startSecureSession();
if (!isset($_SESSION['user_id'])) {
return false;
}
// Check both PHP session and database session expiry
if (!checkSessionExpiry() || !validateDatabaseSession()) {
return false;
}
return true;
}
function requireLogin() {
if (!isLoggedIn()) {
redirect('/auth/login.php');
}
}
function requireUserType($userType) {
requireLogin();
if ($_SESSION['user_type'] !== $userType) {
redirect('/dashboard/');
}
}
function getCurrentUser() {
if (!isLoggedIn()) {
return null;
}
$db = Database::getInstance();
$accountId = $_SESSION['user_id'];
$userType = $_SESSION['user_type'] ?? null;
if ($userType === 'auto_owner') {
return $db->fetch("SELECT * FROM auto_owners WHERE id = ?", [$accountId]);
}
if ($userType === 'passenger') {
return $db->fetch("SELECT * FROM passengers WHERE id = ?", [$accountId]);
}
if ($userType === 'operator') {
return $db->fetch("SELECT * FROM operators WHERE id = ?", [$accountId]);
}
if ($userType === 'validator') {
return $db->fetch("SELECT * FROM validators WHERE id = ?", [$accountId]);
}
if ($userType === 'admin') {
return $db->fetch("SELECT * FROM admins WHERE id = ?", [$accountId]);
}
if ($userType === 'super_admin') {
return $db->fetch("SELECT * FROM super_admins WHERE id = ?", [$accountId]);
}
return null;
}
function getCurrentUserType() {
startSecureSession();
return $_SESSION['user_type'] ?? null;
}
// Backend user management functions
function isBackendUser() {
if (!isLoggedIn()) {
return false;
}
$backendTypes = ['operator', 'validator', 'admin', 'super_admin'];
return in_array($_SESSION['user_type'], $backendTypes);
}
function requireBackendAccess() {
if (!isBackendUser()) {
redirect('/auth/login.php');
}
}
function requireSuperAdminAccess() {
requireLogin();
if ($_SESSION['user_type'] !== 'super_admin') {
redirect('/dashboard/');
}
}
function isSuperAdmin() {
startSecureSession();
return isset($_SESSION['user_type']) && $_SESSION['user_type'] === 'super_admin';
}
function requireAdminAccess() {
requireLogin();
$adminTypes = ['admin', 'super_admin'];
if (!in_array($_SESSION['user_type'], $adminTypes)) {
redirect('/dashboard/');
}
}
function requireOperatorAccess() {
requireLogin();
$operatorTypes = ['operator', 'validator', 'admin', 'super_admin'];
if (!in_array($_SESSION['user_type'], $operatorTypes)) {
redirect('/dashboard/');
}
}
function requireValidatorAccess() {
requireLogin();
$validatorTypes = ['validator', 'admin', 'super_admin'];
if (!in_array($_SESSION['user_type'], $validatorTypes)) {
redirect('/dashboard/');
}
}
// Backend user creation functions (Admin and SuperAdmin can create)
function createBackendUser($userData, $userType, $createdBy) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Unauthorized access'];
}
$db = Database::getInstance();
// Validate user type
$validTypes = ['operator', 'validator', 'admin'];
if (!in_array($userType, $validTypes)) {
return ['success' => false, 'message' => 'Invalid user type'];
}
// Check if email already exists in any user table
$tables = ['auto_owners', 'passengers', 'operators', 'validators', 'admins', 'super_admins'];
foreach ($tables as $table) {
$existingUser = $db->fetch("SELECT id FROM $table WHERE email = ?", [$userData['email']]);
if ($existingUser) {
return ['success' => false, 'message' => 'Email already exists in the system'];
}
}
try {
$hashedPassword = password_hash($userData['password'], PASSWORD_DEFAULT);
$sql = "INSERT INTO $userType (name, email, password, phone, status, created_by) VALUES (?, ?, ?, ?, ?, ?)";
$db->query($sql, [
$userData['name'],
$userData['email'],
$hashedPassword,
$userData['phone'] ?? '',
'pending',
$createdBy
]);
return ['success' => true, 'message' => ucfirst($userType) . ' created successfully', 'id' => $db->lastInsertId()];
} catch (Exception $e) {
logError('Backend user creation error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to create user'];
}
}
function getBackendUsers($userType = null) {
if (!isBackendUser()) {
return [];
}
$db = Database::getInstance();
if ($userType) {
$validTypes = ['operators', 'validators', 'admins', 'super_admins'];
if (!in_array($userType, $validTypes)) {
return [];
}
try {
return $db->fetchAll("SELECT * FROM $userType ORDER BY created_at DESC");
} catch (Exception $e) {
// Table doesn't exist yet
return [];
}
}
// Return all backend users
$users = [];
$tables = ['operators', 'validators', 'admins', 'super_admins'];
// If current user is admin (not super_admin), exclude admin and super_admin tables
if ($_SESSION['user_type'] === 'admin') {
$tables = ['operators', 'validators'];
}
foreach ($tables as $table) {
try {
$tableUsers = $db->fetchAll("SELECT *, '$table' as user_table FROM $table ORDER BY created_at DESC");
$users = array_merge($users, $tableUsers);
} catch (Exception $e) {
// Table doesn't exist yet, skip it
continue;
}
}
return $users;
}
// Location management functions
// From Locations (SuperAdmin only)
function createFromLocation($locationData) {
if (!isBackendUser() || $_SESSION['user_type'] !== 'super_admin') {
return ['success' => false, 'message' => 'Access denied. Only SuperAdmin can create from locations.'];
}
$db = Database::getInstance();
try {
// Validate required fields
$requiredFields = ['name', 'address', 'city', 'state'];
foreach ($requiredFields as $field) {
if (empty($locationData[$field])) {
return ['success' => false, 'message' => ucfirst($field) . ' is required'];
}
}
// Validate coordinates if provided
if (!empty($locationData['latitude']) && !is_numeric($locationData['latitude'])) {
return ['success' => false, 'message' => 'Invalid latitude value'];
}
if (!empty($locationData['longitude']) && !is_numeric($locationData['longitude'])) {
return ['success' => false, 'message' => 'Invalid longitude value'];
}
$sql = "INSERT INTO from_locations (name, address, city, state, pincode, latitude, longitude, description, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$db->query($sql, [
$locationData['name'],
$locationData['address'],
$locationData['city'],
$locationData['state'],
$locationData['pincode'] ?? null,
$locationData['latitude'] ?? null,
$locationData['longitude'] ?? null,
$locationData['description'] ?? null,
$_SESSION['user_id']
]);
$locationId = $db->lastInsertId();
logError('SuperAdmin created new from location: ' . $locationData['name'] . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'From location created successfully', 'id' => $locationId];
} catch (Exception $e) {
logError('From location creation error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to create from location'];
}
}
// To Locations (Admin and SuperAdmin can manage)
function createToLocation($locationData) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Access denied. Only Admin and SuperAdmin can create to locations.'];
}
$db = Database::getInstance();
try {
// Validate required fields
$requiredFields = ['name', 'address', 'city', 'state'];
foreach ($requiredFields as $field) {
if (empty($locationData[$field])) {
return ['success' => false, 'message' => ucfirst($field) . ' is required'];
}
}
// Validate coordinates if provided
if (!empty($locationData['latitude']) && !is_numeric($locationData['latitude'])) {
return ['success' => false, 'message' => 'Invalid latitude value'];
}
if (!empty($locationData['longitude']) && !is_numeric($locationData['longitude'])) {
return ['success' => false, 'message' => 'Invalid longitude value'];
}
$sql = "INSERT INTO to_locations (name, address, city, state, pincode, latitude, longitude, description, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$db->query($sql, [
$locationData['name'],
$locationData['address'],
$locationData['city'],
$locationData['state'],
$locationData['pincode'] ?? null,
$locationData['latitude'] ?? null,
$locationData['longitude'] ?? null,
$locationData['description'] ?? null,
$_SESSION['user_id']
]);
$locationId = $db->lastInsertId();
logError('User created new to location: ' . $locationData['name'] . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'To location created successfully', 'id' => $locationId];
} catch (Exception $e) {
logError('To location creation error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to create to location'];
}
}
// Update functions for both location types
function updateFromLocation($locationId, $locationData) {
if (!isBackendUser() || $_SESSION['user_type'] !== 'super_admin') {
return ['success' => false, 'message' => 'Access denied. Only SuperAdmin can update from locations.'];
}
$db = Database::getInstance();
try {
// Validate required fields
$requiredFields = ['name', 'address', 'city', 'state'];
foreach ($requiredFields as $field) {
if (empty($locationData[$field])) {
return ['success' => false, 'message' => ucfirst($field) . ' is required'];
}
}
// Validate coordinates if provided
if (!empty($locationData['latitude']) && !is_numeric($locationData['latitude'])) {
return ['success' => false, 'message' => 'Invalid latitude value'];
}
if (!empty($locationData['longitude']) && !is_numeric($locationData['longitude'])) {
return ['success' => false, 'message' => 'Invalid longitude value'];
}
$sql = "UPDATE from_locations SET name = ?, address = ?, city = ?, state = ?, pincode = ?,
latitude = ?, longitude = ?, description = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?";
$db->query($sql, [
$locationData['name'],
$locationData['address'],
$locationData['city'],
$locationData['state'],
$locationData['pincode'] ?? null,
$locationData['latitude'] ?? null,
$locationData['longitude'] ?? null,
$locationData['description'] ?? null,
$locationId
]);
logError('SuperAdmin updated from location: ' . $locationData['name'] . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'From location updated successfully'];
} catch (Exception $e) {
logError('From location update error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update from location'];
}
}
function updateToLocation($locationId, $locationData) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Access denied. Only Admin and SuperAdmin can update to locations.'];
}
$db = Database::getInstance();
try {
// Validate required fields
$requiredFields = ['name', 'address', 'city', 'state'];
foreach ($requiredFields as $field) {
if (empty($locationData[$field])) {
return ['success' => false, 'message' => ucfirst($field) . ' is required'];
}
}
// Validate coordinates if provided
if (!empty($locationData['latitude']) && !is_numeric($locationData['latitude'])) {
return ['success' => false, 'message' => 'Invalid latitude value'];
}
if (!empty($locationData['longitude']) && !is_numeric($locationData['longitude'])) {
return ['success' => false, 'message' => 'Invalid longitude value'];
}
$sql = "UPDATE to_locations SET name = ?, address = ?, city = ?, state = ?, pincode = ?,
latitude = ?, longitude = ?, description = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?";
$db->query($sql, [
$locationData['name'],
$locationData['address'],
$locationData['city'],
$locationData['state'],
$locationData['pincode'] ?? null,
$locationData['latitude'] ?? null,
$locationData['longitude'] ?? null,
$locationData['description'] ?? null,
$locationId
]);
logError('User updated to location: ' . $locationData['name'] . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'To location updated successfully'];
} catch (Exception $e) {
logError('To location update error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update to location'];
}
}
// Delete functions for both location types
function deleteFromLocation($locationId) {
if (!isBackendUser() || $_SESSION['user_type'] !== 'super_admin') {
return ['success' => false, 'message' => 'Access denied. Only SuperAdmin can delete from locations.'];
}
$db = Database::getInstance();
try {
// Check if location exists
$location = $db->fetch("SELECT name FROM from_locations WHERE id = ?", [$locationId]);
if (!$location) {
return ['success' => false, 'message' => 'From location not found'];
}
$db->query("DELETE FROM from_locations WHERE id = ?", [$locationId]);
logError('SuperAdmin deleted from location: ' . $location['name'] . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'From location deleted successfully'];
} catch (Exception $e) {
logError('From location deletion error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to delete from location'];
}
}
function deleteToLocation($locationId) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Access denied. Only Admin and SuperAdmin can delete to locations.'];
}
$db = Database::getInstance();
try {
// Check if location exists
$location = $db->fetch("SELECT name FROM to_locations WHERE id = ?", [$locationId]);
if (!$location) {
return ['success' => false, 'message' => 'To location not found'];
}
$db->query("DELETE FROM to_locations WHERE id = ?", [$locationId]);
logError('User deleted to location: ' . $location['name'] . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'To location deleted successfully'];
} catch (Exception $e) {
logError('To location deletion error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to delete to location'];
}
}
// Get functions for both location types
function getFromLocations($status = null) {
if (!isBackendUser()) {
return [];
}
$db = Database::getInstance();
try {
if ($status) {
return $db->fetchAll("SELECT * FROM from_locations WHERE status = ? ORDER BY name ASC", [$status]);
}
return $db->fetchAll("SELECT * FROM from_locations ORDER BY name ASC");
} catch (Exception $e) {
// Table might not exist yet, return empty array
logError('Error getting from locations: ' . $e->getMessage());
return [];
}
}
function getToLocations($status = null) {
if (!isBackendUser()) {
return [];
}
$db = Database::getInstance();
try {
if ($status) {
return $db->fetchAll("SELECT * FROM to_locations WHERE status = ? ORDER BY name ASC", [$status]);
}
return $db->fetchAll("SELECT * FROM to_locations ORDER BY name ASC");
} catch (Exception $e) {
// Table might not exist yet, return empty array
logError('Error getting to locations: ' . $e->getMessage());
return [];
}
}
// Get individual location functions
function getFromLocationById($locationId) {
if (!isBackendUser()) {
return null;
}
$db = Database::getInstance();
return $db->fetch("SELECT * FROM from_locations WHERE id = ?", [$locationId]);
}
function getToLocationById($locationId) {
if (!isBackendUser()) {
return null;
}
$db = Database::getInstance();
return $db->fetch("SELECT * FROM to_locations WHERE id = ?", [$locationId]);
}
// Toggle status functions for both location types
function toggleFromLocationStatus($locationId) {
if (!isBackendUser() || $_SESSION['user_type'] !== 'super_admin') {
return ['success' => false, 'message' => 'Access denied. Only SuperAdmin can change from location status.'];
}
$db = Database::getInstance();
try {
$location = $db->fetch("SELECT name, status FROM from_locations WHERE id = ?", [$locationId]);
if (!$location) {
return ['success' => false, 'message' => 'From location not found'];
}
$newStatus = $location['status'] === 'active' ? 'inactive' : 'active';
$db->query("UPDATE from_locations SET status = ? WHERE id = ?", [$newStatus, $locationId]);
logError('SuperAdmin changed from location status: ' . $location['name'] . ' to ' . $newStatus . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'From location status updated successfully', 'new_status' => $newStatus];
} catch (Exception $e) {
logError('From location status update error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update from location status'];
}
}
function toggleToLocationStatus($locationId) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Access denied. Only Admin and SuperAdmin can change to location status.'];
}
$db = Database::getInstance();
try {
$location = $db->fetch("SELECT name, status FROM to_locations WHERE id = ?", [$locationId]);
if (!$location) {
return ['success' => false, 'message' => 'To location not found'];
}
$newStatus = $location['status'] === 'active' ? 'inactive' : 'active';
$db->query("UPDATE to_locations SET status = ? WHERE id = ?", [$newStatus, $locationId]);
logError('User changed to location status: ' . $location['name'] . ' to ' . $newStatus . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'To location status updated successfully', 'new_status' => $newStatus];
} catch (Exception $e) {
logError('To location status update error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update to location status'];
}
}
// Auto Rickshaw Management Functions (Admin/Validator access)
function getAutoRickshaws($status = null, $limit = null) {
if (!isBackendUser()) {
return [];
}
try {
$db = Database::getInstance();
$pdo = $db->getConnection();
// Check if auto_rickshaws table exists
$result = $pdo->query("SHOW TABLES LIKE 'auto_rickshaws'");
if ($result->rowCount() === 0) {
logError('Auto rickshaws table does not exist');
return [];
}
// Check if auto_owners table exists
$result = $pdo->query("SHOW TABLES LIKE 'auto_owners'");
if ($result->rowCount() === 0) {
logError('Auto owners table does not exist');
return [];
}
$sql = "SELECT ar.*, ao.name as owner_name, ao.email as owner_email, ao.phone as owner_phone
FROM auto_rickshaws ar
JOIN auto_owners ao ON ar.owner_id = ao.id";
$params = [];
if ($status) {
$sql .= " WHERE ar.status = ?";
$params[] = $status;
}
$sql .= " ORDER BY ar.created_at DESC";
if ($limit) {
$sql .= " LIMIT ?";
$params[] = $limit;
}
return $db->fetchAll($sql, $params);
} catch (Exception $e) {
logError('Error fetching auto rickshaws: ' . $e->getMessage());
return [];
}
}
function getAutoRickshawById($id) {
if (!isBackendUser()) {
return null;
}
$db = Database::getInstance();
$sql = "SELECT ar.*, ao.name as owner_name, ao.email as owner_email, ao.phone as owner_phone
FROM auto_rickshaws ar
JOIN auto_owners ao ON ar.owner_id = ao.id
WHERE ar.id = ?";
try {
return $db->fetch($sql, [$id]);
} catch (Exception $e) {
logError('Error fetching auto rickshaw: ' . $e->getMessage());
return null;
}
}
function updateAutoRickshawStatus($id, $status, $adminId) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin', 'validator'])) {
return ['success' => false, 'message' => 'Access denied'];
}
$validStatuses = ['active', 'inactive', 'pending_approval'];
if (!in_array($status, $validStatuses)) {
return ['success' => false, 'message' => 'Invalid status'];
}
$db = Database::getInstance();
try {
$rickshaw = $db->fetch("SELECT * FROM auto_rickshaws WHERE id = ?", [$id]);
if (!$rickshaw) {
return ['success' => false, 'message' => 'Auto rickshaw not found'];
}
$db->query("UPDATE auto_rickshaws SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", [$status, $id]);
logError('Admin updated auto rickshaw status: ID ' . $id . ' to ' . $status . ' by admin ID ' . $adminId);
return ['success' => true, 'message' => 'Auto rickshaw status updated successfully'];
} catch (Exception $e) {
logError('Error updating auto rickshaw status: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update status'];
}
}
// Fare Management Functions (Admin access)
function getFareDetails($status = null) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return [];
}
$db = Database::getInstance();
$sql = "SELECT fd.*,
fl.name as from_location_name, fl.city as from_city, fl.state as from_state,
tl.name as to_location_name, tl.city as to_city, tl.state as to_state,
a.name as created_by_name
FROM fare_details fd
JOIN from_locations fl ON fd.from_location_id = fl.id
JOIN to_locations tl ON fd.to_location_id = tl.id
JOIN admins a ON fd.created_by = a.id";
$params = [];
if ($status) {
$sql .= " WHERE fd.status = ?";
$params[] = $status;
}
$sql .= " ORDER BY fl.name, tl.name";
try {
return $db->fetchAll($sql, $params);
} catch (Exception $e) {
logError('Error fetching fare details: ' . $e->getMessage());
return [];
}
}
function getFareDetailById($id) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return null;
}
$db = Database::getInstance();
$sql = "SELECT fd.*,
fl.name as from_location_name, fl.city as from_city, fl.state as from_state,
tl.name as to_location_name, tl.city as to_city, tl.state as to_state,
a.name as created_by_name
FROM fare_details fd
JOIN from_locations fl ON fd.from_location_id = fl.id
JOIN to_locations tl ON fd.to_location_id = tl.id
JOIN admins a ON fd.created_by = a.id
WHERE fd.id = ?";
try {
return $db->fetch($sql, [$id]);
} catch (Exception $e) {
logError('Error fetching fare detail: ' . $e->getMessage());
return null;
}
}
function createFareDetail($fareData, $adminId) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Access denied'];
}
$db = Database::getInstance();
// Check if at least one fare type is provided
$hasFare = !empty($fareData['fix_rate']) ||
(!empty($fareData['base_fare']) && !empty($fareData['per_km_rate']) && !empty($fareData['minimum_fare']));
if (!$hasFare) {
return ['success' => false, 'message' => 'Please provide either a Fix Rate or all three: Base Fare, Per KM Rate, and Minimum Fare'];
}
// Check if route already exists
$existing = $db->fetch("SELECT id FROM fare_details WHERE from_location_id = ? AND to_location_id = ?",
[$fareData['from_location_id'], $fareData['to_location_id']]);
if ($existing) {
return ['success' => false, 'message' => 'Fare for this route already exists'];
}
try {
$sql = "INSERT INTO fare_details (from_location_id, to_location_id, base_fare, per_km_rate, minimum_fare,
fix_rate, distance_km, estimated_duration_minutes, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$db->query($sql, [
$fareData['from_location_id'],
$fareData['to_location_id'],
$fareData['base_fare'] ?? null,
$fareData['per_km_rate'] ?? null,
$fareData['minimum_fare'] ?? null,
$fareData['fix_rate'] ?? null,
$fareData['distance_km'] ?? null,
$fareData['estimated_duration_minutes'] ?? null,
$adminId
]);
logError('Admin created fare detail: Route ' . $fareData['from_location_id'] . ' to ' . $fareData['to_location_id'] . ' by admin ID ' . $adminId);
return ['success' => true, 'message' => 'Fare detail created successfully', 'id' => $db->lastInsertId()];
} catch (Exception $e) {
logError('Error creating fare detail: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to create fare detail'];
}
}
function updateFareDetail($id, $fareData, $adminId) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Access denied'];
}
$db = Database::getInstance();
try {
$existing = $db->fetch("SELECT * FROM fare_details WHERE id = ?", [$id]);
if (!$existing) {
return ['success' => false, 'message' => 'Fare detail not found'];
}
// Check if route already exists for different ID
if (isset($fareData['from_location_id']) && isset($fareData['to_location_id'])) {
$duplicate = $db->fetch("SELECT id FROM fare_details WHERE from_location_id = ? AND to_location_id = ? AND id != ?",
[$fareData['from_location_id'], $fareData['to_location_id'], $id]);
if ($duplicate) {
return ['success' => false, 'message' => 'Fare for this route already exists'];
}
}
// Check if at least one fare type is provided
$hasFare = !empty($fareData['fix_rate']) ||
(!empty($fareData['base_fare']) && !empty($fareData['per_km_rate']) && !empty($fareData['minimum_fare']));
if (!$hasFare) {
return ['success' => false, 'message' => 'Please provide either a Fix Rate or all three: Base Fare, Per KM Rate, and Minimum Fare'];
}
$sql = "UPDATE fare_details SET
from_location_id = ?, to_location_id = ?, base_fare = ?, per_km_rate = ?,
minimum_fare = ?, fix_rate = ?, distance_km = ?, estimated_duration_minutes = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?";
$db->query($sql, [
$fareData['from_location_id'] ?? $existing['from_location_id'],
$fareData['to_location_id'] ?? $existing['to_location_id'],
$fareData['base_fare'] ?? $existing['base_fare'],
$fareData['per_km_rate'] ?? $existing['per_km_rate'],
$fareData['minimum_fare'] ?? $existing['minimum_fare'],
$fareData['fix_rate'] ?? $existing['fix_rate'],
$fareData['distance_km'] ?? $existing['distance_km'],
$fareData['estimated_duration_minutes'] ?? $existing['estimated_duration_minutes'],
$id
]);
logError('Admin updated fare detail: ID ' . $id . ' by admin ID ' . $adminId);
return ['success' => true, 'message' => 'Fare detail updated successfully'];
} catch (Exception $e) {
logError('Error updating fare detail: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update fare detail'];
}
}
function deleteFareDetail($id, $adminId) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Access denied'];
}
$db = Database::getInstance();
try {
$existing = $db->fetch("SELECT * FROM fare_details WHERE id = ?", [$id]);
if (!$existing) {
return ['success' => false, 'message' => 'Fare detail not found'];
}
$db->query("DELETE FROM fare_details WHERE id = ?", [$id]);
logError('Admin deleted fare detail: ID ' . $id . ' by admin ID ' . $adminId);
return ['success' => true, 'message' => 'Fare detail deleted successfully'];
} catch (Exception $e) {
logError('Error deleting fare detail: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to delete fare detail'];
}
}
function toggleFareDetailStatus($id, $adminId) {
if (!isBackendUser() || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
return ['success' => false, 'message' => 'Access denied'];
}
$db = Database::getInstance();
try {
$fare = $db->fetch("SELECT * FROM fare_details WHERE id = ?", [$id]);
if (!$fare) {
return ['success' => false, 'message' => 'Fare detail not found'];
}
$newStatus = $fare['status'] === 'active' ? 'inactive' : 'active';
$db->query("UPDATE fare_details SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", [$newStatus, $id]);
logError('Admin toggled fare detail status: ID ' . $id . ' to ' . $newStatus . ' by admin ID ' . $adminId);
return ['success' => true, 'message' => 'Fare detail status updated successfully', 'new_status' => $newStatus];
} catch (Exception $e) {
logError('Error toggling fare detail status: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update status'];
}
}
// Legacy function for backward compatibility
function getLocations($status = null) {
// Return from locations for backward compatibility
return getFromLocations($status);
}
function getLocationById($locationId) {
if (!isBackendUser()) {
return null;
}
$db = Database::getInstance();
return $db->fetch("SELECT * FROM locations WHERE id = ?", [$locationId]);
}
function toggleLocationStatus($locationId) {
if (!isBackendUser() || $_SESSION['user_type'] !== 'super_admin') {
return ['success' => false, 'message' => 'Access denied. Only SuperAdmin can change location status.'];
}
$db = Database::getInstance();
try {
$location = $db->fetch("SELECT name, status FROM locations WHERE id = ?", [$locationId]);
if (!$location) {
return ['success' => false, 'message' => 'Location not found'];
}
$newStatus = $location['status'] === 'active' ? 'inactive' : 'active';
$db->query("UPDATE locations SET status = ? WHERE id = ?", [$newStatus, $locationId]);
logError('SuperAdmin changed location status: ' . $location['name'] . ' to ' . $newStatus . ' (ID: ' . $locationId . ')');
return ['success' => true, 'message' => 'Location status updated successfully', 'new_status' => $newStatus];
} catch (Exception $e) {
logError('Location status update error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update location status'];
}
}
// Response functions
function jsonResponse($data, $statusCode = 200) {
http_response_code($statusCode);
header('Content-Type: application/json');
echo json_encode($data);
exit();
}
function redirect($url) {
if (!preg_match('#^https?://#i', $url)) {
$url = url_for($url);
}
header('Location: ' . $url);
exit();
}
// Error handling functions
function logError($message, $context = []) {
$logMessage = date('Y-m-d H:i:s') . ' - ' . $message;
if (!empty($context)) {
$logMessage .= ' - Context: ' . json_encode($context);
}
error_log($logMessage);
}
// Utility functions
function formatDate($date) {
return date('M d, Y H:i', strtotime($date));
}
function formatCurrency($amount) {
return '₹' . number_format($amount, 2);
}
function getStatusBadge($status) {
$badges = [
'active' => 'success',
'inactive' => 'danger',
'pending' => 'warning',
'pending_approval' => 'warning'
];
$badgeClass = $badges[$status] ?? 'secondary';
return '<span class="badge bg-' . $badgeClass . '">' . ucfirst(str_replace('_', ' ', $status)) . '</span>';
}
// Utility for building URLs relative to the application base path
function url_for($path = '') {
$base = rtrim(APP_BASE_PATH ?? '', '/');
$path = '/' . ltrim($path, '/');
return $base . $path;
}
// Initialize database on first load
if (!function_exists('initializeDatabase')) {
require_once __DIR__ . '/../config/database.php';
}
// Commission functions
function getCommissionConfig() {
$db = Database::getInstance();
$config = $db->fetch("SELECT * FROM commission_config WHERE is_active = 1 ORDER BY id DESC LIMIT 1");
if (!$config) {
// Return default configuration if none exists
return [
'commission_type' => 'percentage',
'commission_value' => 10.00,
'min_commission' => null,
'max_commission' => null
];
}
return $config;
}
function calculateCommission($baseFare) {
$config = getCommissionConfig();
if ($config['commission_type'] === 'percentage') {
$commission = $baseFare * ($config['commission_value'] / 100);
// Apply min/max constraints if set
if ($config['min_commission'] !== null && $commission < $config['min_commission']) {
$commission = $config['min_commission'];
}
if ($config['max_commission'] !== null && $commission > $config['max_commission']) {
$commission = $config['max_commission'];
}
return $commission;
} else {
// Fixed commission amount
return $config['commission_value'];
}
}
function updateCommissionConfig($commissionType, $commissionValue, $minCommission = null, $maxCommission = null, $description = '') {
if (!isSuperAdmin()) {
return ['success' => false, 'message' => 'Access denied. Only SuperAdmin can update commission configuration.'];
}
$db = Database::getInstance();
try {
// Deactivate current active config
$db->query("UPDATE commission_config SET is_active = 0 WHERE is_active = 1");
// Insert new configuration
$sql = "INSERT INTO commission_config (commission_type, commission_value, min_commission, max_commission, description, created_by) VALUES (?, ?, ?, ?, ?, ?)";
$db->query($sql, [$commissionType, $commissionValue, $minCommission, $maxCommission, $description, $_SESSION['user_id']]);
logError('SuperAdmin updated commission configuration: ' . $commissionType . ' = ' . $commissionValue);
return ['success' => true, 'message' => 'Commission configuration updated successfully'];
} catch (Exception $e) {
logError('Commission configuration update error: ' . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update commission configuration'];
}
}
function getCommissionHistory() {
if (!isSuperAdmin()) {
return [];
}
$db = Database::getInstance();
return $db->fetchAll("SELECT * FROM commission_config ORDER BY created_at DESC");
}
function getBackendUserById($userId) {
if (!$userId) {
return null;
}
$db = Database::getInstance();
// Check in all backend user tables, but prioritize super_admins first
// since commission configurations are only created by SuperAdmins
$tables = ['super_admins', 'admins', 'validators', 'operators'];
foreach ($tables as $table) {
try {
$user = $db->fetch("SELECT * FROM $table WHERE id = ?", [$userId]);
if ($user) {
return $user;
}
} catch (Exception $e) {
// Table might not exist yet, continue to next table
continue;
}
}
return null;
}
function enforceSessionSecurity() {
// Check session age
if (isset($_SESSION['login_time']) && (time() - $_SESSION['login_time']) > 3600) {
destroySession();
redirect('/auth/login.php?expired=1');
}
// Validate session against database
if (!validateDatabaseSession()) {
destroySession();
redirect('/auth/login.php?invalid=1');
}
// Regenerate session ID periodically
if (isset($_SESSION['last_regeneration']) && (time() - $_SESSION['last_regeneration']) > 1800) {
regenerateSession();
$_SESSION['last_regeneration'] = time();
}
}
function checkRateLimit($action, $maxAttempts = 5, $timeWindow = 300) {
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$key = "rate_limit:{$action}:{$ip}";
$attempts = getRateLimitAttempts($key);
if ($attempts >= $maxAttempts) {
logError('Rate limit exceeded for action: ' . $action . ' from IP: ' . $ip);
return false; // Rate limit exceeded
}
incrementRateLimit($key, $timeWindow);
return true;
}
function getRateLimitAttempts($key) {
// Simple in-memory rate limiting (in production, use Redis or database)
if (!isset($_SESSION['rate_limits'])) {
$_SESSION['rate_limits'] = [];
}
if (!isset($_SESSION['rate_limits'][$key])) {
return 0;
}
$data = $_SESSION['rate_limits'][$key];
if (time() - $data['timestamp'] > $data['window']) {
return 0; // Window expired
}
return $data['attempts'];
}
function incrementRateLimit($key, $timeWindow) {
if (!isset($_SESSION['rate_limits'])) {
$_SESSION['rate_limits'] = [];
}
$_SESSION['rate_limits'][$key] = [
'attempts' => ($_SESSION['rate_limits'][$key]['attempts'] ?? 0) + 1,
'timestamp' => time(),
'window' => $timeWindow
];
}
// SECURITY ENHANCEMENT: Enhanced rate limiting for authentication
function checkAuthRateLimit($action, $maxAttempts = 3, $timeWindow = 900) {
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$key = "auth_rate_limit:{$action}:{$ip}";
$attempts = getRateLimitAttempts($key);
if ($attempts >= $maxAttempts) {
logError('Authentication rate limit exceeded for action: ' . $action . ' from IP: ' . $ip);
return false;
}
incrementRateLimit($key, $timeWindow);
return true;
}
// SECURITY ENHANCEMENT: Check for suspicious activity
function checkSuspiciousActivity($userId, $action) {
$key = "suspicious:{$userId}:{$action}";
$attempts = getRateLimitAttempts($key);
if ($attempts > 10) {
logError('Suspicious activity detected for user: ' . $userId . ' action: ' . $action);
return false;
}
incrementRateLimit($key, 3600); // 1 hour window
return true;
}