2026-01-01 15:40:32 -05:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* AuditLogModel - Handles audit trail logging for all user actions
|
|
|
|
|
*/
|
|
|
|
|
class AuditLogModel {
|
|
|
|
|
private $conn;
|
|
|
|
|
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
/** @var int Maximum allowed limit for pagination */
|
|
|
|
|
private const MAX_LIMIT = 1000;
|
|
|
|
|
|
|
|
|
|
/** @var int Default limit for pagination */
|
|
|
|
|
private const DEFAULT_LIMIT = 100;
|
|
|
|
|
|
|
|
|
|
/** @var array Allowed action types for filtering */
|
|
|
|
|
private const VALID_ACTION_TYPES = [
|
|
|
|
|
'create', 'update', 'delete', 'view', 'security_event',
|
|
|
|
|
'login', 'logout', 'assign', 'comment', 'bulk_update'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/** @var array Allowed entity types for filtering */
|
|
|
|
|
private const VALID_ENTITY_TYPES = [
|
|
|
|
|
'ticket', 'comment', 'user', 'api_key', 'security',
|
|
|
|
|
'template', 'attachment', 'group'
|
|
|
|
|
];
|
|
|
|
|
|
2026-01-01 15:40:32 -05:00
|
|
|
public function __construct($conn) {
|
|
|
|
|
$this->conn = $conn;
|
|
|
|
|
}
|
|
|
|
|
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
/**
|
|
|
|
|
* Validate and sanitize pagination limit
|
|
|
|
|
*
|
|
|
|
|
* @param int $limit Requested limit
|
|
|
|
|
* @return int Validated limit
|
|
|
|
|
*/
|
|
|
|
|
private function validateLimit(int $limit): int {
|
|
|
|
|
if ($limit < 1) {
|
|
|
|
|
return self::DEFAULT_LIMIT;
|
|
|
|
|
}
|
|
|
|
|
return min($limit, self::MAX_LIMIT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate and sanitize pagination offset
|
|
|
|
|
*
|
|
|
|
|
* @param int $offset Requested offset
|
|
|
|
|
* @return int Validated offset (non-negative)
|
|
|
|
|
*/
|
|
|
|
|
private function validateOffset(int $offset): int {
|
|
|
|
|
return max(0, $offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate date format (YYYY-MM-DD)
|
|
|
|
|
*
|
|
|
|
|
* @param string $date Date string
|
|
|
|
|
* @return string|null Validated date or null if invalid
|
|
|
|
|
*/
|
|
|
|
|
private function validateDate(string $date): ?string {
|
|
|
|
|
// Check format
|
|
|
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify it's a valid date
|
|
|
|
|
$parts = explode('-', $date);
|
|
|
|
|
if (!checkdate((int)$parts[1], (int)$parts[2], (int)$parts[0])) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $date;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate action type
|
|
|
|
|
*
|
|
|
|
|
* @param string $actionType Action type to validate
|
|
|
|
|
* @return bool True if valid
|
|
|
|
|
*/
|
|
|
|
|
private function isValidActionType(string $actionType): bool {
|
|
|
|
|
return in_array($actionType, self::VALID_ACTION_TYPES, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate entity type
|
|
|
|
|
*
|
|
|
|
|
* @param string $entityType Entity type to validate
|
|
|
|
|
* @return bool True if valid
|
|
|
|
|
*/
|
|
|
|
|
private function isValidEntityType(string $entityType): bool {
|
|
|
|
|
return in_array($entityType, self::VALID_ENTITY_TYPES, true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 15:40:32 -05:00
|
|
|
/**
|
|
|
|
|
* 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) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$limit = $this->validateLimit((int)$limit);
|
|
|
|
|
|
2026-01-01 15:40:32 -05:00
|
|
|
$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) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$limit = $this->validateLimit((int)$limit);
|
|
|
|
|
$userId = max(0, (int)$userId);
|
|
|
|
|
|
2026-01-01 15:40:32 -05:00
|
|
|
$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) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$limit = $this->validateLimit((int)$limit);
|
|
|
|
|
$offset = $this->validateOffset((int)$offset);
|
|
|
|
|
|
2026-01-01 15:40:32 -05:00
|
|
|
$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) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$limit = $this->validateLimit((int)$limit);
|
|
|
|
|
|
|
|
|
|
// Validate action type to prevent unexpected queries
|
|
|
|
|
if (!$this->isValidActionType($actionType)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 15:40:32 -05:00
|
|
|
$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);
|
|
|
|
|
}
|
2026-01-01 18:25:19 -05:00
|
|
|
|
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
// ========================================
|
|
|
|
|
// Security Event Logging Methods
|
|
|
|
|
// ========================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log a security event
|
|
|
|
|
*
|
|
|
|
|
* @param string $eventType Type of security event
|
|
|
|
|
* @param array $details Additional details
|
|
|
|
|
* @param int|null $userId User ID if known
|
|
|
|
|
* @return bool Success status
|
|
|
|
|
*/
|
|
|
|
|
public function logSecurityEvent($eventType, $details = [], $userId = null) {
|
|
|
|
|
$details['event_type'] = $eventType;
|
|
|
|
|
$details['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
|
|
|
|
|
return $this->log($userId, 'security_event', 'security', null, $details);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log a failed authentication attempt
|
|
|
|
|
*
|
|
|
|
|
* @param string $username Username attempted
|
|
|
|
|
* @param string $reason Reason for failure
|
|
|
|
|
* @return bool Success status
|
|
|
|
|
*/
|
|
|
|
|
public function logFailedAuth($username, $reason = 'Invalid credentials') {
|
|
|
|
|
return $this->logSecurityEvent('failed_auth', [
|
|
|
|
|
'username' => $username,
|
|
|
|
|
'reason' => $reason
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log a CSRF token failure
|
|
|
|
|
*
|
|
|
|
|
* @param string $endpoint The endpoint that was accessed
|
|
|
|
|
* @param int|null $userId User ID if session exists
|
|
|
|
|
* @return bool Success status
|
|
|
|
|
*/
|
|
|
|
|
public function logCsrfFailure($endpoint, $userId = null) {
|
|
|
|
|
return $this->logSecurityEvent('csrf_failure', [
|
|
|
|
|
'endpoint' => $endpoint,
|
|
|
|
|
'method' => $_SERVER['REQUEST_METHOD'] ?? 'Unknown'
|
|
|
|
|
], $userId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log a rate limit exceeded event
|
|
|
|
|
*
|
|
|
|
|
* @param string $endpoint The endpoint that was rate limited
|
|
|
|
|
* @param int|null $userId User ID if session exists
|
|
|
|
|
* @return bool Success status
|
|
|
|
|
*/
|
|
|
|
|
public function logRateLimitExceeded($endpoint, $userId = null) {
|
|
|
|
|
return $this->logSecurityEvent('rate_limit_exceeded', [
|
|
|
|
|
'endpoint' => $endpoint
|
|
|
|
|
], $userId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log an unauthorized access attempt
|
|
|
|
|
*
|
|
|
|
|
* @param string $resource The resource that was accessed
|
|
|
|
|
* @param int|null $userId User ID if session exists
|
|
|
|
|
* @return bool Success status
|
|
|
|
|
*/
|
|
|
|
|
public function logUnauthorizedAccess($resource, $userId = null) {
|
|
|
|
|
return $this->logSecurityEvent('unauthorized_access', [
|
|
|
|
|
'resource' => $resource
|
|
|
|
|
], $userId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get security events (for admin review)
|
|
|
|
|
*
|
|
|
|
|
* @param int $limit Maximum number of events
|
|
|
|
|
* @param int $offset Offset for pagination
|
|
|
|
|
* @return array Security events
|
|
|
|
|
*/
|
|
|
|
|
public function getSecurityEvents($limit = 100, $offset = 0) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$limit = $this->validateLimit((int)$limit);
|
|
|
|
|
$offset = $this->validateOffset((int)$offset);
|
|
|
|
|
|
Implement comprehensive improvement plan (Phases 1-6)
Security (Phase 1-2):
- Add SecurityHeadersMiddleware with CSP, X-Frame-Options, etc.
- Add RateLimitMiddleware for API rate limiting
- Add security event logging to AuditLogModel
- Add ResponseHelper for standardized API responses
- Update config.php with security constants
Database (Phase 3):
- Add migration 014 for additional indexes
- Add migration 015 for ticket dependencies
- Add migration 016 for ticket attachments
- Add migration 017 for recurring tickets
- Add migration 018 for custom fields
Features (Phase 4-5):
- Add ticket dependencies with DependencyModel and API
- Add duplicate detection with check_duplicates API
- Add file attachments with AttachmentModel and upload/download APIs
- Add @mentions with autocomplete and highlighting
- Add quick actions on dashboard rows
Collaboration (Phase 5):
- Add mention extraction in CommentModel
- Add mention autocomplete dropdown in ticket.js
- Add mention highlighting CSS styles
Admin & Export (Phase 6):
- Add StatsModel for dashboard widgets
- Add dashboard stats cards (open, critical, unassigned, etc.)
- Add CSV/JSON export via export_tickets API
- Add rich text editor toolbar in markdown.js
- Add RecurringTicketModel with cron job
- Add CustomFieldModel for per-category fields
- Add admin views: RecurringTickets, CustomFields, Workflow,
Templates, AuditLog, UserActivity
- Add admin APIs: manage_workflows, manage_templates,
manage_recurring, custom_fields, get_users
- Add admin routes in index.php
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 09:55:01 -05:00
|
|
|
$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 = 'security_event'
|
|
|
|
|
ORDER BY al.created_at DESC
|
|
|
|
|
LIMIT ? OFFSET ?"
|
|
|
|
|
);
|
|
|
|
|
$stmt->bind_param("ii", $limit, $offset);
|
|
|
|
|
$stmt->execute();
|
|
|
|
|
$result = $stmt->get_result();
|
|
|
|
|
|
|
|
|
|
$events = [];
|
|
|
|
|
while ($row = $result->fetch_assoc()) {
|
|
|
|
|
if ($row['details']) {
|
|
|
|
|
$row['details'] = json_decode($row['details'], true);
|
|
|
|
|
}
|
|
|
|
|
$events[] = $row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$stmt->close();
|
|
|
|
|
return $events;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 18:25:19 -05:00
|
|
|
/**
|
|
|
|
|
* Get formatted timeline for a specific ticket
|
|
|
|
|
* Includes all ticket updates and comments
|
|
|
|
|
*
|
|
|
|
|
* @param string $ticketId Ticket ID
|
|
|
|
|
* @return array Timeline events
|
|
|
|
|
*/
|
|
|
|
|
public function getTicketTimeline($ticketId) {
|
|
|
|
|
$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 = 'ticket' AND al.entity_id = ?)
|
|
|
|
|
OR (al.entity_type = 'comment' AND JSON_EXTRACT(al.details, '$.ticket_id') = ?)
|
|
|
|
|
ORDER BY al.created_at DESC"
|
|
|
|
|
);
|
|
|
|
|
$stmt->bind_param("ss", $ticketId, $ticketId);
|
|
|
|
|
$stmt->execute();
|
|
|
|
|
$result = $stmt->get_result();
|
|
|
|
|
|
|
|
|
|
$timeline = [];
|
|
|
|
|
while ($row = $result->fetch_assoc()) {
|
|
|
|
|
if ($row['details']) {
|
|
|
|
|
$row['details'] = json_decode($row['details'], true);
|
|
|
|
|
}
|
|
|
|
|
$timeline[] = $row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$stmt->close();
|
|
|
|
|
return $timeline;
|
|
|
|
|
}
|
2026-01-09 11:20:27 -05:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get filtered audit logs with advanced search
|
|
|
|
|
*
|
|
|
|
|
* @param array $filters Associative array of filter criteria
|
|
|
|
|
* @param int $limit Maximum number of logs to return
|
|
|
|
|
* @param int $offset Offset for pagination
|
|
|
|
|
* @return array Array containing logs and total count
|
|
|
|
|
*/
|
|
|
|
|
public function getFilteredLogs($filters = [], $limit = 50, $offset = 0) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
// Validate pagination parameters
|
|
|
|
|
$limit = $this->validateLimit((int)$limit);
|
|
|
|
|
$offset = $this->validateOffset((int)$offset);
|
|
|
|
|
|
2026-01-09 11:20:27 -05:00
|
|
|
$whereConditions = [];
|
|
|
|
|
$params = [];
|
|
|
|
|
$paramTypes = '';
|
|
|
|
|
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
// Action type filter - validate each action type
|
2026-01-09 11:20:27 -05:00
|
|
|
if (!empty($filters['action_type'])) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$actions = array_filter(
|
|
|
|
|
array_map('trim', explode(',', $filters['action_type'])),
|
|
|
|
|
fn($action) => $this->isValidActionType($action)
|
|
|
|
|
);
|
|
|
|
|
if (!empty($actions)) {
|
|
|
|
|
$placeholders = str_repeat('?,', count($actions) - 1) . '?';
|
|
|
|
|
$whereConditions[] = "al.action_type IN ($placeholders)";
|
|
|
|
|
$params = array_merge($params, array_values($actions));
|
|
|
|
|
$paramTypes .= str_repeat('s', count($actions));
|
|
|
|
|
}
|
2026-01-09 11:20:27 -05:00
|
|
|
}
|
|
|
|
|
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
// Entity type filter - validate each entity type
|
2026-01-09 11:20:27 -05:00
|
|
|
if (!empty($filters['entity_type'])) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$entities = array_filter(
|
|
|
|
|
array_map('trim', explode(',', $filters['entity_type'])),
|
|
|
|
|
fn($entity) => $this->isValidEntityType($entity)
|
|
|
|
|
);
|
|
|
|
|
if (!empty($entities)) {
|
|
|
|
|
$placeholders = str_repeat('?,', count($entities) - 1) . '?';
|
|
|
|
|
$whereConditions[] = "al.entity_type IN ($placeholders)";
|
|
|
|
|
$params = array_merge($params, array_values($entities));
|
|
|
|
|
$paramTypes .= str_repeat('s', count($entities));
|
|
|
|
|
}
|
2026-01-09 11:20:27 -05:00
|
|
|
}
|
|
|
|
|
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
// User filter - validate as positive integer
|
2026-01-09 11:20:27 -05:00
|
|
|
if (!empty($filters['user_id'])) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$userId = (int)$filters['user_id'];
|
|
|
|
|
if ($userId > 0) {
|
|
|
|
|
$whereConditions[] = "al.user_id = ?";
|
|
|
|
|
$params[] = $userId;
|
|
|
|
|
$paramTypes .= 'i';
|
|
|
|
|
}
|
2026-01-09 11:20:27 -05:00
|
|
|
}
|
|
|
|
|
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
// Entity ID filter - sanitize (alphanumeric and dashes only)
|
2026-01-09 11:20:27 -05:00
|
|
|
if (!empty($filters['entity_id'])) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$entityId = preg_replace('/[^a-zA-Z0-9_-]/', '', $filters['entity_id']);
|
|
|
|
|
if (!empty($entityId)) {
|
|
|
|
|
$whereConditions[] = "al.entity_id = ?";
|
|
|
|
|
$params[] = $entityId;
|
|
|
|
|
$paramTypes .= 's';
|
|
|
|
|
}
|
2026-01-09 11:20:27 -05:00
|
|
|
}
|
|
|
|
|
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
// Date range filters - validate format
|
2026-01-09 11:20:27 -05:00
|
|
|
if (!empty($filters['date_from'])) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$dateFrom = $this->validateDate($filters['date_from']);
|
|
|
|
|
if ($dateFrom !== null) {
|
|
|
|
|
$whereConditions[] = "DATE(al.created_at) >= ?";
|
|
|
|
|
$params[] = $dateFrom;
|
|
|
|
|
$paramTypes .= 's';
|
|
|
|
|
}
|
2026-01-09 11:20:27 -05:00
|
|
|
}
|
|
|
|
|
if (!empty($filters['date_to'])) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
$dateTo = $this->validateDate($filters['date_to']);
|
|
|
|
|
if ($dateTo !== null) {
|
|
|
|
|
$whereConditions[] = "DATE(al.created_at) <= ?";
|
|
|
|
|
$params[] = $dateTo;
|
|
|
|
|
$paramTypes .= 's';
|
|
|
|
|
}
|
2026-01-09 11:20:27 -05:00
|
|
|
}
|
|
|
|
|
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
// IP address filter - validate format (basic IP pattern)
|
2026-01-09 11:20:27 -05:00
|
|
|
if (!empty($filters['ip_address'])) {
|
Add security logging, domain validation, and output helpers
- Add authentication failure logging to AuthMiddleware (session expiry,
access denied, unauthenticated access attempts)
- Add UrlHelper for secure URL generation with host validation against
configurable ALLOWED_HOSTS whitelist
- Add OutputHelper with consistent XSS-safe escaping functions (h, attr,
json, url, css, truncate, date, cssClass)
- Add validation to AuditLogModel query parameters (pagination limits,
date format validation, action/entity type validation, IP sanitization)
- Add APP_DOMAIN and ALLOWED_HOSTS configuration options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:51:16 -05:00
|
|
|
// Allow partial IP matching but sanitize input
|
|
|
|
|
$ipAddress = preg_replace('/[^0-9.:a-fA-F]/', '', $filters['ip_address']);
|
|
|
|
|
if (!empty($ipAddress) && strlen($ipAddress) <= 45) { // Max IPv6 length
|
|
|
|
|
$whereConditions[] = "al.ip_address LIKE ?";
|
|
|
|
|
$params[] = '%' . $ipAddress . '%';
|
|
|
|
|
$paramTypes .= 's';
|
|
|
|
|
}
|
2026-01-09 11:20:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build WHERE clause
|
|
|
|
|
$whereClause = '';
|
|
|
|
|
if (!empty($whereConditions)) {
|
|
|
|
|
$whereClause = 'WHERE ' . implode(' AND ', $whereConditions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get total count for pagination
|
|
|
|
|
$countSql = "SELECT COUNT(*) as total FROM audit_log al $whereClause";
|
|
|
|
|
$countStmt = $this->conn->prepare($countSql);
|
|
|
|
|
if (!empty($params)) {
|
|
|
|
|
$countStmt->bind_param($paramTypes, ...$params);
|
|
|
|
|
}
|
|
|
|
|
$countStmt->execute();
|
|
|
|
|
$totalResult = $countStmt->get_result();
|
|
|
|
|
$totalCount = $totalResult->fetch_assoc()['total'];
|
|
|
|
|
$countStmt->close();
|
|
|
|
|
|
|
|
|
|
// Get filtered logs
|
|
|
|
|
$sql = "SELECT al.*, u.username, u.display_name
|
|
|
|
|
FROM audit_log al
|
|
|
|
|
LEFT JOIN users u ON al.user_id = u.user_id
|
|
|
|
|
$whereClause
|
|
|
|
|
ORDER BY al.created_at DESC
|
|
|
|
|
LIMIT ? OFFSET ?";
|
|
|
|
|
$stmt = $this->conn->prepare($sql);
|
|
|
|
|
|
|
|
|
|
// Add limit and offset parameters
|
|
|
|
|
$params[] = $limit;
|
|
|
|
|
$params[] = $offset;
|
|
|
|
|
$paramTypes .= 'ii';
|
|
|
|
|
|
|
|
|
|
if (!empty($params)) {
|
|
|
|
|
$stmt->bind_param($paramTypes, ...$params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$stmt->execute();
|
|
|
|
|
$result = $stmt->get_result();
|
|
|
|
|
|
|
|
|
|
$logs = [];
|
|
|
|
|
while ($row = $result->fetch_assoc()) {
|
|
|
|
|
if ($row['details']) {
|
|
|
|
|
$row['details'] = json_decode($row['details'], true);
|
|
|
|
|
}
|
|
|
|
|
$logs[] = $row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$stmt->close();
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'logs' => $logs,
|
|
|
|
|
'total' => $totalCount,
|
|
|
|
|
'pages' => ceil($totalCount / $limit)
|
|
|
|
|
];
|
|
|
|
|
}
|
2026-01-01 15:40:32 -05:00
|
|
|
}
|