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, '