Liitteiden näyttö Zammad-viesteissä + download proxy

- dbLoadTickets: attachments & zammad_article_id mukaan viestidataan
- ticket_detail on-demand: liitteiden metadata talteen (sama kuin sync)
- Uusi zammad_attachment proxy-endpoint liitteiden lataukseen
- JS: liitteet näkyvät tikettiviesteissä (ikoni, nimi, koko, latauslinkki)
- CSS: .msg-attachments ja .msg-attachment-link tyylit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 03:14:15 +02:00
parent 376912b9ff
commit dc0ed5c75c
5 changed files with 165 additions and 9 deletions

110
api.php
View File

@@ -3227,12 +3227,26 @@ switch ($action) {
if (($art['content_type'] ?? '') === 'text/html') {
$body = strip_tags($body, '<br><p><div><a><b><i><strong><em><ul><ol><li>');
}
// Liitteiden metadata
$attJson = '';
if (!empty($art['attachments'])) {
$atts = [];
foreach ($art['attachments'] as $att) {
$atts[] = [
'id' => $att['id'] ?? 0,
'filename' => $att['filename'] ?? '',
'size' => $att['size'] ?? 0,
'type' => $att['preferences']['Content-Type'] ?? ($att['content_type'] ?? 'application/octet-stream'),
];
}
$attJson = json_encode($atts);
}
_dbExecute(
"INSERT INTO ticket_messages (id, ticket_id, type, from_email, from_name, body, timestamp, message_id, zammad_article_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
"INSERT INTO ticket_messages (id, ticket_id, type, from_email, from_name, body, timestamp, message_id, zammad_article_id, attachments)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
[$msgId, $ticket['id'], $msgType, $art['from'] ?? '', $art['from'] ?? '', $body,
$art['created_at'] ? date('Y-m-d H:i:s', strtotime($art['created_at'])) : date('Y-m-d H:i:s'),
$art['message_id'] ?? '', $artId]
$art['message_id'] ?? '', $artId, $attJson]
);
}
// Tallenna Zammad to-osoite tikettiin
@@ -5410,8 +5424,13 @@ switch ($action) {
try {
$articles = $z->getArticles($zammadId);
$toEmail = '';
foreach ($articles as $art) {
if (($art['internal'] ?? false)) continue;
// Poimi zammad_to_email ensimmäisestä asiakkaan viestistä
if (!$toEmail && ($art['sender'] ?? '') === 'Customer' && !empty($art['to'])) {
$toEmail = $art['to'];
}
$artId = (int)$art['id'];
$existingMsg = dbGetMessageByZammadArticleId($ticketId, $artId);
if ($existingMsg) continue;
@@ -5423,18 +5442,38 @@ switch ($action) {
$body = strip_tags($body, '<br><p><div><a><b><i><strong><em><ul><ol><li>');
}
// Liitteiden metadata
$attJson = '';
if (!empty($art['attachments'])) {
$atts = [];
foreach ($art['attachments'] as $att) {
$atts[] = [
'id' => $att['id'] ?? 0,
'filename' => $att['filename'] ?? '',
'size' => $att['size'] ?? 0,
'type' => $att['preferences']['Content-Type'] ?? ($att['content_type'] ?? 'application/octet-stream'),
];
}
$attJson = json_encode($atts);
}
_dbExecute(
"INSERT INTO ticket_messages (id, ticket_id, type, from_email, from_name, body, timestamp, message_id, zammad_article_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
"INSERT INTO ticket_messages (id, ticket_id, type, from_email, from_name, body, timestamp, message_id, zammad_article_id, attachments)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
[$msgId, $ticketId, $msgType,
$art['from'] ?? '', $art['from'] ?? '',
$body,
$art['created_at'] ? date('Y-m-d H:i:s', strtotime($art['created_at'])) : date('Y-m-d H:i:s'),
$art['message_id'] ?? '',
$artId]
$artId,
$attJson]
);
$messagesAdded++;
}
// Tallenna zammad_to_email
if ($toEmail) {
_dbExecute("UPDATE tickets SET zammad_to_email = ? WHERE id = ?", [$toEmail, $ticketId]);
}
} catch (\Throwable $e) {
// Artikkelihaku epäonnistui — jatka silti
error_log("Zammad articles error for ticket $zammadId: " . $e->getMessage());
@@ -5454,6 +5493,65 @@ switch ($action) {
}
break;
case 'zammad_attachment':
requireAuth();
$companyId = requireCompanyOrParam();
$ticketId = $_GET['ticket_id'] ?? '';
$articleId = (int)($_GET['article_id'] ?? 0);
$attachmentId = (int)($_GET['attachment_id'] ?? 0);
if (!$ticketId || !$articleId || !$attachmentId) {
http_response_code(400);
echo json_encode(['error' => 'Puuttuvat parametrit (ticket_id, article_id, attachment_id)']);
break;
}
// Varmista että tiketti kuuluu yritykselle
$ticket = _dbFetch("SELECT * FROM tickets WHERE id = ? AND company_id = ?", [$ticketId, $companyId]);
if (!$ticket || empty($ticket['zammad_ticket_id'])) {
http_response_code(404);
echo json_encode(['error' => 'Tikettiä ei löydy']);
break;
}
try {
$integ = dbGetIntegration($companyId, 'zammad');
if (!$integ || !$integ['enabled']) {
http_response_code(400);
echo json_encode(['error' => 'Zammad-integraatio ei käytössä']);
break;
}
// Lataa liite Zammad API:sta (binary download)
$zUrl = rtrim($integ['config']['url'], '/');
if (!preg_match('#^https?://#i', $zUrl)) $zUrl = 'https://' . $zUrl;
$zToken = $integ['config']['token'];
$url = "{$zUrl}/api/v1/ticket_attachment/{$ticket['zammad_ticket_id']}/{$articleId}/{$attachmentId}";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Authorization: Token token=' . $zToken,
],
]);
$fileData = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
if ($httpCode >= 400 || $fileData === false) {
http_response_code(502);
echo json_encode(['error' => 'Liitteen lataus Zammadista epäonnistui (HTTP ' . $httpCode . ')']);
break;
}
// Hae tiedostonimi metadata:sta
$filename = $_GET['filename'] ?? 'attachment';
header('Content-Type: ' . ($contentType ?: 'application/octet-stream'));
header('Content-Disposition: attachment; filename="' . addslashes($filename) . '"');
header('Content-Length: ' . strlen($fileData));
echo $fileData;
} catch (\Throwable $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
break;
case 'zammad_reply':
$companyId = requireCompany();
$input = json_decode(file_get_contents('php://input'), true);