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:
110
api.php
110
api.php
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user