127 lines
5.6 KiB
PHP
127 lines
5.6 KiB
PHP
<?php
|
|
/**
|
|
* includes/mail_reload.php
|
|
*
|
|
* Lädt die Anhänge einer schon abgerufenen E-Mail nachträglich ins Ticket (für Mails, die abgerufen wurden, bevor DeskHub Anhänge
|
|
* übernommen hat, oder deren Anhang damals nicht übernommen werden konnte). Die Original-Mail wird im Postfach gesucht: über die
|
|
* gespeicherte UID im aktuellen Ordner, sonst über die Message-ID.
|
|
*/
|
|
|
|
require_once __DIR__ . '/imap_spam.php';
|
|
require_once __DIR__ . '/mail_text.php';
|
|
|
|
/**
|
|
* Nachricht des Tickets, zu der eine Mail gehört: die Kundennachricht, die zur Zeit des Abrufs angelegt wurde (die nächstliegende).
|
|
*
|
|
* @param array<int,array{id:int|string,created_at:string}> $threads Kundennachrichten des Tickets
|
|
*/
|
|
function mail_reload_pick_thread(array $threads, string $sourceCreatedAt): ?int
|
|
{
|
|
$at = strtotime($sourceCreatedAt) ?: 0;
|
|
$best = null;
|
|
$bestDiff = PHP_INT_MAX;
|
|
foreach ($threads as $t) {
|
|
$diff = abs((strtotime((string) $t['created_at']) ?: 0) - $at);
|
|
if ($diff < $bestDiff) {
|
|
$bestDiff = $diff;
|
|
$best = (int) $t['id'];
|
|
}
|
|
}
|
|
return $best;
|
|
}
|
|
|
|
/**
|
|
* Findet die Mail im geöffneten Ordner: über die UID (und passende Message-ID, falls beide bekannt), sonst nur über die Message-ID.
|
|
*/
|
|
function mail_reload_find_message(mixed $mbox, array $source): ?int
|
|
{
|
|
$wanted = imap_normalize_message_id((string) ($source['message_id'] ?? ''));
|
|
$uid = (int) ($source['message_uid'] ?? 0);
|
|
if ($uid > 0) {
|
|
$num = (int) @imap_msgno($mbox, $uid);
|
|
if ($num > 0) {
|
|
if ($wanted === '') return $num;
|
|
$h = @imap_headerinfo($mbox, $num);
|
|
if ($h && imap_normalize_message_id($h->message_id ?? '') === $wanted) return $num;
|
|
}
|
|
}
|
|
if ($wanted !== '') {
|
|
$hits = @imap_search($mbox, 'ALL') ?: [];
|
|
foreach ($hits as $num) {
|
|
$h = @imap_headerinfo($mbox, (int) $num);
|
|
if ($h && imap_normalize_message_id($h->message_id ?? '') === $wanted) return (int) $num;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Lädt die Anhänge aller Original-Mails eines Tickets nach. Bereits vorhandene Anhänge werden nicht doppelt gespeichert.
|
|
*
|
|
* @return array{error:?string,added:int,inline:int,html:int,skipped:int,existing:int,mails:int,failed:string[]}
|
|
*/
|
|
function reload_ticket_mail_attachments(PDO $pdo, array $ticket, ?int $staffId = null): array
|
|
{
|
|
$res = ['error' => null, 'added' => 0, 'inline' => 0, 'html' => 0, 'skipped' => 0, 'existing' => 0, 'mails' => 0, 'failed' => []];
|
|
if (!function_exists('imap_open')) {
|
|
$res['error'] = 'PHP-IMAP ist nicht installiert.';
|
|
return $res;
|
|
}
|
|
$ticketId = (int) $ticket['id'];
|
|
$srcStmt = $pdo->prepare(
|
|
'SELECT tes.*, ia.label, ia.host, ia.port, ia.encryption, ia.username, ia.password_enc
|
|
FROM ticket_email_sources tes JOIN imap_accounts ia ON ia.id = tes.imap_account_id
|
|
WHERE tes.ticket_id = ? ORDER BY tes.id'
|
|
);
|
|
$srcStmt->execute([$ticketId]);
|
|
$sources = $srcStmt->fetchAll();
|
|
if (!$sources) {
|
|
$res['error'] = 'Zu diesem Ticket ist keine Original-E-Mail gespeichert (nur per E-Mail abgerufene Tickets).';
|
|
return $res;
|
|
}
|
|
$thStmt = $pdo->prepare("SELECT id, created_at FROM ticket_threads WHERE ticket_id = ? AND author_type = 'customer' AND is_internal_note = 0 ORDER BY id");
|
|
$thStmt->execute([$ticketId]);
|
|
$threads = $thStmt->fetchAll();
|
|
if (!$threads) {
|
|
$res['error'] = 'Das Ticket hat keine Kundennachricht, an die Anhänge gehängt werden könnten.';
|
|
return $res;
|
|
}
|
|
|
|
foreach ($sources as $source) {
|
|
$folder = trim((string) ($source['current_folder'] ?? '')) ?: 'INBOX';
|
|
$label = (string) ($source['label'] ?: $source['username']);
|
|
$mbox = imap_connect_account($source, $folder);
|
|
if (!$mbox) {
|
|
$res['failed'][] = "[$label] Postfach nicht erreichbar: " . (imap_last_error() ?: 'unbekannter Fehler');
|
|
continue;
|
|
}
|
|
$num = mail_reload_find_message($mbox, $source);
|
|
if ($num === null) {
|
|
imap_close($mbox);
|
|
$res['failed'][] = "[$label] Original-E-Mail wurde im Ordner „{$folder}“ nicht gefunden (verschoben oder gelöscht?).";
|
|
continue;
|
|
}
|
|
$threadId = mail_reload_pick_thread($threads, (string) $source['created_at']);
|
|
$structure = @imap_fetchstructure($mbox, $num) ?: null;
|
|
$log = [];
|
|
$fetchPart = fn(string $no) => (string) imap_fetchbody($mbox, $num, $no);
|
|
// Formatierte Fassung der Nachricht (HTML) nachtragen, wenn es noch keine gibt
|
|
$htmlBody = null;
|
|
if (ticket_mail_html_ready($pdo) && $structure) {
|
|
$htmlBody = mail_html_for_display($structure, $fetchPart);
|
|
$has = $pdo->prepare('SELECT message_html FROM ticket_threads WHERE id = ?');
|
|
$has->execute([(int) $threadId]);
|
|
if ($htmlBody !== null && trim((string) $has->fetchColumn()) === '') {
|
|
$pdo->prepare('UPDATE ticket_threads SET message_html = ? WHERE id = ?')->execute([$htmlBody, (int) $threadId]);
|
|
$res['html']++;
|
|
}
|
|
}
|
|
$c = mail_store_attachments($pdo, $structure, $fetchPart, (int) $threadId, $ticketId, $label, $log, true, $htmlBody !== null);
|
|
imap_close($mbox);
|
|
$res['mails']++;
|
|
foreach (['added', 'inline', 'skipped', 'existing'] as $k) $res[$k] += $c[$k];
|
|
}
|
|
if ($res['added'] + $res['inline'] + $res['html'] > 0) log_activity($pdo, $ticketId, $staffId, 'Anhänge und Bilder aus der E-Mail nachgeladen: ' . $res['added'] . ' Anhang/Anhänge, ' . $res['inline'] . ' Bild(er)' . ($res['html'] ? ', Formatierung' : ''));
|
|
return $res;
|
|
}
|