pdo = $db->getConnection(); } catch (Exception $e) { logError('Admin auth database connection failed: ' . $e->getMessage()); throw new Exception('Database connection failed'); } } public function login($username, $password, $rememberMe = false) { try { // Find admin user $stmt = $this->pdo->prepare(" SELECT id, username, email, password, full_name, role, status FROM admin_users WHERE (username = ? OR email = ?) AND status = 'active' "); $stmt->execute([$username, $username]); $admin = $stmt->fetch(); if (!$admin || !verifyPassword($password, $admin['password'])) { logError('Admin login failed', ['username' => $username]); return false; } // Create session session_start(); session_regenerate_id(true); $_SESSION['admin_logged_in'] = true; $_SESSION['admin_id'] = $admin['id']; $_SESSION['admin_username'] = $admin['username']; $_SESSION['admin_full_name'] = $admin['full_name']; $_SESSION['admin_role'] = $admin['role']; $_SESSION['admin_login_time'] = time(); // Update last login $stmt = $this->pdo->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?"); $stmt->execute([$admin['id']]); // Handle remember me if ($rememberMe) { $token = generateSecureToken(); $expires = date('Y-m-d H:i:s', strtotime('+30 days')); $stmt = $this->pdo->prepare(" INSERT INTO admin_sessions (admin_id, session_token, expires_at) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE session_token = ?, expires_at = ? "); $stmt->execute([$admin['id'], $token, $expires, $token, $expires]); setcookie('admin_remember_token', $token, strtotime('+30 days'), '/', '', true, true); } logError('Admin login successful', [ 'admin_id' => $admin['id'], 'username' => $admin['username'], 'role' => $admin['role'] ]); return true; } catch (Exception $e) { logError('Admin login error', ['username' => $username, 'error' => $e->getMessage()]); return false; } } public function logout() { session_start(); if (isset($_SESSION['admin_id'])) { logError('Admin logout', ['admin_id' => $_SESSION['admin_id']]); } // Clear remember token if (isset($_COOKIE['admin_remember_token'])) { try { $stmt = $this->pdo->prepare("DELETE FROM admin_sessions WHERE session_token = ?"); $stmt->execute([$_COOKIE['admin_remember_token']]); } catch (Exception $e) { logError('Error clearing admin remember token', ['error' => $e->getMessage()]); } setcookie('admin_remember_token', '', time() - 3600, '/', '', true, true); } // Clear session session_unset(); session_destroy(); header('Location: support.php'); exit; } public function isLoggedIn() { session_start(); if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) { // Check session timeout (4 hours) if (isset($_SESSION['admin_login_time']) && (time() - $_SESSION['admin_login_time']) > 14400) { $this->logout(); return false; } return true; } // Check remember token if (isset($_COOKIE['admin_remember_token'])) { return $this->checkRememberToken($_COOKIE['admin_remember_token']); } return false; } private function checkRememberToken($token) { try { $stmt = $this->pdo->prepare(" SELECT aus.admin_id, au.username, au.email, au.full_name, au.role FROM admin_sessions aus JOIN admin_users au ON aus.admin_id = au.id WHERE aus.session_token = ? AND aus.expires_at > NOW() AND au.status = 'active' "); $stmt->execute([$token]); $session = $stmt->fetch(); if ($session) { // Auto-login with remember token session_start(); session_regenerate_id(true); $_SESSION['admin_logged_in'] = true; $_SESSION['admin_id'] = $session['admin_id']; $_SESSION['admin_username'] = $session['username']; $_SESSION['admin_full_name'] = $session['full_name']; $_SESSION['admin_role'] = $session['role']; $_SESSION['admin_login_time'] = time(); // Update last login $stmt = $this->pdo->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?"); $stmt->execute([$session['admin_id']]); return true; } else { // Invalid or expired token - clean up setcookie('admin_remember_token', '', time() - 3600, '/', '', true, true); } } catch (Exception $e) { logError('Error checking admin remember token', ['error' => $e->getMessage()]); } return false; } public function requireAdmin() { if (!$this->isLoggedIn()) { header('Location: support.php'); exit; } } public function getCurrentAdmin() { if (!$this->isLoggedIn()) { return null; } session_start(); return [ 'id' => $_SESSION['admin_id'], 'username' => $_SESSION['admin_username'], 'full_name' => $_SESSION['admin_full_name'], 'role' => $_SESSION['admin_role'] ]; } public function createAdmin($username, $email, $password, $fullName, $role = 'admin') { try { // Check if username or email already exists $stmt = $this->pdo->prepare("SELECT id FROM admin_users WHERE username = ? OR email = ?"); $stmt->execute([$username, $email]); if ($stmt->fetch()) { return false; } // Create admin user $hashedPassword = hashPassword($password); $stmt = $this->pdo->prepare(" INSERT INTO admin_users (username, email, password, full_name, role, status) VALUES (?, ?, ?, ?, ?, 'active') "); $result = $stmt->execute([$username, $email, $hashedPassword, $fullName, $role]); if ($result) { logError('Admin user created', [ 'username' => $username, 'email' => $email, 'role' => $role ]); } return $result; } catch (Exception $e) { logError('Error creating admin user', ['username' => $username, 'error' => $e->getMessage()]); return false; } } public function getAllTickets($status = null, $priority = null, $limit = 50, $offset = 0) { try { $where = "1=1"; $params = []; if ($status) { $where .= " AND st.status = ?"; $params[] = $status; } if ($priority) { $where .= " AND st.priority = ?"; $params[] = $priority; } $stmt = $this->pdo->prepare(" SELECT st.*, u.email as user_email, au.full_name as assigned_to_name, (SELECT COUNT(*) FROM support_messages sm WHERE sm.ticket_id = st.id AND sm.sender_type = 'user') as user_replies, (SELECT COUNT(*) FROM support_messages sm WHERE sm.ticket_id = st.id AND sm.sender_type = 'admin') as admin_replies, (SELECT sm.created_at FROM support_messages sm WHERE sm.ticket_id = st.id ORDER BY sm.created_at DESC LIMIT 1) as last_activity FROM support_tickets st JOIN users u ON st.user_id = u.id LEFT JOIN admin_users au ON st.assigned_to = au.id WHERE {$where} ORDER BY st.updated_at DESC, st.created_at DESC LIMIT ? OFFSET ? "); $params[] = $limit; $params[] = $offset; $stmt->execute($params); return $stmt->fetchAll(); } catch (Exception $e) { logError('Error fetching admin tickets', ['error' => $e->getMessage()]); return []; } } public function getTicketById($ticketId) { try { $stmt = $this->pdo->prepare(" SELECT st.*, u.email as user_email, au.full_name as assigned_to_name FROM support_tickets st JOIN users u ON st.user_id = u.id LEFT JOIN admin_users au ON st.assigned_to = au.id WHERE st.id = ? "); $stmt->execute([$ticketId]); return $stmt->fetch(); } catch (Exception $e) { logError('Error fetching ticket by ID', ['ticket_id' => $ticketId, 'error' => $e->getMessage()]); return null; } } public function getTicketMessages($ticketId, $includeInternal = true) { try { $where = $includeInternal ? "1=1" : "sm.is_internal = 0"; $stmt = $this->pdo->prepare(" SELECT sm.*, CASE WHEN sm.sender_type = 'user' THEN u.email WHEN sm.sender_type = 'admin' THEN au.full_name END as sender_name FROM support_messages sm LEFT JOIN users u ON sm.sender_type = 'user' AND sm.sender_id = u.id LEFT JOIN admin_users au ON sm.sender_type = 'admin' AND sm.sender_id = au.id WHERE sm.ticket_id = ? AND {$where} ORDER BY sm.created_at ASC "); $stmt->execute([$ticketId]); return $stmt->fetchAll(); } catch (Exception $e) { logError('Error fetching ticket messages', ['ticket_id' => $ticketId, 'error' => $e->getMessage()]); return []; } } public function updateTicketStatus($ticketId, $status, $adminId) { try { $stmt = $this->pdo->prepare(" UPDATE support_tickets SET status = ?, resolved_at = CASE WHEN ? = 'resolved' COLLATE utf8mb4_unicode_ci THEN NOW() ELSE resolved_at END WHERE id = ? "); $result = $stmt->execute([$status, $status, $ticketId]); if ($result) { logError('Ticket status updated', [ 'ticket_id' => $ticketId, 'status' => $status, 'admin_id' => $adminId ]); } return $result; } catch (Exception $e) { logError('Error updating ticket status', ['ticket_id' => $ticketId, 'error' => $e->getMessage()]); return false; } } public function assignTicket($ticketId, $adminId, $assignedBy) { try { $stmt = $this->pdo->prepare("UPDATE support_tickets SET assigned_to = ? WHERE id = ?"); $result = $stmt->execute([$adminId, $ticketId]); if ($result) { logError('Ticket assigned', [ 'ticket_id' => $ticketId, 'assigned_to' => $adminId, 'assigned_by' => $assignedBy ]); } return $result; } catch (Exception $e) { logError('Error assigning ticket', ['ticket_id' => $ticketId, 'error' => $e->getMessage()]); return false; } } public function addReply($ticketId, $adminId, $message, $isInternal = false) { try { $this->pdo->beginTransaction(); // Insert message $stmt = $this->pdo->prepare(" INSERT INTO support_messages (ticket_id, sender_type, sender_id, message, is_internal) VALUES (?, 'admin', ?, ?, ?) "); $stmt->execute([$ticketId, $adminId, $message, $isInternal ? 1 : 0]); // Update ticket status to pending if not internal if (!$isInternal) { $stmt = $this->pdo->prepare("UPDATE support_tickets SET status = 'pending' WHERE id = ?"); $stmt->execute([$ticketId]); } $this->pdo->commit(); logError('Admin reply added', [ 'ticket_id' => $ticketId, 'admin_id' => $adminId, 'is_internal' => $isInternal ]); return true; } catch (Exception $e) { $this->pdo->rollBack(); logError('Error adding admin reply', ['ticket_id' => $ticketId, 'error' => $e->getMessage()]); return false; } } public function getAdminUsers() { try { $stmt = $this->pdo->prepare("SELECT id, username, full_name, role, status FROM admin_users WHERE status = 'active' ORDER BY full_name"); $stmt->execute(); return $stmt->fetchAll(); } catch (Exception $e) { logError('Error fetching admin users', ['error' => $e->getMessage()]); return []; } } public function getTicketStats() { try { $stmt = $this->pdo->prepare(" SELECT COUNT(*) as total, SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN status = 'resolved' THEN 1 ELSE 0 END) as resolved, SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed, SUM(CASE WHEN priority = 'urgent' THEN 1 ELSE 0 END) as urgent, SUM(CASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) THEN 1 ELSE 0 END) as today FROM support_tickets "); $stmt->execute(); return $stmt->fetch(); } catch (Exception $e) { logError('Error fetching ticket stats', ['error' => $e->getMessage()]); return []; } } } // Helper function for JSON responses function adminJsonResponse($success, $message, $data = null) { header('Content-Type: application/json'); echo json_encode([ 'success' => $success, 'message' => $message, 'data' => $data ]); exit; } ?>