['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_reload.php';
$failures = [];
$check = function (bool $ok, string $what) use (&$failures): void {
if (!$ok) $failures[] = $what;
};
$pdo = get_db();
// Mail wie von Outlook: Text und HTML (alternative) + eingebettetes Logo (related) + Word-Datei (mixed)
$docx = random_bytes(900);
$png = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==');
$html = '
und de nächste Teil
Absatz zwei fett

';
$mail = "From: Olaf Andresen \r\nTo: einsendungen@example.org\r\nSubject: Aw: [#260101-0001] Haus Ledwin Teil 11\r\nMessage-ID: \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: quoted-printable\r\n\r\nund de n=C3=A4chste Teil\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: \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=\"Haus Ledwin .docx\"\r\nContent-Disposition: attachment; filename=\"Haus Ledwin .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);
// Server starten
$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; }
}
$logFile = $tmp . '/imap.log';
$proc = proc_open([PHP_BINARY, __DIR__ . '/fixtures/fake_imap_server.php', (string) $port, $tmp . '/mail.eml', $logFile], [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 {
$pdo->prepare("INSERT INTO imap_accounts (label, host, port, encryption, username, password_enc, folder) VALUES ('Test','127.0.0.1',?, 'none','u',?, 'INBOX')")
->execute([$port, encrypt_secret('geheim')]);
$accountId = (int) $pdo->lastInsertId();
$now = date('Y-m-d H:i:s');
$pdo->exec("INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, created_at, updated_at) VALUES ('260101-0001','Haus Ledwin','Olaf','olaf@example.de',1,2,'open','email','{$now}','{$now}')");
$ticketId = (int) $pdo->lastInsertId();
$early = date('Y-m-d H:i:s', time() - 3600);
$pdo->exec("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES ({$ticketId},'customer','erste Nachricht',0,'{$early}')");
$firstThread = (int) $pdo->lastInsertId();
$pdo->exec("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES ({$ticketId},'customer','und de nächste Teil',0,'{$now}')");
$replyThread = (int) $pdo->lastInsertId();
$pdo->exec("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES ({$ticketId},'staff','Antwort',0,'{$now}')");
$pdo->prepare('INSERT INTO ticket_email_sources (ticket_id, imap_account_id, message_uid, message_id, current_folder, created_at) VALUES (?,?,?,?,?,?)')
->execute([$ticketId, $accountId, 7, '', 'INBOX', $now]);
$ticket = $pdo->query("SELECT * FROM tickets WHERE id = {$ticketId}")->fetch(PDO::FETCH_ASSOC);
// Zuordnung zur Nachricht
$threads = $pdo->query("SELECT id, created_at FROM ticket_threads WHERE ticket_id = {$ticketId} AND author_type = 'customer' ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
$check(mail_reload_pick_thread($threads, $now) === $replyThread, 'Mail wird der Nachricht zugeordnet, die zur Zeit des Abrufs entstand');
$check(mail_reload_pick_thread($threads, $early) === $firstThread, 'frühe Mail gehört zur ersten Nachricht');
$r = reload_ticket_mail_attachments($pdo, $ticket, 1);
$check($r['error'] === null && $r['mails'] === 1 && $r['added'] === 1 && $r['inline'] === 1 && $r['html'] === 1 && $r['failed'] === [], 'Nachladen: 1 Anhang übernommen: ' . json_encode($r) . ' | Server: ' . @file_get_contents($tmp . '/srv.err'));
$rows = $pdo->query("SELECT * FROM ticket_attachments WHERE thread_id = {$replyThread}")->fetchAll(PDO::FETCH_ASSOC);
$files = array_values(array_filter($rows, fn($r) => (int) $r['is_inline'] === 0));
$inl = array_values(array_filter($rows, fn($r) => (int) $r['is_inline'] === 1));
$check(count($files) === 1 && $files[0]['original_name'] === 'Haus Ledwin .docx', 'Anhang hängt an der richtigen Nachricht: ' . json_encode($rows));
$check(count($inl) === 1 && $inl[0]['content_id'] === 'logo1@example.de', 'das Logo ist als eingebettetes Bild gespeichert (nicht in der Anhangliste)');
$check($inl && file_get_contents($tmp . '/uploads/' . $inl[0]['stored_name']) === $png, 'Inhalt des eingebetteten Bildes stimmt');
$mh = (string) $pdo->query("SELECT message_html FROM ticket_threads WHERE id = {$replyThread}")->fetchColumn();
$check(str_contains($mh, 'und de nächste Teil
') && str_contains($mh, 'fett') && str_contains($mh, 'cid:logo1@example.de'), 'HTML-Fassung der Nachricht ist gespeichert: ' . substr($mh, 0, 200));
$check(!str_contains($mh, '