Zammad-tiketit: vastaus Zammad API:n kautta + to-osoite + HTML-viestit
1. Zammad-tiketeille näytetään vastaanotto-osoite (esim. support@web1.fi) lähettäjäkentässä eikä SMTP-postilaatikkolistaa — vastaus menee Zammad API:n kautta. 2. Ensimmäisen artikkelin to-osoite tallennetaan zammad_to_email kenttään on-demand artikkelien haussa. 3. Korjattu _dbFetchRow → _dbFetchOne zammad_reply endpointissa. 4. sanitizeHtml() renderöi viestien HTML turvallisesti. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
11
api.php
11
api.php
@@ -3075,8 +3075,13 @@ switch ($action) {
|
|||||||
if ($integ && $integ['enabled']) {
|
if ($integ && $integ['enabled']) {
|
||||||
$z = new ZammadClient($integ['config']['url'], $integ['config']['token']);
|
$z = new ZammadClient($integ['config']['url'], $integ['config']['token']);
|
||||||
$articles = $z->getArticles((int)$ticket['zammad_ticket_id']);
|
$articles = $z->getArticles((int)$ticket['zammad_ticket_id']);
|
||||||
|
$toEmail = '';
|
||||||
foreach ($articles as $art) {
|
foreach ($articles as $art) {
|
||||||
if (($art['internal'] ?? false)) continue;
|
if (($art['internal'] ?? false)) continue;
|
||||||
|
// Tallenna ensimmäisen saapuneen viestin to-osoite
|
||||||
|
if (!$toEmail && ($art['sender'] ?? '') === 'Customer' && !empty($art['to'])) {
|
||||||
|
$toEmail = $art['to'];
|
||||||
|
}
|
||||||
$artId = (int)$art['id'];
|
$artId = (int)$art['id'];
|
||||||
$existingMsg = dbGetMessageByZammadArticleId($ticket['id'], $artId);
|
$existingMsg = dbGetMessageByZammadArticleId($ticket['id'], $artId);
|
||||||
if ($existingMsg) continue;
|
if ($existingMsg) continue;
|
||||||
@@ -3094,6 +3099,10 @@ switch ($action) {
|
|||||||
$art['message_id'] ?? '', $artId]
|
$art['message_id'] ?? '', $artId]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Tallenna Zammad to-osoite tikettiin
|
||||||
|
if ($toEmail) {
|
||||||
|
_dbExecute("UPDATE tickets SET zammad_to_email = ? WHERE id = ?", [$toEmail, $ticket['id']]);
|
||||||
|
}
|
||||||
// Lataa tiketti uudelleen viestien kanssa
|
// Lataa tiketti uudelleen viestien kanssa
|
||||||
$tickets = dbLoadTickets($companyId);
|
$tickets = dbLoadTickets($companyId);
|
||||||
foreach ($tickets as $t) {
|
foreach ($tickets as $t) {
|
||||||
@@ -5276,7 +5285,7 @@ switch ($action) {
|
|||||||
$body = $input['body'] ?? '';
|
$body = $input['body'] ?? '';
|
||||||
if (!$ticketId || !$body) { http_response_code(400); echo json_encode(['error' => 'Tiketti ja viesti vaaditaan']); break; }
|
if (!$ticketId || !$body) { http_response_code(400); echo json_encode(['error' => 'Tiketti ja viesti vaaditaan']); break; }
|
||||||
|
|
||||||
$ticket = _dbFetchRow("SELECT * FROM tickets WHERE id = ? AND company_id = ?", [$ticketId, $companyId]);
|
$ticket = _dbFetchOne("SELECT * FROM tickets WHERE id = ? AND company_id = ?", [$ticketId, $companyId]);
|
||||||
if (!$ticket || !$ticket['zammad_ticket_id']) {
|
if (!$ticket || !$ticket['zammad_ticket_id']) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['error' => 'Tiketti ei ole Zammad-tiketti']);
|
echo json_encode(['error' => 'Tiketti ei ole Zammad-tiketti']);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Noxus HUB</title>
|
<title>Noxus HUB</title>
|
||||||
<link rel="stylesheet" href="style.css?v=20260313b">
|
<link rel="stylesheet" href="style.css?v=20260313c">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Login -->
|
<!-- Login -->
|
||||||
@@ -2229,6 +2229,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="script.js?v=20260313b"></script>
|
<script src="script.js?v=20260313c"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
12
script.js
12
script.js
@@ -1846,12 +1846,16 @@ async function showTicketDetail(id, companyId = '') {
|
|||||||
const ccField = document.getElementById('reply-cc');
|
const ccField = document.getElementById('reply-cc');
|
||||||
if (ccField) ccField.value = ticket.cc || '';
|
if (ccField) ccField.value = ticket.cc || '';
|
||||||
|
|
||||||
// Mailbox-valinta — täytetään yrityksen postilaatikoista
|
// Mailbox-valinta — Zammad-tiketit vastaa Zammadin kautta, muut SMTP:llä
|
||||||
const mbSelect = document.getElementById('reply-mailbox-select');
|
const mbSelect = document.getElementById('reply-mailbox-select');
|
||||||
if (mbSelect) {
|
if (mbSelect) {
|
||||||
|
if (ticket.source === 'zammad' && ticket.zammad_ticket_id) {
|
||||||
|
// Zammad-tiketti: vastaus menee Zammadin kautta
|
||||||
|
const zTo = ticket.zammad_to_email || ticket.zammad_group || 'Zammad';
|
||||||
|
mbSelect.innerHTML = `<option value="zammad" selected>${esc(zTo)} (Zammad)</option>`;
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
const mailboxes = await apiCall('all_mailboxes');
|
const mailboxes = await apiCall('all_mailboxes');
|
||||||
// Suodata pois piilotetut postilaatikot (paitsi jos tiketin oma mailbox on piilotettu — se näytetään silti)
|
|
||||||
const visibleMailboxes = mailboxes.filter(mb =>
|
const visibleMailboxes = mailboxes.filter(mb =>
|
||||||
String(mb.id) === String(ticket.mailbox_id || '') ||
|
String(mb.id) === String(ticket.mailbox_id || '') ||
|
||||||
(!currentHiddenMailboxes.includes(String(mb.id)) && !currentHiddenMailboxes.includes(mb.id))
|
(!currentHiddenMailboxes.includes(String(mb.id)) && !currentHiddenMailboxes.includes(mb.id))
|
||||||
@@ -1864,11 +1868,11 @@ async function showTicketDetail(id, companyId = '') {
|
|||||||
`<option value="${esc(mb.id)}" ${String(mb.id) === ticketMbId ? 'selected' : ''}>${esc(mb.nimi || mb.smtp_from_email)} <${esc(mb.smtp_from_email)}></option>`
|
`<option value="${esc(mb.id)}" ${String(mb.id) === ticketMbId ? 'selected' : ''}>${esc(mb.nimi || mb.smtp_from_email)} <${esc(mb.smtp_from_email)}></option>`
|
||||||
).join('');
|
).join('');
|
||||||
mbSelect.innerHTML = optionsHtml;
|
mbSelect.innerHTML = optionsHtml;
|
||||||
// Vaihda allekirjoitusta kun mailbox vaihtuu
|
} catch (e) { mbSelect.innerHTML = '<option>Ei postilaatikoita</option>'; }
|
||||||
|
}
|
||||||
mbSelect.addEventListener('change', function() {
|
mbSelect.addEventListener('change', function() {
|
||||||
updateSignaturePreview(this.value);
|
updateSignaturePreview(this.value);
|
||||||
});
|
});
|
||||||
} catch (e) { mbSelect.innerHTML = '<option>Ei postilaatikoita</option>'; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allekirjoituksen esikatselu
|
// Allekirjoituksen esikatselu
|
||||||
|
|||||||
Reference in New Issue
Block a user