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