c442e2d47f
All ticket_id parameters were bound as integer ("i"), which stripped
leading zeros before insertion into ticket_attachments.ticket_id
(VARCHAR 9). This caused a mismatch: upload_attachment.php creates
the directory using the full string (e.g. /uploads/000123456/) but
the DB stored the integer form ("123456"), so download and delete
would look in the wrong path.
Changed getAttachments, addAttachment, getTotalSizeForTicket, and
getAttachmentCount to use string binding ("s") so the canonical
zero-padded ticket ID is stored and read back consistently.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
197 lines
6.1 KiB
PHP
197 lines
6.1 KiB
PHP
<?php
|
|
/**
|
|
* AttachmentModel - Handles ticket file attachments
|
|
*/
|
|
|
|
class AttachmentModel {
|
|
private $conn;
|
|
|
|
public function __construct($conn) {
|
|
$this->conn = $conn;
|
|
}
|
|
|
|
/**
|
|
* 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 && (int)$attachment['uploaded_by'] === (int)$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);
|
|
}
|
|
|
|
}
|