198 lines
16 KiB
PHP
198 lines
16 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Prüft das Plugin "Schichtübergabe": Notizen, Sichtbarkeit je Abteilung, ungelesen/gelesen, Menü-Zähler, wichtige Notizen in der Seitenleiste,
|
|
* Löschrechte und automatisches Aufräumen.
|
|
*/
|
|
|
|
$pluginDir = sys_get_temp_dir() . '/deskhub-sh-' . bin2hex(random_bytes(4));
|
|
mkdir($pluginDir, 0755, true);
|
|
putenv('DESKHUB_PLUGINS_DIR=' . $pluginDir);
|
|
putenv('DESKHUB_TESTING=1');
|
|
putenv('DESKHUB_CATALOG_DEFAULTS=' . __DIR__ . '/fixtures/no-catalog.php');
|
|
putenv('DESKHUB_SQLITE_PATH=:memory:');
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
|
|
$failures = [];
|
|
$check = function (bool $ok, string $what) use (&$failures): void {
|
|
if (!$ok) $failures[] = $what;
|
|
};
|
|
$pdo = get_db();
|
|
|
|
require_once __DIR__ . '/fixtures/paths.php';
|
|
$src = plugin_source_dir_or_skip('schichtuebergabe', 'plugin_schichtuebergabe');
|
|
$zipPath = sys_get_temp_dir() . '/sh-' . bin2hex(random_bytes(3)) . '.zip';
|
|
$zip = new ZipArchive();
|
|
$zip->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, '<script>alert(1)</script>', null, true);
|
|
$html = plugin_filter('ticket.view.sidebar', '', ['ticketId' => 1, 'ticket' => [], 'staff' => $ben]);
|
|
$check(str_contains($html, '<script>') && !str_contains($html, '<script>'), 'Inhalte werden in der Seitenleiste maskiert');
|
|
$check(plugin_filter('ticket.view.sidebar', '', ['ticketId' => 1, 'ticket' => [], 'staff' => null]) === '', 'ohne Mitarbeiter keine Seitenleiste');
|
|
|
|
// Der Verfasser sieht seine eigene wichtige Notiz ebenfalls (12 Stunden), mit der Zahl der Leser
|
|
$ownHtml = plugin_filter('ticket.view.sidebar', '', ['ticketId' => 1, 'ticket' => [], 'staff' => $anna]);
|
|
$check(str_contains($ownHtml, 'Ihre Notiz') && str_contains($ownHtml, 'Server X zickt') && str_contains($ownHtml, 'von 1 gelesen'), 'Verfasser sieht seine wichtige Notiz in der Seitenleiste, mit „von 1 gelesen“ (Ben): ' . strip_tags($ownHtml));
|
|
$check(!str_contains($ownHtml, 'Nur für Abteilung 1'), 'unwichtige eigene Notizen erscheinen dort nicht');
|
|
$pdo->prepare('UPDATE plugin_schichtuebergabe_notes SET created_at = ? WHERE id = ?')->execute([date('Y-m-d H:i:s', time() - 13 * 3600), $all['id']]);
|
|
$check(!str_contains(plugin_filter('ticket.view.sidebar', '', ['ticketId' => 1, 'ticket' => [], 'staff' => $anna]), 'Server X zickt'), 'nach 12 Stunden verschwindet die eigene Notiz aus der Seitenleiste');
|
|
$pdo->prepare('UPDATE plugin_schichtuebergabe_notes SET created_at = ? WHERE id = ?')->execute([db_now(), $all['id']]);
|
|
$check(schicht_unread_count($pdo, $anna) === 0, 'die eigene Notiz zählt trotzdem nicht als „neu“ im Menü');
|
|
$both = schicht_sidebar_notes($pdo, $chef);
|
|
$check(count(array_unique(array_column($both, 'id'))) === count($both), 'keine Notiz doppelt (ungelesen und eigene)');
|
|
|
|
// Löschen
|
|
$check(!schicht_delete($pdo, $ben, $all['id']), 'fremde Notiz kann ein Mitarbeiter nicht löschen');
|
|
$check(schicht_delete($pdo, $anna, $mine['id']), 'der Verfasser kann löschen');
|
|
$check(schicht_delete($pdo, $chef, $all['id']), 'Administrator kann jede Notiz löschen');
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM plugin_schichtuebergabe_reads WHERE note_id IN ({$all['id']}, {$mine['id']})")->fetchColumn() === 0, 'Lesemarken werden mit gelöscht');
|
|
$check(!schicht_delete($pdo, $chef, 99999), 'unbekannte Notiz');
|
|
|
|
// Aufbewahrung
|
|
plugin_set_setting('schichtuebergabe', 'keep_days', '7');
|
|
$pdo->prepare('UPDATE plugin_schichtuebergabe_notes SET created_at = ? WHERE id = ?')->execute([date('Y-m-d H:i:s', time() - 10 * 86400), $tech['id']]);
|
|
$check(!in_array($tech['id'], $ids(schicht_list($pdo, $chef)), true), 'Notizen älter als die Frist werden nicht mehr angezeigt');
|
|
plugin_set_setting('schichtuebergabe', 'last_cleanup', '0');
|
|
$check(schicht_cleanup($pdo) >= 1 && (int) $pdo->query("SELECT COUNT(*) FROM plugin_schichtuebergabe_notes WHERE id = {$tech['id']}")->fetchColumn() === 0, 'Aufräumen löscht abgelaufene Notizen');
|
|
$check(schicht_cleanup($pdo) === 0, 'Aufräumen läuft höchstens einmal pro Stunde');
|
|
plugin_set_setting('schichtuebergabe', 'keep_days', '0');
|
|
$check(schicht_keep_days() === 1, 'Frist hat eine Untergrenze');
|
|
|
|
plugin_set_setting('schichtuebergabe', 'keep_days', '30');
|
|
// Deutlicher Hinweis oben auf jeder Seite (auch direkt nach der Anmeldung), solange etwas ungelesen ist
|
|
$check(plugin_filter_permission('staff.page.top') === 'ui.sidebar', 'der Seitenkopf-Hinweis braucht die Berechtigung ui.sidebar (hat das Plugin)');
|
|
$check(str_contains((string) file_get_contents(dirname(__DIR__) . '/staff/_header.php'), "plugin_filter('staff.page.top'"), 'das Layout des Mitarbeiterbereichs ruft den Hinweis oben auf jeder Seite auf');
|
|
$bannerFor = fn(?array $who, string $page = 'reports') => plugin_filter('staff.page.top', '', ['staff' => $who, 'activePage' => $page]);
|
|
$pdo->exec('DELETE FROM plugin_schichtuebergabe_reads');
|
|
$pdo->exec('DELETE FROM plugin_schichtuebergabe_notes');
|
|
$pdo->exec('DELETE FROM plugin_schichtuebergabe_reads');
|
|
$check($bannerFor($ben) === '', 'ohne Notizen kein Hinweis');
|
|
$n1 = schicht_create($pdo, $anna, "Normale Info\nzweite Zeile", null, false);
|
|
$h1 = $bannerFor($ben);
|
|
$check(str_contains($h1, 'Schichtübergabe: 1 neue Notiz') && str_contains($h1, 'Normale Info') && str_contains($h1, '#0f6f6a') && !str_contains($h1, 'wichtig)') && str_contains($h1, 'page=schichtuebergabe') && str_contains($h1, 'Normale Info<br />'), 'eine normale Notiz: grüner Hinweis mit Text und Link');
|
|
$n2 = schicht_create($pdo, $anna, 'Server X ist ausgefallen', null, true);
|
|
$h2 = $bannerFor($ben);
|
|
$check(str_contains($h2, '2 neue Notizen') && str_contains($h2, '(1 wichtig)') && str_contains($h2, '#dc2626') && strpos($h2, 'Server X ist ausgefallen') < strpos($h2, 'Normale Info'), 'mit wichtiger Notiz: roter Hinweis, die wichtige zuerst');
|
|
$check($bannerFor($anna, 'tickets') === '' && !str_contains($bannerFor($anna, 'tickets'), 'neue Notiz'), 'auf anderen Seiten sieht der Verfasser keinen „neu“-Hinweis zu seinen eigenen Notizen');
|
|
$own = $bannerFor($anna, 'reports');
|
|
$check(str_contains($own, 'Ihre Notiz an die nächste Schicht') && str_contains($own, 'Server X ist ausgefallen') && str_contains($own, 'Normale Info') && str_contains($own, 'noch von niemandem gelesen') && !str_contains($own, 'neue Notiz') && str_contains($own, '#93c5fd'), 'auf der Übersicht sieht der Verfasser seine eigenen Notizen als ruhige Bestätigung (blau), noch ungelesen: ' . substr_count($own, 'schicht-banner-own') . ' Kästen');
|
|
$check(substr_count($own, 'schicht-banner-own') === 2 && str_contains($own, '(wichtig)') && str_contains($own, 'alle Mitarbeiter'), 'beide Notizen als eigene Kästen, die wichtige ist gekennzeichnet');
|
|
$check(str_contains($bannerFor($anna, 'dashboard'), 'Ihre Notiz an die nächste Schicht'), 'auch auf der Dashboard-Seite');
|
|
schicht_mark_read($pdo, $ben, [$n2['id']]);
|
|
$check(str_contains($bannerFor($anna, 'reports'), 'Gelesen von: Ben'), 'der Verfasser sieht, wer die Notiz gelesen hat');
|
|
$check($bannerFor($ben, 'plugin_schichtuebergabe') === '', 'auf der Seite der Schichtübergabe selbst entfallen die Hinweise');
|
|
$pdo->prepare('UPDATE plugin_schichtuebergabe_notes SET created_at = ? WHERE id IN (?, ?)')->execute([date('Y-m-d H:i:s', time() - 13 * 3600), $n1['id'], $n2['id']]);
|
|
$check(!str_contains($bannerFor($anna, 'reports'), 'Ihre Notiz an die nächste Schicht'), 'nach 12 Stunden verschwindet die Bestätigung des Verfassers');
|
|
$pdo->prepare('UPDATE plugin_schichtuebergabe_notes SET created_at = ? WHERE id IN (?, ?)')->execute([db_now(), $n1['id'], $n2['id']]);
|
|
$check($bannerFor($ben, 'plugin_schichtuebergabe') === '', 'auf der Seite der Schichtübergabe selbst erscheint der Hinweis nicht');
|
|
for ($i = 0; $i < 4; $i++) schicht_create($pdo, $anna, 'Weitere ' . $i, null, false);
|
|
$check(str_contains($bannerFor($ben), '… und 2 weitere') && substr_count($bannerFor($ben), '<strong>Anna</strong>') === 3, 'höchstens drei Notizen im Hinweis, der Rest als „weitere“');
|
|
schicht_create($pdo, $anna, '<img src=x onerror=alert(1)>', null, true);
|
|
$check(!str_contains($bannerFor($ben), '<img') && str_contains($bannerFor($ben), '<img'), 'Inhalte im Hinweis werden maskiert');
|
|
schicht_mark_read($pdo, $ben, array_map(fn($n) => $n['id'], schicht_unread($pdo, $ben)));
|
|
$check($bannerFor($ben) === '', 'nach dem Lesen verschwindet der Hinweis');
|
|
$tc = schicht_create($pdo, $chef, 'Nur Technik', $d2, false);
|
|
$check(!str_contains($bannerFor($anna, 'tickets'), 'Nur Technik') && str_contains($bannerFor($ben), 'Nur Technik'), 'Abteilungsnotizen erscheinen nur bei der Abteilung');
|
|
$check($bannerFor(null) === '', 'ohne Mitarbeiter kein Hinweis');
|
|
|
|
// ── Seite: Gelesen-Knöpfe, nichts wird beim Öffnen automatisch gelesen ──────
|
|
$_SESSION['csrf_token'] = 'tok';
|
|
$shPage = $pluginDir . '/schichtuebergabe/pages/schichtuebergabe.php';
|
|
$shRender = function (array $who, string $method = 'GET', array $post = []) use ($pdo, $shPage): string {
|
|
$_SERVER['REQUEST_METHOD'] = $method;
|
|
$_POST = $post + ($method === 'POST' ? ['csrf_token' => 'tok'] : []);
|
|
$staff = $who;
|
|
ob_start();
|
|
include $shPage;
|
|
return (string) ob_get_clean();
|
|
};
|
|
$pdo->exec('DELETE FROM plugin_schichtuebergabe_reads');
|
|
$pdo->exec('DELETE FROM plugin_schichtuebergabe_notes');
|
|
$u1 = schicht_create($pdo, $anna, 'Lesetest eins', null, false)['id'];
|
|
$u2 = schicht_create($pdo, $anna, 'Lesetest zwei', null, true)['id'];
|
|
$pg = $shRender($ben);
|
|
$check(count(schicht_unread($pdo, $ben)) === 2 && str_contains($pg, 'Alle 2 als gelesen markieren') && substr_count($pg, '✓ Gelesen') === 2, 'Öffnen der Seite markiert nichts automatisch, Knöpfe sind da');
|
|
$check(str_contains($bannerFor($ben), '✓ Gelesen') && str_contains($bannerFor($ben), 'value="mark_all"'), 'der Hinweis hat einen Gelesen-Knopf');
|
|
$pg = $shRender($ben, 'POST', ['action' => 'mark_read', 'id' => (string) $u1]);
|
|
$check(count(schicht_unread($pdo, $ben)) === 1 && str_contains($pg, 'Als gelesen markiert') && str_contains($pg, 'Alle 1 als gelesen markieren'), 'eine Notiz einzeln als gelesen markieren');
|
|
$pg = $shRender($ben, 'POST', ['action' => 'mark_all']);
|
|
$check(count(schicht_unread($pdo, $ben)) === 0 && !str_contains($pg, 'als gelesen markieren</button>') && $bannerFor($ben) === '', 'alle als gelesen: Hinweis verschwindet');
|
|
$check(count(schicht_unread($pdo, $anna)) === 0 && !str_contains($shRender($anna), '✓ Gelesen'), 'eigene Notizen sind schon gelesen, kein Knopf');
|
|
$check(substr_count($bannerFor($anna, 'reports'), 'schicht-banner-own') === 2 && str_contains($bannerFor($anna, 'reports'), 'value="hide_own"'), 'eigene Bestätigungen haben einen Ausblenden-Knopf');
|
|
$shRender($anna, 'POST', ['action' => 'hide_own', 'id' => (string) $u2]);
|
|
$check(substr_count($bannerFor($anna, 'reports'), 'schicht-banner-own') === 1 && !str_contains($bannerFor($anna, 'reports'), 'Lesetest zwei') && str_contains($shRender($anna), 'Lesetest zwei'), 'ausgeblendete Bestätigung verschwindet, die Notiz bleibt in der Liste');
|
|
$check(substr_count($bannerFor($chef, 'reports'), 'schicht-banner-own') === 0 && count(schicht_unread($pdo, $ben)) === 0, 'Ausblenden betrifft nur den eigenen Mitarbeiter');
|
|
|
|
foreach (scandir($pluginDir) ?: [] as $entry) {
|
|
if ($entry === '.' || $entry === '..') continue;
|
|
is_dir($pluginDir . '/' . $entry) ? plugin_rmdir($pluginDir . '/' . $entry) : @unlink($pluginDir . '/' . $entry);
|
|
}
|
|
@rmdir($pluginDir);
|
|
@unlink($zipPath);
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo 'Schichtübergabe plugin checks passed.' . "\n";
|