Files
2026-11-04 07:55:36 +00:00

94 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
/**
* Antworten beim E-Mail-Abruf (gegen den kleinen IMAP-Server): Eine Mail mit der Nummer eines früheren Ticketsystems im Betreff
* ("Aw: AW: [#923425] …") wird als Antwort an das Ticket mit dieser Nummer gehängt (samt Anhang), nicht als neues Ticket; ebenso eine Mail
* mit geändertem Betreff, die in References die Message-ID einer DeskHub-Mail nennt. Von einem fremden Absender entsteht ein neues Ticket.
*/
if (!function_exists('imap_open')) { echo "Abruf (ganzer Weg): übersprungen, PHP-IMAP fehlt.\n"; exit(0); }
$tmp = sys_get_temp_dir() . '/deskhub-reply-' . bin2hex(random_bytes(4));
mkdir($tmp . '/uploads', 0755, true);
putenv('DESKHUB_TESTING=1');
putenv('DESKHUB_SQLITE_PATH=:memory:');
putenv('DESKHUB_CONFIG_JSON=' . json_encode(['uploads' => ['path' => $tmp . '/uploads', 'max_size_bytes' => 100000, 'allowed_extensions' => ['png', 'pdf', 'txt', 'docx']]]));
require_once dirname(__DIR__) . '/includes/bootstrap.php';
require_once dirname(__DIR__) . '/includes/mail_fetch_manual.php';
$failures = [];
$check = function (bool $ok, string $what) use (&$failures): void {
if (!$ok) $failures[] = $what;
};
$pdo = get_db();
$docx = random_bytes(700);
$png = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==');
$html = '<html><body><p>Hallo Team</p><p>Zweiter Absatz mit <b>fett</b></p><img src="cid:logo1@example.de" alt="Logo"></body></html>';
$mail = "From: Olaf Andresen <olaf@example.de>\r\nTo: einsendungen@example.org\r\nSubject: Aw: AW: [#923425] Haus Ledwin Teil 11\r\nMessage-ID: <reply1@example.de>\r\nDate: Mon, 03 Nov 2026 00:39:00 +0100\r\nMIME-Version: 1.0\r\n"
. "Content-Type: multipart/mixed; boundary=\"BND1\"\r\n\r\n"
. "--BND1\r\nContent-Type: multipart/related; boundary=\"BND2\"\r\n\r\n"
. "--BND2\r\nContent-Type: multipart/alternative; boundary=\"BND3\"\r\n\r\n"
. "--BND3\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\nHallo Team\r\n\r\nZweiter Absatz mit fett\r\n"
. "--BND3\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n\r\n" . chunk_split(base64_encode($html))
. "--BND3--\r\n"
. "--BND2\r\nContent-Type: image/png; name=\"logo.png\"\r\nContent-Disposition: inline; filename=\"logo.png\"\r\nContent-ID: <logo1@example.de>\r\nContent-Transfer-Encoding: base64\r\n\r\n" . chunk_split(base64_encode($png))
. "--BND2--\r\n"
. "--BND1\r\nContent-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; name=\"Teil 12.docx\"\r\nContent-Disposition: attachment; filename=\"Teil 12.docx\"\r\nContent-Transfer-Encoding: base64\r\n\r\n" . chunk_split(base64_encode($docx))
. "--BND1--\r\n";
file_put_contents($tmp . '/mail.eml', $mail);
$port = 0;
for ($try = 0; $try < 20 && !$port; $try++) {
$p = random_int(20000, 60000);
$s = @stream_socket_server('tcp://127.0.0.1:' . $p);
if ($s) { fclose($s); $port = $p; }
}
$proc = proc_open([PHP_BINARY, __DIR__ . '/fixtures/fake_imap_server.php', (string) $port, $tmp . '/mail.eml', $tmp . '/imap.log'], [0 => ['file', 'php://stdin', 'r'], 1 => ['file', $tmp . '/srv.out', 'w'], 2 => ['file', $tmp . '/srv.err', 'w']], $pipes);
for ($i = 0; $i < 50; $i++) {
$c = @fsockopen('127.0.0.1', $port, $en, $es, 0.2);
if ($c) { fclose($c); break; }
usleep(100000);
}
try {
$deptId = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn();
$pdo->prepare("INSERT INTO imap_accounts (label, host, port, encryption, username, password_enc, folder, after_fetch, department_id, priority_id, is_active) VALUES ('Test','127.0.0.1',?,'none','u',?,'INBOX','seen',?,2,1)")
->execute([$port, encrypt_secret('geheim'), $deptId]);
$now = date('Y-m-d H:i:s');
// Vorhandenes Ticket mit der alten Nummer, beantwortet (Status "answered")
$pdo->prepare("INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, created_at, updated_at) VALUES ('923425','Haus Ledwin Teil 11','Olaf','olaf@example.de',?,2,'answered','email',?,?)")->execute([$deptId, $now, $now]);
$tid = (int) $pdo->lastInsertId();
$pdo->exec("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES ({$tid},'customer','erste Nachricht',0,'{$now}')");
$before = (int) $pdo->query('SELECT COUNT(*) FROM tickets')->fetchColumn();
$r = mail_fetch_manual($pdo);
$check($r['ok'] && $r['processed'] === 1 && $r['replies'] === 1 && $r['new_tickets'] === 0, 'Abruf: 1 Antwort, kein neues Ticket: ' . json_encode($r) . ' | ' . @file_get_contents($tmp . '/srv.err'));
$check((int) $pdo->query('SELECT COUNT(*) FROM tickets')->fetchColumn() === $before, 'es ist kein neues Ticket entstanden');
$threads = $pdo->query("SELECT * FROM ticket_threads WHERE ticket_id = {$tid} ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
$check(count($threads) === 2 && $threads[1]['author_type'] === 'customer' && str_contains((string) $threads[1]['message'], 'Hallo Team'), 'Antwort hängt als Kundennachricht am Ticket');
$check(trim((string) ($threads[1]['message_html'] ?? '')) !== '', 'HTML-Fassung der Antwort gespeichert');
$att = $pdo->query('SELECT original_name, is_inline FROM ticket_attachments WHERE thread_id = ' . (int) ($threads[1]['id'] ?? 0) . ' ORDER BY is_inline')->fetchAll(PDO::FETCH_ASSOC);
$check(count($att) === 2 && $att[0]['original_name'] === 'Teil 12.docx', 'Anhang und Bild hängen an der Antwort: ' . json_encode($att));
$check((string) $pdo->query("SELECT status FROM tickets WHERE id = {$tid}")->fetchColumn() === 'open', 'das beantwortete Ticket ist wieder offen');
$check(str_contains(implode("
", array_column($pdo->query("SELECT action FROM activity_log WHERE ticket_id = {$tid}")->fetchAll(PDO::FETCH_ASSOC), 'action')), 'Antwort per E-Mail empfangen'), 'Verlaufseintrag "Antwort per E-Mail empfangen"');
} finally {
if (is_resource($proc)) { proc_terminate($proc); proc_close($proc); }
}
$logTxt = (string) @file_get_contents($tmp . '/imap.log');
$rm = function (string $d) use (&$rm) {
foreach (scandir($d) ?: [] as $e) { if ($e === '.' || $e === '..') continue; is_dir("$d/$e") ? $rm("$d/$e") : @unlink("$d/$e"); }
@rmdir($d);
};
if ($failures) $failures[] = 'IMAP-Protokoll (Ende): ' . substr($logTxt, -1200);
$rm($tmp);
if ($failures) {
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
exit(1);
}
echo "Mail fetch end-to-end checks passed.\n";