diff --git a/api.php b/api.php index 79c15ab..983aa19 100644 --- a/api.php +++ b/api.php @@ -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) diff --git a/db.php b/db.php index 0fcb9ad..964ec9d 100644 --- a/db.php +++ b/db.php @@ -677,6 +677,8 @@ function initDatabase(): void { "ALTER TABLE ticket_messages ADD COLUMN zammad_article_id INT DEFAULT NULL AFTER message_id", "ALTER TABLE availability_queries ADD COLUMN hostname VARCHAR(255) DEFAULT '' AFTER ip_address", "ALTER TABLE availability_queries ADD COLUMN org VARCHAR(255) DEFAULT '' AFTER hostname", + "ALTER TABLE tickets ADD COLUMN to_email VARCHAR(255) DEFAULT '' AFTER from_name", + "ALTER TABLE tickets ADD COLUMN bcc TEXT DEFAULT '' AFTER cc", ]; foreach ($alters as $sql) { try { $db->query($sql); } catch (\Throwable $e) { /* sarake on jo olemassa / jo ajettu */ } diff --git a/index.html b/index.html index f59ad5b..a9fc24d 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@