conn = $conn; } /** * Log an action to the audit trail * * @param int $userId User ID performing the action * @param string $actionType Type of action (e.g., 'create', 'update', 'delete', 'view') * @param string $entityType Type of entity (e.g., 'ticket', 'comment', 'api_key') * @param string|null $entityId ID of the entity affected * @param array|null $details Additional details as associative array * @param string|null $ipAddress IP address of the user * @return bool Success status */ public function log($userId, $actionType, $entityType, $entityId = null, $details = null, $ipAddress = null) { // Convert details array to JSON $detailsJson = null; if ($details !== null) { $detailsJson = json_encode($details); } // Get IP address if not provided if ($ipAddress === null) { $ipAddress = $this->getClientIP(); } $stmt = $this->conn->prepare( "INSERT INTO audit_log (user_id, action_type, entity_type, entity_id, details, ip_address) VALUES (?, ?, ?, ?, ?, ?)" ); $stmt->bind_param("isssss", $userId, $actionType, $entityType, $entityId, $detailsJson, $ipAddress); $success = $stmt->execute(); $stmt->close(); return $success; } /** * Get audit logs for a specific entity * * @param string $entityType Type of entity * @param string $entityId ID of the entity * @param int $limit Maximum number of logs to return * @return array Array of audit log records */ public function getLogsByEntity($entityType, $entityId, $limit = 100) { $stmt = $this->conn->prepare( "SELECT al.*, u.username, u.display_name FROM audit_log al LEFT JOIN users u ON al.user_id = u.user_id WHERE al.entity_type = ? AND al.entity_id = ? ORDER BY al.created_at DESC LIMIT ?" ); $stmt->bind_param("ssi", $entityType, $entityId, $limit); $stmt->execute(); $result = $stmt->get_result(); $logs = []; while ($row = $result->fetch_assoc()) { // Decode JSON details if ($row['details']) { $row['details'] = json_decode($row['details'], true); } $logs[] = $row; } $stmt->close(); return $logs; } /** * Get audit logs for a specific user * * @param int $userId User ID * @param int $limit Maximum number of logs to return * @return array Array of audit log records */ public function getLogsByUser($userId, $limit = 100) { $stmt = $this->conn->prepare( "SELECT al.*, u.username, u.display_name FROM audit_log al LEFT JOIN users u ON al.user_id = u.user_id WHERE al.user_id = ? ORDER BY al.created_at DESC LIMIT ?" ); $stmt->bind_param("ii", $userId, $limit); $stmt->execute(); $result = $stmt->get_result(); $logs = []; while ($row = $result->fetch_assoc()) { // Decode JSON details if ($row['details']) { $row['details'] = json_decode($row['details'], true); } $logs[] = $row; } $stmt->close(); return $logs; } /** * Get recent audit logs (for admin panel) * * @param int $limit Maximum number of logs to return * @param int $offset Offset for pagination * @return array Array of audit log records */ public function getRecentLogs($limit = 50, $offset = 0) { $stmt = $this->conn->prepare( "SELECT al.*, u.username, u.display_name FROM audit_log al LEFT JOIN users u ON al.user_id = u.user_id ORDER BY al.created_at DESC LIMIT ? OFFSET ?" ); $stmt->bind_param("ii", $limit, $offset); $stmt->execute(); $result = $stmt->get_result(); $logs = []; while ($row = $result->fetch_assoc()) { // Decode JSON details if ($row['details']) { $row['details'] = json_decode($row['details'], true); } $logs[] = $row; } $stmt->close(); return $logs; } /** * Get audit logs filtered by action type * * @param string $actionType Action type to filter by * @param int $limit Maximum number of logs to return * @return array Array of audit log records */ public function getLogsByAction($actionType, $limit = 100) { $stmt = $this->conn->prepare( "SELECT al.*, u.username, u.display_name FROM audit_log al LEFT JOIN users u ON al.user_id = u.user_id WHERE al.action_type = ? ORDER BY al.created_at DESC LIMIT ?" ); $stmt->bind_param("si", $actionType, $limit); $stmt->execute(); $result = $stmt->get_result(); $logs = []; while ($row = $result->fetch_assoc()) { // Decode JSON details if ($row['details']) { $row['details'] = json_decode($row['details'], true); } $logs[] = $row; } $stmt->close(); return $logs; } /** * Get total count of audit logs * * @return int Total count */ public function getTotalCount() { $result = $this->conn->query("SELECT COUNT(*) as count FROM audit_log"); $row = $result->fetch_assoc(); return (int)$row['count']; } /** * Delete old audit logs (for maintenance) * * @param int $daysToKeep Number of days of logs to keep * @return int Number of deleted records */ public function deleteOldLogs($daysToKeep = 90) { $stmt = $this->conn->prepare( "DELETE FROM audit_log WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)" ); $stmt->bind_param("i", $daysToKeep); $stmt->execute(); $affectedRows = $stmt->affected_rows; $stmt->close(); return $affectedRows; } /** * Get client IP address (handles proxies) * * @return string Client IP address */ private function getClientIP() { $ipAddress = ''; // Check for proxy headers if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) { // Cloudflare $ipAddress = $_SERVER['HTTP_CF_CONNECTING_IP']; } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) { // Nginx proxy $ipAddress = $_SERVER['HTTP_X_REAL_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { // Standard proxy header $ipAddress = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]; } elseif (!empty($_SERVER['REMOTE_ADDR'])) { // Direct connection $ipAddress = $_SERVER['REMOTE_ADDR']; } return trim($ipAddress); } /** * Helper: Log ticket creation * * @param int $userId User ID * @param string $ticketId Ticket ID * @param array $ticketData Ticket data * @return bool Success status */ public function logTicketCreate($userId, $ticketId, $ticketData) { return $this->log( $userId, 'create', 'ticket', $ticketId, ['title' => $ticketData['title'], 'priority' => $ticketData['priority'] ?? null] ); } /** * Helper: Log ticket update * * @param int $userId User ID * @param string $ticketId Ticket ID * @param array $changes Array of changed fields * @return bool Success status */ public function logTicketUpdate($userId, $ticketId, $changes) { return $this->log($userId, 'update', 'ticket', $ticketId, $changes); } /** * Helper: Log comment creation * * @param int $userId User ID * @param int $commentId Comment ID * @param string $ticketId Associated ticket ID * @return bool Success status */ public function logCommentCreate($userId, $commentId, $ticketId) { return $this->log( $userId, 'create', 'comment', (string)$commentId, ['ticket_id' => $ticketId] ); } /** * Helper: Log ticket view * * @param int $userId User ID * @param string $ticketId Ticket ID * @return bool Success status */ public function logTicketView($userId, $ticketId) { return $this->log($userId, 'view', 'ticket', $ticketId); } }