Files
tinker_tickets/api/export_tickets.php
Jared Vititoe ed9c2a39d1 Fix error message disclosure in API endpoints
Replace exception getMessage() exposure with generic error messages
to prevent internal information disclosure. Errors are now logged
with full details while clients receive sanitized responses.

Affected endpoints:
- add_comment, update_comment, delete_comment
- update_ticket, export_tickets
- generate_api_key, revoke_api_key
- manage_templates, manage_workflows, manage_recurring
- custom_fields, get_users

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:56:29 -05:00

168 lines
5.7 KiB
PHP

<?php
/**
* Export Tickets API
*
* Exports tickets to CSV format with optional filtering
* Respects ticket visibility settings
*/
// Disable error display in the output
ini_set('display_errors', 0);
error_reporting(E_ALL);
// Apply rate limiting
require_once dirname(__DIR__) . '/middleware/RateLimitMiddleware.php';
RateLimitMiddleware::apply('api');
try {
// Include required files
require_once dirname(__DIR__) . '/config/config.php';
require_once dirname(__DIR__) . '/helpers/Database.php';
require_once dirname(__DIR__) . '/models/TicketModel.php';
// Check authentication via session
session_start();
if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) {
header('Content-Type: application/json');
http_response_code(401);
echo json_encode(['success' => 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;
// 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)
// getAllTickets already applies visibility filtering via getVisibilityFilter
$result = $ticketModel->getAllTickets(1, 10000, $status, 'created_at', 'desc', $category, $type, $search);
$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;
} else {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Invalid format. Use csv or json.']);
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'
]);
}