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 = []; session_destroy(); header('Location: support.php'); exit; } public function isLoggedIn() { session_start(); if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) { return true; } // Check remember me 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 au.id, au.username, au.full_name, au.role FROM admin_sessions ass JOIN admin_users au ON ass.admin_id = au.id WHERE ass.session_token = ? AND ass.expires_at > NOW() AND au.status = 'active' "); $stmt->execute([$token]); $session = $stmt->fetch(); if ($session) { // Restore session $_SESSION['admin_logged_in'] = true; $_SESSION['admin_id'] = $session['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['id']]); return true; } else { // Invalid or expired token - delete it 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; } } // SUPPORT TICKET METHODS 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_admin_name, (SELECT COUNT(*) FROM support_messages sm WHERE sm.ticket_id = st.id) as message_count FROM support_tickets st LEFT 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.created_at DESC LIMIT $limit OFFSET $offset "); $stmt->execute($params); return $stmt->fetchAll(); } catch (Exception $e) { logError('Error fetching tickets', ['error' => $e->getMessage()]); return []; } } public function getTicketStats() { try { $stmt = $this->pdo->query(" 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 FROM support_tickets "); return $stmt->fetch(); } catch (Exception $e) { logError('Error fetching ticket stats', ['error' => $e->getMessage()]); return ['total' => 0, 'open' => 0, 'pending' => 0, 'resolved' => 0, 'closed' => 0]; } } public function getTicketById($ticketId) { try { $stmt = $this->pdo->prepare(" SELECT st.*, u.email as user_email, u.gender, u.date_of_birth, u.postcode, au.full_name as assigned_admin_name FROM support_tickets st LEFT 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', ['ticket_id' => $ticketId, 'error' => $e->getMessage()]); return null; } } public function getTicketMessages($ticketId, $includeInternal = false) { try { $where = $includeInternal ? "1=1" : "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) { try { $stmt = $this->pdo->prepare("UPDATE support_tickets SET assigned_to = ?, updated_at = NOW() WHERE id = ?"); return $stmt->execute([$adminId, $ticketId]); } catch (Exception $e) { logError('Error assigning ticket', ['ticket_id' => $ticketId, 'admin_id' => $adminId, 'error' => $e->getMessage()]); return false; } } public function addTicketReply($ticketId, $message, $adminId, $isInternal = false) { try { $stmt = $this->pdo->prepare(" INSERT INTO support_messages (ticket_id, sender_type, sender_id, message, is_internal) VALUES (?, 'admin', ?, ?, ?) "); $result = $stmt->execute([$ticketId, $adminId, $message, $isInternal]); if ($result) { // Update ticket timestamp $stmt = $this->pdo->prepare("UPDATE support_tickets SET updated_at = NOW() WHERE id = ?"); $stmt->execute([$ticketId]); } return $result; } catch (Exception $e) { logError('Error adding ticket reply', ['ticket_id' => $ticketId, 'admin_id' => $adminId, 'error' => $e->getMessage()]); return false; } } public function getAdminUsers() { try { $stmt = $this->pdo->prepare("SELECT id, username, full_name, role 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 []; } } // REDEMPTION MANAGEMENT METHODS public function getAllRedemptions($status = null, $dateFilter = null, $limit = 50, $offset = 0) { try { $where = "1=1"; $params = []; if ($status && $status !== 'all') { $where .= " AND rr.status = ?"; $params[] = $status; } if ($dateFilter) { switch ($dateFilter) { case 'today': $where .= " AND DATE(rr.created_at) = CURDATE()"; break; case 'yesterday': $where .= " AND DATE(rr.created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)"; break; case 'this_week': $where .= " AND rr.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)"; break; case 'this_month': $where .= " AND rr.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)"; break; } } $stmt = $this->pdo->prepare(" SELECT rr.*, u.email as user_email, au.full_name as processed_by_name, (SELECT COUNT(*) FROM redemption_requests WHERE user_id = rr.user_id) as user_total_redemptions FROM redemption_requests rr LEFT JOIN users u ON rr.user_id = u.id LEFT JOIN admin_users au ON rr.processed_by = au.id WHERE $where ORDER BY rr.created_at DESC LIMIT $limit OFFSET $offset "); $stmt->execute($params); return $stmt->fetchAll(); } catch (Exception $e) { logError('Error fetching redemptions', ['error' => $e->getMessage()]); return []; } } public function getRedemptionStats() { try { $stmt = $this->pdo->query(" SELECT COUNT(*) as total, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN status = 'processing' THEN 1 ELSE 0 END) as processing, SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed, SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed, SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled, SUM(CASE WHEN status = 'pending' THEN amount_inr ELSE 0 END) as pending_amount, SUM(CASE WHEN status IN ('processing', 'completed') THEN amount_inr ELSE 0 END) as processed_amount FROM redemption_requests "); return $stmt->fetch(); } catch (Exception $e) { logError('Error fetching redemption stats', ['error' => $e->getMessage()]); return [ 'total' => 0, 'pending' => 0, 'processing' => 0, 'completed' => 0, 'failed' => 0, 'cancelled' => 0, 'pending_amount' => 0, 'processed_amount' => 0 ]; } } public function updateRedemptionStatus($redemptionId, $status, $adminNotes, $adminId) { try { $this->pdo->beginTransaction(); // Get redemption details $stmt = $this->pdo->prepare("SELECT * FROM redemption_requests WHERE id = ?"); $stmt->execute([$redemptionId]); $redemption = $stmt->fetch(); if (!$redemption) { throw new Exception('Redemption request not found'); } // Update redemption status $stmt = $this->pdo->prepare(" UPDATE redemption_requests SET status = ?, admin_notes = ?, processed_by = ?, processed_at = NOW(), updated_at = NOW() WHERE id = ? "); $stmt->execute([$status, $adminNotes, $adminId, $redemptionId]); // Update point transaction status $stmt = $this->pdo->prepare(" UPDATE point_transactions SET status = ? WHERE reference_id = ? AND transaction_type = 'redeemed' "); $stmt->execute([$status, $redemption['request_id']]); // If status is failed or cancelled, refund the points if (in_array($status, ['failed', 'cancelled'])) { $stmt = $this->pdo->prepare(" UPDATE user_points SET points = points + ?, total_redeemed = total_redeemed - ? WHERE user_id = ? "); $stmt->execute([$redemption['points_redeemed'], $redemption['points_redeemed'], $redemption['user_id']]); // Add refund transaction $refundDescription = "Refund for {$status} redemption request - " . $redemption['request_id']; $stmt = $this->pdo->prepare(" INSERT INTO point_transactions (user_id, transaction_type, points, source, description, reference_id) VALUES (?, 'earned', ?, 'refund', ?, ?) "); $stmt->execute([$redemption['user_id'], $redemption['points_redeemed'], $refundDescription, $redemption['request_id']]); } $this->pdo->commit(); logError('Redemption status updated', [ 'redemption_id' => $redemptionId, 'request_id' => $redemption['request_id'], 'old_status' => $redemption['status'], 'new_status' => $status, 'admin_id' => $adminId, 'admin_notes' => $adminNotes ]); return true; } catch (Exception $e) { $this->pdo->rollback(); logError('Error updating redemption status', [ 'redemption_id' => $redemptionId, 'error' => $e->getMessage() ]); return false; } } public function getRedemptionById($redemptionId) { try { $stmt = $this->pdo->prepare(" SELECT rr.*, u.email as user_email, u.gender, u.date_of_birth, u.postcode, au.full_name as processed_by_name, up.points as user_current_points, up.total_earned, up.total_redeemed FROM redemption_requests rr LEFT JOIN users u ON rr.user_id = u.id LEFT JOIN admin_users au ON rr.processed_by = au.id LEFT JOIN user_points up ON rr.user_id = up.user_id WHERE rr.id = ? "); $stmt->execute([$redemptionId]); return $stmt->fetch(); } catch (Exception $e) { logError('Error fetching redemption', ['redemption_id' => $redemptionId, 'error' => $e->getMessage()]); return null; } } public function getRecentRedemptions($limit = 5) { try { $stmt = $this->pdo->prepare(" SELECT rr.request_id, rr.amount_inr, rr.status, rr.created_at, u.email as user_email FROM redemption_requests rr LEFT JOIN users u ON rr.user_id = u.id ORDER BY rr.created_at DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } catch (Exception $e) { logError('Error fetching recent redemptions', ['error' => $e->getMessage()]); return []; } } } // Utility functions function adminJsonResponse($success, $message, $data = null) { header('Content-Type: application/json'); echo json_encode([ 'success' => $success, 'message' => $message, 'data' => $data ]); exit; } ?>