<?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; }