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
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Upload Attachment API
|
|
|
|
|
*
|
|
|
|
|
* Handles file uploads for ticket attachments
|
|
|
|
|
*/
|
|
|
|
|
|
2026-01-20 16:53:00 -05:00
|
|
|
// Capture errors for debugging
|
|
|
|
|
ini_set('display_errors', 0);
|
|
|
|
|
error_reporting(E_ALL);
|
|
|
|
|
|
|
|
|
|
// Apply rate limiting (also starts session)
|
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
|
|
|
require_once dirname(__DIR__) . '/middleware/RateLimitMiddleware.php';
|
|
|
|
|
RateLimitMiddleware::apply('api');
|
|
|
|
|
|
2026-01-20 16:53:00 -05:00
|
|
|
// Ensure session is started
|
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
|
|
|
session_start();
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
require_once dirname(__DIR__) . '/config/config.php';
|
|
|
|
|
require_once dirname(__DIR__) . '/helpers/ResponseHelper.php';
|
|
|
|
|
require_once dirname(__DIR__) . '/models/AttachmentModel.php';
|
|
|
|
|
require_once dirname(__DIR__) . '/models/AuditLogModel.php';
|
|
|
|
|
require_once dirname(__DIR__) . '/middleware/CsrfMiddleware.php';
|
|
|
|
|
|
|
|
|
|
header('Content-Type: application/json');
|
|
|
|
|
|
|
|
|
|
// Check authentication
|
|
|
|
|
if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) {
|
|
|
|
|
ResponseHelper::unauthorized();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle GET requests to list attachments
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
|
|
|
$ticketId = $_GET['ticket_id'] ?? '';
|
|
|
|
|
|
|
|
|
|
if (empty($ticketId)) {
|
|
|
|
|
ResponseHelper::error('Ticket ID is required');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 15:16:14 -05:00
|
|
|
// Validate ticket ID format (9-digit number)
|
|
|
|
|
if (!preg_match('/^\d{9}$/', $ticketId)) {
|
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
|
|
|
ResponseHelper::error('Invalid ticket ID format');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$attachmentModel = new AttachmentModel();
|
|
|
|
|
$attachments = $attachmentModel->getAttachments($ticketId);
|
|
|
|
|
|
|
|
|
|
// Add formatted file size and icon to each attachment
|
|
|
|
|
foreach ($attachments as &$att) {
|
|
|
|
|
$att['file_size_formatted'] = AttachmentModel::formatFileSize($att['file_size']);
|
|
|
|
|
$att['icon'] = AttachmentModel::getFileIcon($att['mime_type']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ResponseHelper::success(['attachments' => $attachments]);
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
ResponseHelper::serverError('Failed to load attachments');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only accept POST requests for uploads
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
|
|
|
ResponseHelper::error('Method not allowed', 405);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify CSRF token
|
|
|
|
|
$csrfToken = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
|
|
|
|
if (!CsrfMiddleware::validateToken($csrfToken)) {
|
|
|
|
|
ResponseHelper::forbidden('Invalid CSRF token');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get ticket ID
|
|
|
|
|
$ticketId = $_POST['ticket_id'] ?? '';
|
|
|
|
|
if (empty($ticketId)) {
|
|
|
|
|
ResponseHelper::error('Ticket ID is required');
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 15:16:14 -05:00
|
|
|
// Validate ticket ID format (9-digit number)
|
|
|
|
|
if (!preg_match('/^\d{9}$/', $ticketId)) {
|
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
|
|
|
ResponseHelper::error('Invalid ticket ID format');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if file was uploaded
|
|
|
|
|
if (!isset($_FILES['file']) || $_FILES['file']['error'] === UPLOAD_ERR_NO_FILE) {
|
|
|
|
|
ResponseHelper::error('No file uploaded');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$file = $_FILES['file'];
|
|
|
|
|
|
|
|
|
|
// Check for upload errors
|
|
|
|
|
if ($file['error'] !== UPLOAD_ERR_OK) {
|
|
|
|
|
$errorMessages = [
|
|
|
|
|
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize directive',
|
|
|
|
|
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE directive',
|
|
|
|
|
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
|
|
|
|
|
UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
|
|
|
|
|
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
|
|
|
|
|
UPLOAD_ERR_EXTENSION => 'File upload stopped by extension'
|
|
|
|
|
];
|
|
|
|
|
$message = $errorMessages[$file['error']] ?? 'Unknown upload error';
|
|
|
|
|
ResponseHelper::error($message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check file size
|
|
|
|
|
$maxSize = $GLOBALS['config']['MAX_UPLOAD_SIZE'] ?? 10485760; // 10MB default
|
|
|
|
|
if ($file['size'] > $maxSize) {
|
|
|
|
|
ResponseHelper::error('File size exceeds maximum allowed (' . AttachmentModel::formatFileSize($maxSize) . ')');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get MIME type
|
|
|
|
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
|
|
|
|
$mimeType = $finfo->file($file['tmp_name']);
|
|
|
|
|
|
|
|
|
|
// Validate file type
|
|
|
|
|
if (!AttachmentModel::isAllowedType($mimeType)) {
|
|
|
|
|
ResponseHelper::error('File type not allowed: ' . $mimeType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create upload directory if it doesn't exist
|
|
|
|
|
$uploadDir = $GLOBALS['config']['UPLOAD_DIR'] ?? dirname(__DIR__) . '/uploads';
|
|
|
|
|
if (!is_dir($uploadDir)) {
|
|
|
|
|
if (!mkdir($uploadDir, 0755, true)) {
|
|
|
|
|
ResponseHelper::serverError('Failed to create upload directory');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create ticket subdirectory
|
|
|
|
|
$ticketDir = $uploadDir . '/' . $ticketId;
|
|
|
|
|
if (!is_dir($ticketDir)) {
|
|
|
|
|
if (!mkdir($ticketDir, 0755, true)) {
|
|
|
|
|
ResponseHelper::serverError('Failed to create ticket upload directory');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate unique filename
|
|
|
|
|
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
|
|
|
|
$safeExtension = preg_replace('/[^a-zA-Z0-9]/', '', $extension);
|
|
|
|
|
$uniqueFilename = uniqid('att_', true) . ($safeExtension ? '.' . $safeExtension : '');
|
|
|
|
|
$targetPath = $ticketDir . '/' . $uniqueFilename;
|
|
|
|
|
|
|
|
|
|
// Move uploaded file
|
|
|
|
|
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
|
|
|
|
|
ResponseHelper::serverError('Failed to move uploaded file');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sanitize original filename
|
|
|
|
|
$originalFilename = basename($file['name']);
|
|
|
|
|
$originalFilename = preg_replace('/[^\w\s\-\.]/', '', $originalFilename);
|
|
|
|
|
if (empty($originalFilename)) {
|
|
|
|
|
$originalFilename = 'attachment' . ($safeExtension ? '.' . $safeExtension : '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save to database
|
|
|
|
|
try {
|
|
|
|
|
$attachmentModel = new AttachmentModel();
|
|
|
|
|
$attachmentId = $attachmentModel->addAttachment(
|
|
|
|
|
$ticketId,
|
|
|
|
|
$uniqueFilename,
|
|
|
|
|
$originalFilename,
|
|
|
|
|
$file['size'],
|
|
|
|
|
$mimeType,
|
|
|
|
|
$_SESSION['user']['user_id']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!$attachmentId) {
|
|
|
|
|
// Clean up file if database insert fails
|
|
|
|
|
unlink($targetPath);
|
|
|
|
|
ResponseHelper::serverError('Failed to save attachment record');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Log the upload
|
|
|
|
|
$auditLog = new AuditLogModel();
|
|
|
|
|
$auditLog->log(
|
|
|
|
|
$_SESSION['user']['user_id'],
|
|
|
|
|
'attachment_upload',
|
|
|
|
|
'ticket_attachments',
|
|
|
|
|
$attachmentId,
|
|
|
|
|
null,
|
|
|
|
|
json_encode([
|
|
|
|
|
'ticket_id' => $ticketId,
|
|
|
|
|
'filename' => $originalFilename,
|
|
|
|
|
'size' => $file['size'],
|
|
|
|
|
'mime_type' => $mimeType
|
|
|
|
|
])
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ResponseHelper::created([
|
|
|
|
|
'attachment_id' => $attachmentId,
|
|
|
|
|
'filename' => $originalFilename,
|
|
|
|
|
'file_size' => $file['size'],
|
|
|
|
|
'file_size_formatted' => AttachmentModel::formatFileSize($file['size']),
|
|
|
|
|
'mime_type' => $mimeType,
|
|
|
|
|
'icon' => AttachmentModel::getFileIcon($mimeType),
|
|
|
|
|
'uploaded_by' => $_SESSION['user']['display_name'] ?? $_SESSION['user']['username'],
|
|
|
|
|
'uploaded_at' => date('Y-m-d H:i:s')
|
|
|
|
|
], 'File uploaded successfully');
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
// Clean up file on error
|
|
|
|
|
if (file_exists($targetPath)) {
|
|
|
|
|
unlink($targetPath);
|
|
|
|
|
}
|
|
|
|
|
ResponseHelper::serverError('Failed to process attachment');
|
|
|
|
|
}
|