['path' => $uploads, 'max_size_bytes' => 4 * 1048576, 'allowed_extensions' => ['png', 'pdf', 'txt']]])); require_once dirname(__DIR__) . '/includes/bootstrap.php'; require_once dirname(__DIR__) . '/includes/integrations.php'; $failures = []; $check = function (bool $ok, string $what) use (&$failures): void { if (!$ok) $failures[] = $what; }; $pdo = get_db(); set_setting('upload_allowed_extensions', 'png, pdf, txt'); set_setting('upload_max_size_mb', '4'); // Grenzen im Formular-Angebot $def = public_form_definition($pdo, null, (int) get_setting('default_department_id', '1')); $payload = public_form_payload($def); $check(!empty($payload['attachments']['enabled']) && $payload['attachments']['maxFiles'] === 5 && $payload['attachments']['maxFileBytes'] === 4 * 1048576 && $payload['attachments']['allowedExtensions'] === ['png', 'pdf', 'txt'], 'Formular nennt die Grenzen: ' . json_encode($payload['attachments'])); $base = ['name' => 'Anna Muster', 'email' => 'anna@example.de', 'subject' => 'Mit Anhang', 'message' => 'Siehe Datei.']; $b64 = fn(string $s) => base64_encode($s); $count = fn() => (int) $pdo->query('SELECT COUNT(*) FROM tickets')->fetchColumn(); $fails = function (array $in) use ($pdo, $count): ?string { $before = $count(); try { public_ticket_create($pdo, $in, '203.0.113.5'); } catch (AdminModelException $e) { return $e->getMessage() . ' | ' . ($count() === $before ? 'kein Ticket' : 'TICKET ANGELEGT'); } return null; }; // Erfolg: zwei Dateien $r = public_ticket_create($pdo, $base + ['attachments' => [['name' => 'Bericht.pdf', 'data' => $b64("%PDF-1.4\n" . str_repeat('x', 500))], ['name' => 'notiz.txt', 'data' => $b64('Hallo')]]], '203.0.113.5'); $number = (string) ($r['ticketNumber'] ?? $r['number'] ?? ''); $tid = (int) $pdo->query('SELECT id FROM tickets WHERE ticket_number = ' . $pdo->quote($number))->fetchColumn(); $att = $pdo->query('SELECT a.* FROM ticket_attachments a JOIN ticket_threads t ON t.id = a.thread_id WHERE t.ticket_id = ' . $tid . ' ORDER BY a.id')->fetchAll(PDO::FETCH_ASSOC); $check(count($att) === 2 && $att[0]['original_name'] === 'Bericht.pdf' && $att[1]['original_name'] === 'notiz.txt', 'Anhänge hängen an der ersten Nachricht: ' . json_encode(array_column($att, 'original_name')) . ' ' . json_encode($r)); $check($att && file_get_contents($uploads . '/' . $att[1]['stored_name']) === 'Hallo' && (int) $att[1]['filesize'] === 5, 'Inhalt wurde gespeichert'); // Ohne Anhänge wie bisher $r2 = public_ticket_create($pdo, $base, '203.0.113.5'); $check(!empty($r2['ticketNumber'] ?? $r2['number'] ?? ''), 'ohne Anhänge funktioniert alles wie bisher'); // Fehlerfälle: nichts wird angelegt $e = $fails($base + ['attachments' => [['name' => 'x.exe', 'data' => $b64('MZ')]]]); $check($e !== null && str_ends_with($e, 'kein Ticket'), 'gesperrte Endung (.exe) lehnt ab, ohne Ticket: ' . $e); $e = $fails($base + ['attachments' => [['name' => 'a.docx', 'data' => $b64('x')]]]); $check($e !== null && str_ends_with($e, 'kein Ticket'), 'nicht freigegebene Endung (.docx) lehnt ab: ' . $e); $e = $fails($base + ['attachments' => [['name' => 'a.php', 'data' => $b64(' array_fill(0, 6, ['name' => 'a.txt', 'data' => $b64('x')])]); $check($e !== null && str_contains($e, 'höchstens 5') && str_ends_with($e, 'kein Ticket'), 'mehr als 5 Dateien lehnt ab: ' . $e); $e = $fails($base + ['attachments' => [['name' => 'gross.pdf', 'data' => $b64(str_repeat('x', 4 * 1048576 + 1))]]]); $check($e !== null && str_contains($e, 'zu groß') && str_ends_with($e, 'kein Ticket'), 'zu große Datei lehnt ab: ' . $e); $e = $fails($base + ['attachments' => [['name' => 'a.txt', 'data' => $b64(str_repeat('x', 3 * 1048576))], ['name' => 'b.txt', 'data' => $b64(str_repeat('x', 3 * 1048576))], ['name' => 'c.txt', 'data' => $b64(str_repeat('x', 3 * 1048576))], ['name' => 'd.txt', 'data' => $b64(str_repeat('x', 2 * 1048576))]]]); $check($e !== null && str_contains($e, 'zusammen') && str_ends_with($e, 'kein Ticket'), 'zusammen zu groß (über 10 MB) lehnt ab: ' . $e); $e = $fails($base + ['attachments' => [['name' => 'a.txt', 'data' => '!!!kein-base64!!!']]]); $check($e !== null && str_contains($e, 'Base64') && str_ends_with($e, 'kein Ticket'), 'ungültiges Base64 lehnt ab: ' . $e); $e = $fails($base + ['attachments' => [['data' => $b64('x')]]]); $check($e !== null && str_ends_with($e, 'kein Ticket'), 'Anhang ohne Namen lehnt ab'); $e = $fails($base + ['attachments' => 'text']); $check($e !== null && str_contains($e, 'Format'), 'falsches Format lehnt ab: ' . $e); $e = $fails($base + ['attachments' => [['name' => '../../evil.txt', 'data' => $b64('x')]]]); $check($e === null && (int) $pdo->query("SELECT COUNT(*) FROM ticket_attachments WHERE original_name = 'evil.txt'")->fetchColumn() === 1, 'Pfadanteile im Dateinamen werden entfernt: ' . json_encode($e)); // Eigenes Formular ohne Anhänge $deptId = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn(); $pdo->prepare('UPDATE departments SET own_form = 1, form_config = ? WHERE id = ?')->execute([json_encode(['title' => 'Ohne Dateien', 'attachment_show' => false, 'subject_show' => true, 'message_show' => true]), $deptId]); $def2 = public_form_definition($pdo, null, $deptId); $check(public_form_payload($def2)['attachments']['enabled'] === false, 'eigenes Formular kann Anhänge abschalten (im Angebot)'); $e = $fails($base + ['departmentId' => $deptId, 'fields' => [], 'attachments' => [['name' => 'a.txt', 'data' => $b64('x')]]]); $check($e !== null && str_contains($e, 'keine Dateien') && str_ends_with($e, 'kein Ticket'), 'bei abgeschalteten Anhängen: Ablehnung: ' . $e); $rm = function (string $d) use (&$rm) { foreach (scandir($d) ?: [] as $x) { if ($x === '.' || $x === '..') continue; is_dir("$d/$x") ? $rm("$d/$x") : @unlink("$d/$x"); } @rmdir($d); }; $rm($uploads); if ($failures) { fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n"); exit(1); } echo "Public API attachment checks passed.\n";