false, 'error' => 'Authentication required']); exit; } $currentUser = $_SESSION['user']; // Use centralized database connection $conn = Database::getConnection(); // Get filter parameters $status = isset($_GET['status']) ? $_GET['status'] : null; $category = isset($_GET['category']) ? $_GET['category'] : null; $type = isset($_GET['type']) ? $_GET['type'] : null; $search = isset($_GET['search']) ? trim($_GET['search']) : null; $format = isset($_GET['format']) ? $_GET['format'] : 'csv'; $ticketIds = isset($_GET['ticket_ids']) ? $_GET['ticket_ids'] : null; $singleIdRaw = isset($_GET['ticket_id']) ? trim($_GET['ticket_id']) : null; $singleId = ($singleIdRaw !== null && ctype_digit($singleIdRaw) && (int)$singleIdRaw > 0) ? $singleIdRaw : null; // Initialize model $ticketModel = new TicketModel($conn); // Check if specific ticket IDs are provided if ($ticketIds) { // Parse and validate ticket IDs $ticketIdArray = array_filter(array_map('trim', explode(',', $ticketIds))); if (empty($ticketIdArray)) { header('Content-Type: application/json'); http_response_code(400); echo json_encode(['success' => false, 'error' => 'No valid ticket IDs provided']); exit; } // Get specific tickets by IDs $allTickets = $ticketModel->getTicketsByIds($ticketIdArray); // Filter tickets based on visibility - only export tickets the user can access $tickets = []; foreach ($allTickets as $ticket) { if ($ticketModel->canUserAccessTicket($ticket, $currentUser)) { $tickets[] = $ticket; } } } else { // Get all tickets with filters (no pagination for export) // Pass $currentUser so visibility filtering is applied correctly $result = $ticketModel->getAllTickets(1, 10000, $status, 'created_at', 'desc', $category, $type, $search, [], $currentUser); $tickets = $result['tickets']; } if ($format === 'csv') { // CSV Export header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="tickets_export_' . date('Y-m-d_His') . '.csv"'); header('Cache-Control: no-cache, must-revalidate'); header('Pragma: no-cache'); // Create output stream $output = fopen('php://output', 'w'); // Add BOM for Excel UTF-8 compatibility fprintf($output, chr(0xEF) . chr(0xBB) . chr(0xBF)); // CSV Headers $headers = [ 'Ticket ID', 'Title', 'Status', 'Priority', 'Category', 'Type', 'Created By', 'Assigned To', 'Created At', 'Updated At', 'Description' ]; fputcsv($output, $headers); // CSV Data foreach ($tickets as $ticket) { $row = [ $ticket['ticket_id'], $ticket['title'], $ticket['status'], 'P' . $ticket['priority'], $ticket['category'], $ticket['type'], $ticket['creator_display_name'] ?? $ticket['creator_username'] ?? 'System', $ticket['assigned_display_name'] ?? $ticket['assigned_username'] ?? 'Unassigned', $ticket['created_at'], $ticket['updated_at'], $ticket['description'] ]; fputcsv($output, $row); } fclose($output); exit; } elseif ($format === 'json') { // JSON Export header('Content-Type: application/json'); header('Content-Disposition: attachment; filename="tickets_export_' . date('Y-m-d_His') . '.json"'); echo json_encode([ 'exported_at' => date('c'), 'total_tickets' => count($tickets), 'tickets' => array_map(function($t) { return [ 'ticket_id' => $t['ticket_id'], 'title' => $t['title'], 'status' => $t['status'], 'priority' => $t['priority'], 'category' => $t['category'], 'type' => $t['type'], 'description' => $t['description'], 'created_by' => $t['creator_display_name'] ?? $t['creator_username'], 'assigned_to' => $t['assigned_display_name'] ?? $t['assigned_username'], 'created_at' => $t['created_at'], 'updated_at' => $t['updated_at'] ]; }, $tickets) ], JSON_PRETTY_PRINT); exit; } elseif ($format === 'full') { // Full single-ticket export: ticket + all comments + audit timeline if (!$singleId) { header('Content-Type: application/json'); http_response_code(400); echo json_encode(['success' => false, 'error' => 'format=full requires a ticket_id parameter']); exit; } $ticket = $ticketModel->getTicketById($singleId); if (!$ticket || !$ticketModel->canUserAccessTicket($ticket, $currentUser)) { header('Content-Type: application/json'); http_response_code(404); echo json_encode(['success' => false, 'error' => 'Ticket not found']); exit; } $commentModel = new CommentModel($conn); $auditLogModel = new AuditLogModel($conn); // Load flat comment list (no threading nesting in export) $rawComments = $commentModel->getCommentsByTicketId($ticket['ticket_id'], false); $timeline = $auditLogModel->getTicketTimeline((string)$ticket['ticket_id']); $comments = array_map(function($c) { return [ 'comment_id' => $c['comment_id'], 'author' => $c['display_name'] ?? $c['username'] ?? 'Unknown', 'created_at' => $c['created_at'], 'updated_at' => $c['updated_at'] ?? null, 'comment_text' => $c['comment_text'], 'parent_comment_id' => $c['parent_comment_id'] ?? null, ]; }, $rawComments); $timelineOut = array_map(function($row) { $details = $row['details']; if (is_string($details)) { $details = json_decode($details, true) ?? $details; } return [ 'action' => $row['action_type'], 'entity' => $row['entity_type'], 'actor' => $row['display_name'] ?? $row['username'] ?? 'System', 'details' => $details, 'created_at' => $row['created_at'], ]; }, $timeline); header('Content-Type: application/json'); header('Content-Disposition: attachment; filename="ticket_' . $ticket['ticket_id'] . '_full_' . date('Y-m-d_His') . '.json"'); header('Cache-Control: no-cache, must-revalidate'); echo json_encode([ 'exported_at' => date('c'), 'ticket' => [ 'ticket_id' => $ticket['ticket_id'], 'title' => $ticket['title'], 'status' => $ticket['status'], 'priority' => 'P' . $ticket['priority'], 'category' => $ticket['category'], 'type' => $ticket['type'], 'visibility' => $ticket['visibility'] ?? 'public', 'description' => $ticket['description'], 'created_by' => $ticket['creator_display_name'] ?? $ticket['creator_username'] ?? 'System', 'assigned_to' => $ticket['assigned_display_name'] ?? $ticket['assigned_username'] ?? 'Unassigned', 'created_at' => $ticket['created_at'], 'updated_at' => $ticket['updated_at'], 'closed_at' => $ticket['closed_at'] ?? null, ], 'comments' => $comments, 'comment_count' => count($comments), 'timeline' => $timelineOut, ], JSON_PRETTY_PRINT); exit; } else { header('Content-Type: application/json'); http_response_code(400); echo json_encode(['success' => false, 'error' => 'Invalid format. Use csv, json, or full.']); exit; } } catch (Exception $e) { error_log("Export tickets API error: " . $e->getMessage()); header('Content-Type: application/json'); http_response_code(500); echo json_encode([ 'success' => false, 'error' => 'An internal error occurred' ]); }