open($zipPath, ZipArchive::CREATE); foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($src, FilesystemIterator::SKIP_DOTS)) as $f) { $zip->addFile($f->getPathname(), str_replace('\\', '/', substr($f->getPathname(), strlen($src) + 1))); } $zip->close(); $r = plugin_install_zip($pdo, $zipPath); $check($r['ok'] && $r['slug'] === 'schichtuebergabe', 'Plugin lässt sich installieren: ' . json_encode($r)); $act = plugin_set_active($pdo, 'schichtuebergabe', true, ['hooks.cron', 'ui.menu', 'ui.sidebar', 'db.migrations']); $check($act['ok'], 'Plugin lässt sich aktivieren: ' . json_encode($act)); $state = &plugin_state(); $state['booted'] = false; plugins_boot(); $check(count(plugin_menu_items(['id' => 1, 'role' => 'agent'])) === 1, 'Menüpunkt ist für jeden Mitarbeiter sichtbar'); $d1 = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn(); $pdo->prepare("INSERT INTO departments (name, email, is_active, created_at) VALUES ('Technik', 'technik@team.example', 1, ?)")->execute([db_now()]); $d2 = (int) $pdo->lastInsertId(); $mk = function (string $name, string $role, ?int $dept) use ($pdo): array { $pdo->prepare('INSERT INTO staff (name, email, password_hash, role, department_id, is_active, created_at) VALUES (?,?,?,?,?,1,?)') ->execute([$name, strtolower($name) . '@team.example', 'x', $role, $dept, db_now()]); return ['id' => (int) $pdo->lastInsertId(), 'role' => $role, 'department_id' => $dept, 'name' => $name]; }; $anna = $mk('Anna', 'agent', $d1); $ben = $mk('Ben', 'agent', $d2); $chef = $mk('Chef', 'admin', null); // Anlegen und Prüfungen $check(!schicht_create($pdo, $anna, ' ', null, false)['ok'], 'leere Notiz wird abgelehnt'); $check(!schicht_create($pdo, $anna, str_repeat('x', 3001), null, false)['ok'], 'zu lange Notiz wird abgelehnt'); $check(!schicht_create($pdo, $anna, 'Test', $d2, false)['ok'], 'nur für eigene Abteilungen schreiben'); $check(!schicht_create($pdo, $anna, 'Test', 99999, false)['ok'] && !schicht_create($pdo, $chef, 'Test', 99999, false)['ok'], 'unbekannte Abteilung wird abgelehnt'); $all = schicht_create($pdo, $anna, "Server X zickt.\nBitte beobachten.", null, true); $mine = schicht_create($pdo, $anna, 'Nur für Abteilung 1', $d1, false); $tech = schicht_create($pdo, $chef, 'Nur für Technik', $d2, false); $check($all['ok'] && $mine['ok'] && $tech['ok'], 'Notizen lassen sich anlegen'); // Sichtbarkeit $ids = fn(array $rows) => array_map(fn($n) => (int) $n['id'], $rows); $check($ids(schicht_list($pdo, $ben)) === [$tech['id'], $all['id']], 'Ben sieht „alle“ und Technik, nicht Abteilung 1: ' . json_encode($ids(schicht_list($pdo, $ben)))); $check($ids(schicht_list($pdo, $anna)) === [$mine['id'], $all['id']], 'Anna sieht „alle“ und ihre Abteilung'); $check(count(schicht_list($pdo, $chef)) === 3, 'Administrator sieht alles'); // Ungelesen / gelesen $check(schicht_unread_count($pdo, $anna) === 0, 'Verfasser hat die eigene Notiz schon gelesen'); $check(schicht_unread_count($pdo, $ben) === 2 && schicht_unread_count($pdo, $chef) === 2, 'Andere sehen sie als neu (Ben 2, Chef 2 ohne die eigene)'); $unread = schicht_unread($pdo, $ben); $check(str_contains((string) $unread[1]['message'], "\n") && $unread[1]['author'] === 'Anna', 'Notiz enthält Zeilenumbruch und Verfasser'); $_SESSION['staff_id'] = $ben['id']; $label = schicht_menu_label(); $check($label === 'Schichtübergabe (2 neu)', 'Menü zeigt den Zähler: ' . $label); schicht_mark_read($pdo, $ben, [$all['id']]); schicht_mark_read($pdo, $ben, [$all['id']]); $check(schicht_unread_count($pdo, $ben) === 1, 'gelesene Notiz zählt nicht mehr (auch doppelt markieren schadet nicht)'); schicht_mark_read($pdo, $ben, array_map(fn($n) => $n['id'], schicht_unread($pdo, $ben))); $check(schicht_unread_count($pdo, $ben) === 0, 'alles gelesen'); $check(schicht_readers($pdo, $all['id']) === ['Anna', 'Ben'], 'Verfasser sieht, wer gelesen hat: ' . json_encode(schicht_readers($pdo, $all['id']))); unset($_SESSION['staff_id']); // Seitenleiste: nur wichtige und ungelesene $html = plugin_filter('ticket.view.sidebar', '', ['ticketId' => 1, 'ticket' => [], 'staff' => $chef]); $check(str_contains($html, 'Wichtige Schichtübergabe') && str_contains($html, 'Server X zickt') && !str_contains($html, 'Nur für Technik'), 'Seitenleiste zeigt nur wichtige ungelesene Notizen'); $check(plugin_filter('ticket.view.sidebar', '', ['ticketId' => 1, 'ticket' => [], 'staff' => $ben]) === '', 'wer sie gelesen hat, sieht sie nicht mehr in der Seitenleiste'); $xss = schicht_create($pdo, $anna, '', null, true); $html = plugin_filter('ticket.view.sidebar', '', ['ticketId' => 1, 'ticket' => [], 'staff' => $ben]); $check(str_contains($html, '<script>') && !str_contains($html, '