Files
breadcrumb-the-shire/modules/helpdesk/pages/helpdesk/ticket-file-data().php
fs f40b23d2b0 fix(helpdesk): detect file extension from magic bytes when filename has none
OData TicketLog doesn't expose the original filename (DataText field).
When the derived filename has no extension, detect the file type from
magic bytes (PNG, JPEG, GIF, PDF, ZIP) and append the correct extension.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:58:12 +02:00

100 lines
3.0 KiB
PHP

<?php
use MintyPHP\Module\Helpdesk\HelpdeskAuthorizationPolicy;
use MintyPHP\Module\Helpdesk\Service\BcSoapGateway;
use MintyPHP\Router;
use MintyPHP\Support\Guard;
Guard::requireLogin();
Guard::requireAbilityOrForbidden(HelpdeskAuthorizationPolicy::ABILITY_ACCESS);
$request = requestInput();
if ($request->method() !== 'GET') {
http_response_code(405);
Router::json(['ok' => false, 'error' => 'method_not_allowed']);
return;
}
$entryNo = (int) $request->query('entryNo', '0');
$ticketNo = trim((string) $request->query('ticketNo', ''));
$filename = trim((string) $request->query('filename', ''));
if ($entryNo <= 0) {
http_response_code(400);
Router::json(['ok' => false, 'error' => 'missing_entry_no']);
return;
}
$soapGateway = app(BcSoapGateway::class);
$odataGateway = app(\MintyPHP\Module\Helpdesk\Service\BcODataGateway::class);
// Resolve contactNo from ticket's Current_Contact_Name for SOAP authorization
$contactNo = '';
if ($ticketNo !== '') {
try {
$ticket = $odataGateway->getTicket($ticketNo);
if (is_array($ticket)) {
$contactName = trim((string) ($ticket['Current_Contact_Name'] ?? ''));
$customerName = trim((string) ($ticket['Company_Contact_Name'] ?? ''));
if ($contactName !== '' && $customerName !== '') {
$contacts = $odataGateway->getContactsForCustomer('', $customerName);
foreach ($contacts as $contact) {
if (trim((string) ($contact['Name'] ?? '')) === $contactName) {
$contactNo = trim((string) ($contact['No'] ?? ''));
break;
}
}
}
}
} catch (\Throwable) {
// Continue without contactNo — SOAP may still work
}
}
try {
$result = $soapGateway->getTicketFile($entryNo, $contactNo);
} catch (\Throwable) {
http_response_code(502);
Router::json(['ok' => false, 'error' => 'Failed to load file']);
return;
}
if (!$result['ok'] || !isset($result['data'])) {
http_response_code(404);
Router::json(['ok' => false, 'error' => $result['error'] ?? 'File not found']);
return;
}
$data = $result['data'];
$maxFileSize = 20 * 1024 * 1024; // 20 MB
if (strlen($data) > $maxFileSize) {
http_response_code(413);
Router::json(['ok' => false, 'error' => 'File too large']);
return;
}
// Ensure filename has an extension — derive from content if missing
$displayFilename = $filename !== '' ? $filename : 'attachment-' . $entryNo;
if (!str_contains($displayFilename, '.')) {
$ext = match (true) {
str_starts_with($data, "\x89PNG") => '.png',
str_starts_with($data, "\xFF\xD8\xFF") => '.jpg',
str_starts_with($data, "GIF8") => '.gif',
str_starts_with($data, "%PDF") => '.pdf',
str_starts_with($data, "PK\x03\x04") => '.zip',
default => '',
};
$displayFilename .= $ext;
}
header('X-Content-Type-Options: nosniff');
header('Content-Security-Policy: sandbox');
Router::download($displayFilename, $data);