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>
100 lines
3.0 KiB
PHP
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);
|