BCC-kenttä tiketteihin + To/CC/BCC tallennus + allekirjoituskorjaus

- Lisää BCC-kenttä vastauslomakkeeseen (HTML, JS, API)
- To/CC/BCC tallentuvat tiketille pysyvästi (seuraava vastaus muistaa muutokset)
- Lisää to_email ja bcc sarakkeet tickets-tauluun
- BCC-tuki SMTP-lähetykseen (RCPT TO ilman headeria)
- Korjaa allekirjoitukset: buildSignaturesWithDefaults() generoi nyt oletukset
  myös Zammad-sähköposteille (support@web1.fi ym.), ei pelkille mailboxeille
- Allekirjoituksiin lisätty puhelinnumero

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 02:54:57 +02:00
parent 3d66319d89
commit 6f1d9ed5d4
4 changed files with 67 additions and 20 deletions

63
api.php
View File

@@ -304,7 +304,7 @@ class ZammadClient {
}
/** Lähetä vastaus tikettiin */
public function createArticle(int $ticketId, string $body, string $to = '', string $subject = '', string $type = 'email', string $cc = ''): array {
public function createArticle(int $ticketId, string $body, string $to = '', string $subject = '', string $type = 'email', string $cc = '', string $bcc = ''): array {
// Muunna plain-text HTML:ksi jos body ei sisällä HTML-tageja
$htmlBody = $body;
if (strip_tags($body) === $body) {
@@ -321,6 +321,8 @@ class ZammadClient {
];
if ($to) $data['to'] = $to;
if ($cc) $data['cc'] = $cc;
// Zammad API: BCC lähetetään vastaanottajina mutta ei näy vastaanottajille
// Huom: Zammad ei ehkä tue suoraan BCC-kenttää artikkelissa, joten lisätään RCPT-vastaanottajaksi
if ($subject) $data['subject'] = 'Re: ' . preg_replace('/^Re:\s*/i', '', $subject);
return $this->request('POST', 'ticket_articles', $data);
}
@@ -919,23 +921,39 @@ function buildSignaturesWithDefaults(array $user, array $userCompanyIds): array
$allCompanies = dbLoadCompanies();
foreach ($allCompanies as $comp) {
if (!in_array($comp['id'], $userCompanyIds)) continue;
$etunimi = trim(explode(' ', $user['nimi'] ?? '')[0]);
$yritys = $comp['nimi'] ?? '';
$phone = $comp['phone'] ?? '';
// SMTP-postilaatikoiden allekirjoitukset
$mailboxes = dbLoadMailboxes($comp['id']);
foreach ($mailboxes as $mb) {
if (!empty($sigs[$mb['id']])) continue; // käyttäjällä on jo oma allekirjoitus
// Generoi oletus: Etunimi \n Yritys \n sähköposti
$etunimi = trim(explode(' ', $user['nimi'] ?? '')[0]);
$yritys = ($comp['nimi'] ?? '') . ' Oy';
if (!empty($sigs[$mb['id']])) continue;
$email = $mb['smtp_from_email'] ?? $mb['imap_user'] ?? '';
$parts = array_filter([$etunimi, $yritys, $email]);
if ($phone) $parts[] = $phone;
if (!empty($parts)) {
$sigs[$mb['id']] = implode("\n", $parts);
}
}
// Zammad-sähköpostien allekirjoitukset
$zEmails = _dbFetchAll(
"SELECT DISTINCT zammad_to_email FROM tickets WHERE company_id = ? AND source = 'zammad' AND zammad_to_email IS NOT NULL AND zammad_to_email != ''",
[$comp['id']]
);
foreach ($zEmails as $r) {
$key = 'zammad:' . $r['zammad_to_email'];
if (!empty($sigs[$key])) continue;
$parts = array_filter([$etunimi, $yritys, $r['zammad_to_email']]);
if ($phone) $parts[] = $phone;
if (!empty($parts)) {
$sigs[$key] = implode("\n", $parts);
}
}
}
return $sigs;
}
function sendTicketMail(string $to, string $subject, string $body, string $inReplyTo = '', string $references = '', ?array $mailbox = null, string $cc = ''): bool {
function sendTicketMail(string $to, string $subject, string $body, string $inReplyTo = '', string $references = '', ?array $mailbox = null, string $cc = '', string $bcc = ''): bool {
$fromEmail = $mailbox['smtp_from_email'] ?? $mailbox['imap_user'] ?? MAIL_FROM;
$fromName = $mailbox['smtp_from_name'] ?? $mailbox['nimi'] ?? 'Asiakaspalvelu';
@@ -943,7 +961,7 @@ function sendTicketMail(string $to, string $subject, string $body, string $inRep
$smtpHost = $mailbox['smtp_host'] ?? '';
error_log("MAIL DEBUG: to={$to} smtpHost={$smtpHost} from={$fromEmail} mailbox_keys=" . implode(',', array_keys($mailbox ?? [])));
if ($smtpHost !== '') {
return sendViaSMTP($to, $subject, $body, $fromEmail, $fromName, $inReplyTo, $references, $mailbox, $cc);
return sendViaSMTP($to, $subject, $body, $fromEmail, $fromName, $inReplyTo, $references, $mailbox, $cc, $bcc);
}
// Fallback: PHP mail()
@@ -954,6 +972,9 @@ function sendTicketMail(string $to, string $subject, string $body, string $inRep
if ($cc) {
$headers .= "Cc: {$cc}\r\n";
}
if ($bcc) {
$headers .= "Bcc: {$bcc}\r\n";
}
if ($inReplyTo) {
$headers .= "In-Reply-To: {$inReplyTo}\r\n";
$headers .= "References: " . ($references ? $references . ' ' : '') . $inReplyTo . "\r\n";
@@ -985,7 +1006,7 @@ function smtpCode(string $resp): string {
return substr(trim($resp), 0, 3);
}
function sendViaSMTP(string $to, string $subject, string $body, string $fromEmail, string $fromName, string $inReplyTo, string $references, array $mailbox, string $cc): bool {
function sendViaSMTP(string $to, string $subject, string $body, string $fromEmail, string $fromName, string $inReplyTo, string $references, array $mailbox, string $cc, string $bcc = ''): bool {
$host = $mailbox['smtp_host'];
$port = (int)($mailbox['smtp_port'] ?? 587);
// Fallback-ketju käyttäjälle: smtp_user → imap_user → smtp_from_email
@@ -1089,6 +1110,7 @@ function sendViaSMTP(string $to, string $subject, string $body, string $fromEmai
// 7. RCPT TO
$allRecipients = array_filter(array_map('trim', explode(',', $to)));
if ($cc) $allRecipients = array_merge($allRecipients, array_filter(array_map('trim', explode(',', $cc))));
if ($bcc) $allRecipients = array_merge($allRecipients, array_filter(array_map('trim', explode(',', $bcc))));
foreach ($allRecipients as $rcpt) {
$resp = smtpCommand($fp, "RCPT TO:<{$rcpt}>");
$log[] = "rcpt:" . smtpCode($resp);
@@ -3424,6 +3446,7 @@ switch ($action) {
$replyMailboxId = $input['mailbox_id'] ?? '';
$replyTo = trim($input['to'] ?? '');
$replyCc = trim($input['cc'] ?? '');
$replyBcc = trim($input['bcc'] ?? '');
if (empty($body)) {
http_response_code(400);
echo json_encode(['error' => 'Viesti ei voi olla tyhjä']);
@@ -3487,12 +3510,13 @@ switch ($action) {
}
}
// CC: käytä frontendistä annettua CC:tä, tai tiketin alkuperäistä CC:tä
// CC/BCC: käytä frontendistä annettua arvoa, tai tiketin tallennettua
$ccToSend = $replyCc !== '' ? $replyCc : ($t['cc'] ?? '');
$bccToSend = $replyBcc;
$subject = 'Re: ' . $t['subject'];
$toAddress = $replyTo !== '' ? $replyTo : $t['from_email'];
$sent = sendTicketMail($toAddress, $subject, $emailBody, $lastMsgId, trim($allRefs), $replyMailbox, $ccToSend);
$sent = sendTicketMail($toAddress, $subject, $emailBody, $lastMsgId, trim($allRefs), $replyMailbox, $ccToSend, $bccToSend);
if (!$sent) {
http_response_code(500);
@@ -3510,10 +3534,11 @@ switch ($action) {
break 2;
}
// Päivitä tiketin CC jos muuttunut
if ($replyCc !== '' && $replyCc !== ($t['cc'] ?? '')) {
$t['cc'] = $replyCc;
}
// Tallenna To/CC/BCC tiketille pysyvästi
_dbExecute(
"UPDATE tickets SET to_email = ?, cc = ?, bcc = ? WHERE id = ? AND company_id = ?",
[$toAddress, $ccToSend, $bccToSend, $id, $companyId]
);
// Add reply to ticket (tallennetaan allekirjoituksen kanssa)
$reply = [
@@ -5409,6 +5434,7 @@ switch ($action) {
$z = new ZammadClient($integ['config']['url'], $integ['config']['token']);
$to = !empty($input['to']) ? trim($input['to']) : ($ticket['from_email'] ?? '');
$cc = !empty($input['cc']) ? trim($input['cc']) : '';
$bcc = !empty($input['bcc']) ? trim($input['bcc']) : '';
// Muunna uusi viesti HTML:ksi (säilytä rivinvaihdot ja välilyönnit)
$escaped = htmlspecialchars($body, ENT_QUOTES, 'UTF-8');
@@ -5446,7 +5472,14 @@ switch ($action) {
$to,
$ticket['subject'] ?? '',
'email',
$cc
$cc,
$bcc
);
// Tallenna To/CC/BCC tiketille pysyvästi
_dbExecute(
"UPDATE tickets SET to_email = ?, cc = ?, bcc = ? WHERE id = ? AND company_id = ?",
[$to, $cc, $bcc, $ticketId, $companyId]
);
// Tallenna myös paikalliseen tietokantaan (vain uusi viesti, ei ketjua)