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'] === 'rundmail', 'Plugin lässt sich installieren: ' . json_encode($r)); $act = plugin_set_active($pdo, 'rundmail', true, ['hooks.cron', 'ui.menu', '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 Mitarbeiter sichtbar'); $sentMails = []; $GLOBALS['rundmail_send'] = function (string $to, string $subject, string $html) use (&$sentMails): bool { if (str_contains($to, 'kaputt')) return false; $sentMails[] = compact('to', 'subject', 'html'); return true; }; $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(); $prio = (int) $pdo->query('SELECT id FROM priorities ORDER BY sort_order LIMIT 1')->fetchColumn(); $mkStaff = 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, 'email' => strtolower($name) . '@team.example']; }; $anna = $mkStaff('Anna', 'agent', $d1); $chef = $mkStaff('Chef', 'admin', null); $pdo->prepare("INSERT INTO organizations (name, created_at) VALUES ('Muster GmbH', ?)")->execute([db_now()]); $org = (int) $pdo->lastInsertId(); $mkUser = function (string $name, string $email, ?int $org) use ($pdo): int { $pdo->prepare('INSERT INTO users (name, email, password_hash, organization_id, created_at) VALUES (?,?,?,?,?)')->execute([$name, $email, 'x', $org, db_now()]); return (int) $pdo->lastInsertId(); }; $u1 = $mkUser('Erika Muster', 'erika@muster.example', $org); $u2 = $mkUser('Max Muster', 'max@muster.example', $org); $n = 0; $mk = function (string $name, string $email, int $dept, string $status = 'open', ?int $userId = null, int $spam = 0) use ($pdo, $prio, &$n): int { $n++; $now = db_now(); $pdo->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, user_id, department_id, priority_id, status, source, is_spam, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)') ->execute(['RM-' . $n, 'T' . $n, $name, $email, $userId, $dept, $prio, $status, 'web', $spam, $now, $now]); return (int) $pdo->lastInsertId(); }; $mk('Erika Muster', 'erika@muster.example', $d1, 'open', $u1); $mk('Erika Muster', 'ERIKA@muster.example', $d1, 'pending', $u1); // gleiche Adresse, einmal $mk('Gast Eins', 'gast1@kunde.example', $d1); $mk('Gast Zwei', 'gast2@kunde.example', $d2); $mk('Fertig', 'fertig@kunde.example', $d1, 'closed'); $mk('Gelöst', 'geloest@kunde.example', $d1, 'resolved'); $mk('Spammer', 'spam@kunde.example', $d1, 'open', null, 1); $mk('Discord', 'discord-123@discord.invalid', $d1); $mk('Anonym', 'anon_abcd@deleted.local', $d1); $mk('Kaputt', 'kaputt@kunde.example', $d1); $pdo->prepare('INSERT INTO banlist (match_type, value, created_at) VALUES (?,?,?)')->execute(['email', 'gesperrt@kunde.example', db_now()]); $mk('Gesperrt', 'gesperrt@kunde.example', $d1); $mails = fn(array $rows) => array_map(fn($r) => $r['email'], $rows); // Empfänger $all = $mails(rundmail_recipients($pdo, $chef, 'open', null)); sort($all); $check($all === ['erika@muster.example', 'gast1@kunde.example', 'gast2@kunde.example', 'kaputt@kunde.example'], 'offene Tickets: nur brauchbare Adressen, einmal je Adresse: ' . json_encode($all)); $dep = $mails(rundmail_recipients($pdo, $chef, 'department', $d2)); $check($dep === ['gast2@kunde.example'], 'Abteilung: nur offene Tickets dieser Abteilung'); $orgAll = $mails(rundmail_recipients($pdo, $chef, 'organization', $org)); $check($orgAll === ['erika@muster.example', 'max@muster.example'], 'Organisation (Administrator): alle Mitglieder'); $orgStaff = $mails(rundmail_recipients($pdo, $anna, 'organization', $org)); $check($orgStaff === ['erika@muster.example'], 'Organisation (Mitarbeiter): nur Mitglieder mit offenem Ticket in der eigenen Abteilung'); $annaOpen = $mails(rundmail_recipients($pdo, $anna, 'open', null)); sort($annaOpen); $check($annaOpen === ['erika@muster.example', 'gast1@kunde.example', 'kaputt@kunde.example'], 'Mitarbeiter sehen nur Kunden der eigenen Abteilung'); $check(rundmail_recipients($pdo, $anna, 'department', $d2) === [], 'fremde Abteilung ist für Mitarbeiter leer'); $groupsAnna = array_column(rundmail_groups($pdo, $anna), 'key'); $groupsChef = array_column(rundmail_groups($pdo, $chef), 'key'); $check(!in_array('department:' . $d2, $groupsAnna, true) && in_array('department:' . $d2, $groupsChef, true) && in_array('organization:' . $org, $groupsAnna, true), 'Gruppenauswahl richtet sich nach den Abteilungen'); $check(rundmail_parse_group($pdo, 'department:99999') === null && rundmail_parse_group($pdo, 'x') === null && rundmail_parse_group($pdo, 'open')['type'] === 'open', 'Gruppenschlüssel werden geprüft'); // Prüfungen $check(rundmail_validate('', 'Text') !== null && rundmail_validate('Betreff', '') !== null && rundmail_validate("A\nB", 'x') !== null && rundmail_validate(str_repeat('x', 151), 'x') !== null, 'Betreff und Text werden geprüft'); $check(!rundmail_create($pdo, $anna, 'department:' . $d2, 'Hallo', 'Text')['ok'], 'Mitarbeiter können nicht an fremde Abteilungen senden'); $check(!rundmail_create($pdo, $chef, 'open', '', 'Text')['ok'], 'ohne Betreff kein Versand'); $check(!rundmail_create($pdo, $chef, 'organization:99999', 'Hallo', 'Text')['ok'], 'unbekannte Organisation'); // Rendern [$subj, $html] = rundmail_render(['subject' => 'Wartung', 'body' => "Hallo {name},\nfett"], 'Erika