conn = new mysqli( $GLOBALS['config']['DB_HOST'], $GLOBALS['config']['DB_USER'], $GLOBALS['config']['DB_PASS'], $GLOBALS['config']['DB_NAME'] ); if ($this->conn->connect_error) { throw new Exception('Database connection failed: ' . $this->conn->connect_error); } } /** * Get all attachments for a ticket */ public function getAttachments($ticketId) { $sql = "SELECT a.*, u.username, u.display_name FROM ticket_attachments a LEFT JOIN users u ON a.uploaded_by = u.user_id WHERE a.ticket_id = ? ORDER BY a.uploaded_at DESC"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("s", $ticketId); $stmt->execute(); $result = $stmt->get_result(); $attachments = []; while ($row = $result->fetch_assoc()) { $attachments[] = $row; } $stmt->close(); return $attachments; } /** * Get a single attachment by ID */ public function getAttachment($attachmentId) { $sql = "SELECT a.*, u.username, u.display_name FROM ticket_attachments a LEFT JOIN users u ON a.uploaded_by = u.user_id WHERE a.attachment_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $attachmentId); $stmt->execute(); $result = $stmt->get_result(); $attachment = $result->fetch_assoc(); $stmt->close(); return $attachment; } /** * Add a new attachment record */ public function addAttachment($ticketId, $filename, $originalFilename, $fileSize, $mimeType, $uploadedBy) { $sql = "INSERT INTO ticket_attachments (ticket_id, filename, original_filename, file_size, mime_type, uploaded_by) VALUES (?, ?, ?, ?, ?, ?)"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("sssisi", $ticketId, $filename, $originalFilename, $fileSize, $mimeType, $uploadedBy); $result = $stmt->execute(); if ($result) { $attachmentId = $this->conn->insert_id; $stmt->close(); return $attachmentId; } $stmt->close(); return false; } /** * Delete an attachment record */ public function deleteAttachment($attachmentId) { $sql = "DELETE FROM ticket_attachments WHERE attachment_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $attachmentId); $result = $stmt->execute(); $stmt->close(); return $result; } /** * Get total attachment size for a ticket */ public function getTotalSizeForTicket($ticketId) { $sql = "SELECT COALESCE(SUM(file_size), 0) as total_size FROM ticket_attachments WHERE ticket_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("s", $ticketId); $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); $stmt->close(); return (int)$row['total_size']; } /** * Get attachment count for a ticket */ public function getAttachmentCount($ticketId) { $sql = "SELECT COUNT(*) as count FROM ticket_attachments WHERE ticket_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("s", $ticketId); $stmt->execute(); $result = $stmt->get_result(); $row = $result->fetch_assoc(); $stmt->close(); return (int)$row['count']; } /** * Check if user can delete attachment (owner or admin) */ public function canUserDelete($attachmentId, $userId, $isAdmin = false) { if ($isAdmin) { return true; } $attachment = $this->getAttachment($attachmentId); return $attachment && $attachment['uploaded_by'] == $userId; } /** * Format file size for display */ public static function formatFileSize($bytes) { if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 2) . ' GB'; } elseif ($bytes >= 1048576) { return number_format($bytes / 1048576, 2) . ' MB'; } elseif ($bytes >= 1024) { return number_format($bytes / 1024, 2) . ' KB'; } else { return $bytes . ' bytes'; } } /** * Get file icon based on mime type */ public static function getFileIcon($mimeType) { if (strpos($mimeType, 'image/') === 0) { return '🖼️'; } elseif (strpos($mimeType, 'video/') === 0) { return '🎬'; } elseif (strpos($mimeType, 'audio/') === 0) { return '🎵'; } elseif ($mimeType === 'application/pdf') { return '📄'; } elseif (strpos($mimeType, 'text/') === 0) { return '📝'; } elseif (in_array($mimeType, ['application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed', 'application/gzip'])) { return '📦'; } elseif (in_array($mimeType, ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'])) { return '📘'; } elseif (in_array($mimeType, ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'])) { return '📊'; } else { return '📎'; } } /** * Validate file type against allowed types */ public static function isAllowedType($mimeType) { $allowedTypes = $GLOBALS['config']['ALLOWED_FILE_TYPES'] ?? [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf', 'text/plain', 'text/csv', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed', 'application/json', 'application/xml' ]; return in_array($mimeType, $allowedTypes); } public function __destruct() { if ($this->conn) { $this->conn->close(); } } }