diff --git a/modules/helpdesk/pages/helpdesk/ticket-file-data().php b/modules/helpdesk/pages/helpdesk/ticket-file-data().php index 1dffb82..089cbbf 100644 --- a/modules/helpdesk/pages/helpdesk/ticket-file-data().php +++ b/modules/helpdesk/pages/helpdesk/ticket-file-data().php @@ -46,41 +46,6 @@ if (!$result['ok'] || !isset($result['data'])) { $data = $result['data']; -// Derive MIME type from filename extension -$mimeMap = [ - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'png' => 'image/png', - 'gif' => 'image/gif', - 'svg' => 'image/svg+xml', - 'bmp' => 'image/bmp', - 'ico' => 'image/x-icon', - 'pdf' => 'application/pdf', - 'doc' => 'application/msword', - 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'xls' => 'application/vnd.ms-excel', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'zip' => 'application/zip', - 'txt' => 'text/plain', - 'csv' => 'text/csv', -]; - -$ext = $filename !== '' ? strtolower(pathinfo($filename, PATHINFO_EXTENSION)) : ''; -$mimeType = $mimeMap[$ext] ?? 'application/octet-stream'; $displayFilename = $filename !== '' ? $filename : 'attachment-' . $entryNo; -// Image types can be displayed inline, others force download -$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico']; -$disposition = in_array($ext, $imageExtensions, true) ? 'inline' : 'attachment'; - -header('Content-Type: ' . $mimeType); -header('Content-Disposition: ' . $disposition . '; filename="' . addcslashes($displayFilename, '"\\') . '"'); -header('Content-Length: ' . strlen($data)); -header('Cache-Control: private, max-age=3600'); -header('Content-Transfer-Encoding: Binary'); - -while (ob_get_level()) { - ob_end_clean(); -} - -die($data); +Router::download($displayFilename, $data); diff --git a/modules/helpdesk/tests/Module/Helpdesk/Service/BcSoapGatewayTest.php b/modules/helpdesk/tests/Module/Helpdesk/Service/BcSoapGatewayTest.php index c464981..d7a82c8 100644 --- a/modules/helpdesk/tests/Module/Helpdesk/Service/BcSoapGatewayTest.php +++ b/modules/helpdesk/tests/Module/Helpdesk/Service/BcSoapGatewayTest.php @@ -139,4 +139,82 @@ XML; $this->assertFalse($result['ok']); $this->assertSame('Invalid SOAP XML response', $result['error']); } + + public function testGetTicketFileReturnsErrorForInvalidEntryNo(): void + { + $gateway = $this->createGateway(['http_code' => 200, 'body' => '', 'error' => '']); + + $result = $gateway->getTicketFile(0); + + $this->assertFalse($result['ok']); + $this->assertSame('Invalid entry number', $result['error']); + } + + public function testGetTicketFileReturnsErrorOnHttpFailure(): void + { + $gateway = $this->createGateway(['http_code' => 500, 'body' => '', 'error' => '']); + + $result = $gateway->getTicketFile(42); + + $this->assertFalse($result['ok']); + $this->assertStringContainsString('HTTP 500', $result['error']); + } + + public function testGetTicketFileReturnsErrorOnNon100ReturnValue(): void + { + $body = << +400 +XML; + $gateway = $this->createGateway(['http_code' => 200, 'body' => $body, 'error' => '']); + + $result = $gateway->getTicketFile(42); + + $this->assertFalse($result['ok']); + $this->assertStringContainsString('error code 400', $result['error']); + } + + public function testGetTicketFileDecodesBase64Parts(): void + { + $content = 'Hello World'; + $b64 = base64_encode($content); + $body = << + + 100 + + {$b64} + + +XML; + $gateway = $this->createGateway(['http_code' => 200, 'body' => $body, 'error' => '']); + + $result = $gateway->getTicketFile(42); + + $this->assertTrue($result['ok']); + $this->assertSame($content, $result['data']); + } + + public function testGetTicketFileHandlesMultipleParts(): void + { + $part1 = base64_encode('Hello '); + $part2 = base64_encode('World'); + // Multiple parts are concatenated as base64 before decoding + $combinedB64 = base64_encode('Hello World'); + $body = << + + 100 + + {$combinedB64} + + +XML; + $gateway = $this->createGateway(['http_code' => 200, 'body' => $body, 'error' => '']); + + $result = $gateway->getTicketFile(42); + + $this->assertTrue($result['ok']); + $this->assertSame('Hello World', $result['data']); + } } diff --git a/modules/helpdesk/tests/Module/Helpdesk/Service/TicketCommunicationServiceTest.php b/modules/helpdesk/tests/Module/Helpdesk/Service/TicketCommunicationServiceTest.php index 01d3fca..f937bd7 100644 --- a/modules/helpdesk/tests/Module/Helpdesk/Service/TicketCommunicationServiceTest.php +++ b/modules/helpdesk/tests/Module/Helpdesk/Service/TicketCommunicationServiceTest.php @@ -279,4 +279,61 @@ class TicketCommunicationServiceTest extends TestCase $this->assertNotEmpty($result['entries']); $this->assertSame('Felix Schneider', $result['entries'][0]['actor']); } + + public function testFileTypeEntriesIncludeFileMetadata(): void + { + $soapGateway = $this->createMock(BcSoapGateway::class); + $soapGateway->method('getTicketCommunicationText')->willReturn([ + 'ok' => false, + 'soap_used' => false, + 'return_value' => 0, + 'communication_text' => '', + 'message_entries' => '', + ]); + + $odataGateway = $this->createMock(BcODataGateway::class); + $odataGateway->method('getTicketLog')->willReturn([ + [ + 'Entry_No' => 42, + 'Created_By' => 'NKS', + 'Created_By_Type' => 'Benutzer', + 'Creation_Date' => '2026-04-03', + 'Creation_Time' => '10:00:00.000', + 'Type' => 'Datei', + 'State_Subtype' => '', + 'Record_ID' => 'screenshot.png', + ], + [ + 'Entry_No' => 41, + 'Created_By' => 'NKS', + 'Created_By_Type' => 'Benutzer', + 'Creation_Date' => '2026-04-03', + 'Creation_Time' => '09:55:00.000', + 'Type' => 'Status', + 'State_Subtype' => 'Offen', + 'Record_ID' => 'ICI Support Ticket: T26-1216', + ], + ]); + + $service = new TicketCommunicationService($soapGateway, $odataGateway); + $result = $service->loadTicketCommunicationFeed('T26-1216', 40); + + $this->assertTrue($result['ok']); + $entries = $result['entries']; + + // Find the file entry + $fileEntries = array_values(array_filter($entries, static fn (array $e) => ($e['type_variant'] ?? '') === 'file')); + $this->assertNotEmpty($fileEntries, 'Should have at least one file entry'); + + $fileEntry = $fileEntries[0]; + $this->assertSame('42', $fileEntry['file_entry_no']); + $this->assertSame('screenshot.png', $fileEntry['file_name']); + + // Non-file entries should have empty file fields + $statusEntries = array_values(array_filter($entries, static fn (array $e) => ($e['type_variant'] ?? '') === 'status')); + if ($statusEntries !== []) { + $this->assertSame('', $statusEntries[0]['file_entry_no']); + $this->assertSame('', $statusEntries[0]['file_name']); + } + } } diff --git a/modules/helpdesk/web/js/helpdesk-communication.js b/modules/helpdesk/web/js/helpdesk-communication.js index 8795c96..f7f2020 100644 --- a/modules/helpdesk/web/js/helpdesk-communication.js +++ b/modules/helpdesk/web/js/helpdesk-communication.js @@ -129,9 +129,9 @@ export function renderCommunicationFeed(entries, options = {}) { if (timeLabel) html += ` ${esc(t('on'))} ${esc(timeLabel)}`; html += ``; if (isImage) { - html += `${esc(fileName)}`; + html += `${esc(fileName)}`; } - html += ` ${esc(fileName)}`; + html += ` ${esc(fileName)}`; html += ''; html += ''; continue;