Upload via GUI (346 Dateien)
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// Minimaler SMTP-Server für Tests: php smtp_server.php <port> <logdatei>. Nimmt jede Mail an und schreibt "MAIL <empfänger>" ins Log.
|
||||
$port = (int) ($argv[1] ?? 0);
|
||||
$log = (string) ($argv[2] ?? '');
|
||||
$srv = stream_socket_server("tcp://127.0.0.1:$port", $errno, $errstr);
|
||||
if (!$srv) { fwrite(STDERR, "Server nicht gestartet: $errstr\n"); exit(1); }
|
||||
file_put_contents($log, "READY\n", FILE_APPEND);
|
||||
while ($c = @stream_socket_accept($srv, 120)) {
|
||||
stream_set_timeout($c, 10);
|
||||
fwrite($c, "220 test ESMTP\r\n");
|
||||
$to = '';
|
||||
$data = false;
|
||||
while (($line = fgets($c)) !== false) {
|
||||
$l = rtrim($line, "\r\n");
|
||||
if ($data) {
|
||||
if ($l === '.') { $data = false; file_put_contents($log, "MAIL $to\n", FILE_APPEND); fwrite($c, "250 queued\r\n"); }
|
||||
continue;
|
||||
}
|
||||
$u = strtoupper($l);
|
||||
if (str_starts_with($u, 'EHLO')) fwrite($c, "250 test\r\n");
|
||||
elseif (str_starts_with($u, 'RCPT TO:')) { $to = trim(substr($l, 8), ' <>'); fwrite($c, "250 ok\r\n"); }
|
||||
elseif ($u === 'DATA') { $data = true; fwrite($c, "354 go\r\n"); }
|
||||
elseif ($u === 'QUIT') { fwrite($c, "221 bye\r\n"); break; }
|
||||
else fwrite($c, "250 ok\r\n");
|
||||
}
|
||||
fclose($c);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Prüft die Übersetzungen, die Plugins selbst mitbringen (lang/<Sprachcode>.json): Sie gelten nur für aktive Plugins und nur in der
|
||||
* gewählten Sprache, das Sprachpaket hat bei gleichem Text Vorrang, Muster mit {} funktionieren, ungültige Dateien stören nicht,
|
||||
* und die Übersicht unter Sprachen kennt sie.
|
||||
*/
|
||||
|
||||
$pluginDir = sys_get_temp_dir() . '/deskhub-i18n-pl-' . 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();
|
||||
$mkZip = function (array $files): string {
|
||||
$path = sys_get_temp_dir() . '/plp-' . bin2hex(random_bytes(3)) . '.zip';
|
||||
$z = new ZipArchive();
|
||||
$z->open($path, ZipArchive::CREATE);
|
||||
foreach ($files as $name => $content) $z->addFromString($name, $content);
|
||||
$z->close();
|
||||
return $path;
|
||||
};
|
||||
$flag = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 2"><rect width="3" height="2" fill="#009246"/></svg>';
|
||||
|
||||
// Sprachpaket "xx" mit einem Text, der auch im Plugin steht (Sprachpaket gewinnt)
|
||||
$pack = $mkZip([
|
||||
'plugin.json' => json_encode(['slug' => 'lang-xx', 'name' => 'Testsprache', 'version' => '1.0.0', 'entry' => 'plugin.php', 'permissions' => [], 'language' => ['code' => 'xx', 'name' => 'Testsprache', 'flag' => 'flag.svg', 'strings' => 'strings.json']]),
|
||||
'plugin.php' => "<?php\n",
|
||||
'flag.svg' => $flag,
|
||||
'strings.json' => json_encode(['Gemeinsamer Text' => 'Shared (pack)', 'Nur im Sprachpaket' => 'Only in pack'], JSON_UNESCAPED_UNICODE),
|
||||
]);
|
||||
$check(plugin_install_zip($pdo, $pack)['ok'] && plugin_set_active($pdo, 'lang-xx', true, [])['ok'], 'Sprachpaket installiert und aktiv');
|
||||
|
||||
// Plugin mit eigenen Übersetzungen
|
||||
$mkPlugin = function (string $slug, array $langFiles) use ($mkZip, $pdo): array {
|
||||
$files = [
|
||||
'plugin.json' => json_encode(['slug' => $slug, 'name' => 'Plugin ' . $slug, 'version' => '1.0.0', 'entry' => 'plugin.php', 'permissions' => []]),
|
||||
'plugin.php' => "<?php\n",
|
||||
];
|
||||
foreach ($langFiles as $name => $content) $files['lang/' . $name] = $content;
|
||||
return plugin_install_zip($pdo, $mkZip($files));
|
||||
};
|
||||
$strs = fn(array $a) => json_encode($a, JSON_UNESCAPED_UNICODE);
|
||||
$r1 = $mkPlugin('probe-a', [
|
||||
'xx.json' => $strs(['_info' => 'Test', 'Plugin-Text' => 'Plugin text', 'Gemeinsamer Text' => 'Shared (plugin)', '{#} Treffer im Plugin' => '{} hits in plugin', 'Hallo, {}' => 'Hi, {}']),
|
||||
'yy.json' => $strs(['Plugin-Text' => 'Texte du plugin']),
|
||||
'Kaputt.json' => '{}',
|
||||
]);
|
||||
$r2 = $mkPlugin('probe-b', ['xx.json' => '{ das ist kein JSON', 'zz.json' => $strs(['Nur b' => 'Only b'])]);
|
||||
$check($r1['ok'] && $r2['ok'], 'Test-Plugins lassen sich installieren: ' . json_encode([$r1, $r2]));
|
||||
$tr = fn(string $html, string $code = 'xx') => i18n_translate_html($html, $code);
|
||||
|
||||
// Solange das Plugin nicht aktiv ist, gilt seine Datei nicht
|
||||
i18n_languages(true);
|
||||
$check(!str_contains($tr('<p>Plugin-Text</p>'), 'Plugin text'), 'inaktives Plugin: seine Übersetzungen gelten nicht');
|
||||
$check(plugin_set_active($pdo, 'probe-a', true, [])['ok'], 'Plugin lässt sich aktivieren');
|
||||
$check(plugin_set_active($pdo, 'probe-b', true, [])['ok'], 'zweites Plugin lässt sich aktivieren');
|
||||
// Wörterbuch direkt aufbauen (i18n_dictionary() merkt sich je Anfrage ein Ergebnis)
|
||||
$dict = ['exact' => [], 'patterns' => [], 'count' => 0];
|
||||
foreach (i18n_plugin_string_files('xx') as $f) i18n_load_strings_file($f, $dict);
|
||||
i18n_load_strings_file((string) (i18n_languages(true)['xx']['strings'] ?? ''), $dict);
|
||||
$check(isset($dict['exact']['Plugin-Text']) && $dict['exact']['Plugin-Text'] === 'Plugin text', 'Text aus lang/xx.json eines aktiven Plugins wird geladen');
|
||||
$check(($dict['exact']['Gemeinsamer Text'] ?? '') === 'Shared (pack)', 'Bei gleichem Text gewinnt das Sprachpaket');
|
||||
$check(($dict['exact']['Nur im Sprachpaket'] ?? '') === 'Only in pack', 'Texte nur im Sprachpaket bleiben erhalten');
|
||||
$check(i18n_lookup('7 Treffer im Plugin', $dict) === '7 hits in plugin', 'Muster mit {#} aus dem Plugin funktioniert: ' . var_export(i18n_lookup('7 Treffer im Plugin', $dict), true));
|
||||
$check(i18n_lookup('Hallo, Anna', $dict) === 'Hi, Anna', 'Muster mit {} aus dem Plugin funktioniert');
|
||||
$check(!isset($dict['exact']['_info']), 'Schlüssel mit Unterstrich („_info“) werden ignoriert');
|
||||
$files = i18n_plugin_string_files('xx');
|
||||
$check(array_keys($files) === ['probe-a', 'probe-b'] && ($dict['count'] ?? 0) >= 5, 'beide Plugins liefern ihre Datei; die kaputte Datei von probe-b stört nicht');
|
||||
$check(i18n_plugin_string_files('yy') === ['probe-a' => $pluginDir . '/probe-a/lang/yy.json'], 'eine andere Sprache bekommt nur die passenden Dateien');
|
||||
$check(i18n_plugin_string_files('de') === [] && i18n_plugin_string_files('../etc') === [] && i18n_plugin_string_files('XX') === [], 'Deutsch, ungültige Codes und Großbuchstaben liefern nichts');
|
||||
|
||||
// Übersicht unter Sprachen
|
||||
$ov = i18n_plugin_language_overview();
|
||||
$check(($ov['probe-a']['xx'] ?? null) === 4 && ($ov['probe-a']['yy'] ?? null) === 1 && ($ov['probe-b']['xx'] ?? null) === 0 && ($ov['probe-b']['zz'] ?? null) === 1, 'Übersicht zählt die Texte je Plugin und Sprache: ' . json_encode($ov));
|
||||
$check(!isset($ov['probe-a']['Kaputt']) && !isset($ov['probe-a']['kaputt']), 'Dateien mit ungültigem Sprachcode im Namen werden nicht aufgeführt');
|
||||
|
||||
// Plugin deaktivieren: Datei gilt nicht mehr
|
||||
plugin_set_active($pdo, 'probe-a', false, []);
|
||||
$check(array_keys(i18n_plugin_string_files('xx')) === ['probe-b'], 'deaktiviertes Plugin liefert keine Übersetzungen mehr');
|
||||
// Fertige Seite: Plugin-Text und Muster werden übersetzt
|
||||
plugin_set_active($pdo, 'probe-a', true, []);
|
||||
i18n_dictionary('xx', true);
|
||||
$page = $tr('<h1>Plugin-Text</h1><p>Hallo, Anna</p><p>Gemeinsamer Text</p><p>Nicht übersetzt</p>');
|
||||
$check(str_contains($page, '<h1>Plugin text</h1>') && str_contains($page, 'Hi, Anna') && str_contains($page, 'Shared (pack)') && str_contains($page, 'Nicht übersetzt'), 'fertige Seite wird mit den Texten der Plugins übersetzt: ' . $page);
|
||||
|
||||
foreach (scandir($pluginDir) ?: [] as $entry) {
|
||||
if ($entry === '.' || $entry === '..') continue;
|
||||
is_dir($pluginDir . '/' . $entry) ? plugin_rmdir($pluginDir . '/' . $entry) : @unlink($pluginDir . '/' . $entry);
|
||||
}
|
||||
@rmdir($pluginDir);
|
||||
if ($failures) {
|
||||
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
||||
exit(1);
|
||||
}
|
||||
echo "Plugin language files checks passed.\n";
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Prüft die Mail-Warteschlange: Eine fehlgeschlagene Mail wird gespeichert (Test-Mails nicht), später erneut versucht (mit wachsenden
|
||||
* Abständen), nach Ende der Störung gesendet, nach zu vielen Versuchen aufgegeben, nie doppelt gesendet; dazu Ausschalter,
|
||||
* Gesundheitsanzeige und Cron-Job.
|
||||
* Der SMTP-Server wird durch tests/fixtures/smtp_server.php ersetzt: erst nicht erreichbar, dann gestartet.
|
||||
*/
|
||||
|
||||
$db = sys_get_temp_dir() . '/deskhub-mq-' . bin2hex(random_bytes(4)) . '.sqlite';
|
||||
putenv('DESKHUB_TESTING=1');
|
||||
putenv('DESKHUB_SQLITE_PATH=' . $db);
|
||||
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
||||
|
||||
$failures = [];
|
||||
$check = function (bool $ok, string $what) use (&$failures): void { if (!$ok) $failures[] = $what; };
|
||||
$pdo = get_db();
|
||||
$port = random_int(33000, 33499);
|
||||
$log = sys_get_temp_dir() . '/deskhub-mq-smtp-' . bin2hex(random_bytes(4)) . '.log';
|
||||
set_setting('mail_enabled', '1');
|
||||
set_setting('smtp_host', '127.0.0.1');
|
||||
set_setting('smtp_port', (string) $port);
|
||||
set_setting('smtp_encryption', 'none');
|
||||
set_setting('smtp_username', '');
|
||||
set_setting('smtp_from_email', 'support@firma.example');
|
||||
$count = fn(): int => (int) $pdo->query('SELECT COUNT(*) FROM mail_queue')->fetchColumn();
|
||||
|
||||
// 1) Server nicht erreichbar: Mail wird gespeichert
|
||||
$ok = send_mail('kunde@firma.example', 'Ihre Antwort', '<p>Hallo</p>');
|
||||
$check($ok === false && mail_last_queued() === true, 'Fehlgeschlagene Mail kommt in die Warteschlange');
|
||||
$q = $pdo->query('SELECT * FROM mail_queue')->fetchAll();
|
||||
$check(count($q) === 1 && $q[0]['status'] === 'pending' && $q[0]['to_email'] === 'kunde@firma.example' && (int) $q[0]['attempts'] === 1 && $q[0]['body'] === '<p>Hallo</p>', 'Eintrag enthält Empfänger, Text und den ersten Versuch');
|
||||
$check($q && (string) $q[0]['last_error'] !== '', 'Der Fehlergrund wird gespeichert');
|
||||
$check($q && strtotime((string) $q[0]['next_attempt_at']) > time() + 240, 'Nächster Versuch frühestens in etwa 5 Minuten');
|
||||
|
||||
// Test-Mails und .invalid-Adressen werden nicht eingereiht
|
||||
$ok2 = send_mail('test@firma.example', 'Test', '<p>t</p>', null, false);
|
||||
$check($ok2 === false && mail_last_queued() === false && $count() === 1, 'Test-Mails (queue=false) kommen nicht in die Warteschlange');
|
||||
$check(send_mail('bot@x.invalid', 'x', 'x') === true && $count() === 1, 'Adressen mit .invalid werden übersprungen');
|
||||
|
||||
// 2) Noch nicht fällig: nichts passiert
|
||||
$r = mail_queue_run($pdo);
|
||||
$check($r['sent'] === 0 && $r['failed'] === 0, 'Noch nicht fällige Mails werden nicht versucht');
|
||||
|
||||
// 3) Fällig, Server weiter tot: nächster Versuch später (wachsender Abstand)
|
||||
$pdo->exec("UPDATE mail_queue SET next_attempt_at = '2000-01-01 00:00:00'");
|
||||
$r = mail_queue_run($pdo);
|
||||
$row = $pdo->query('SELECT * FROM mail_queue')->fetch();
|
||||
$check($r['retry'] === 1 && (int) $row['attempts'] === 2 && $row['status'] === 'pending', 'Zweiter Fehlversuch bleibt in der Warteschlange');
|
||||
$check(strtotime((string) $row['next_attempt_at']) > time() + 9 * 60 && strtotime((string) $row['next_attempt_at']) < time() + 11 * 60, 'Abstand wächst auf 10 Minuten');
|
||||
|
||||
// Gesundheitsanzeige
|
||||
$check(in_array('mail_queue_pending', array_column(system_health($pdo), 'code'), true), 'Statusanzeige nennt die wartenden Mails');
|
||||
|
||||
// 4) Ausschalter
|
||||
set_setting('mail_queue_enabled', '0');
|
||||
send_mail('zweiter@firma.example', 'Aus', '<p>x</p>');
|
||||
$check($count() === 1, 'Ausgeschaltete Warteschlange nimmt nichts mehr an');
|
||||
set_setting('mail_queue_enabled', '1');
|
||||
|
||||
// 5) Server wieder da: Mail geht raus, genau einmal
|
||||
$proc = proc_open([PHP_BINARY, __DIR__ . '/fixtures/smtp_server.php', (string) $port, $log], [1 => ['file', sys_get_temp_dir() . '/deskhub-mq-out.txt', 'w'], 2 => ['file', sys_get_temp_dir() . '/deskhub-mq-err.txt', 'w']], $pipes);
|
||||
for ($i = 0; $i < 50 && !is_file($log); $i++) usleep(100000);
|
||||
$pdo->exec("UPDATE mail_queue SET next_attempt_at = '2000-01-01 00:00:00'");
|
||||
$r = mail_queue_run($pdo);
|
||||
$row = $pdo->query('SELECT * FROM mail_queue')->fetch();
|
||||
$check($r['sent'] === 1 && $row['status'] === 'sent' && $row['sent_at'] !== null && $row['last_error'] === null, 'Nach Ende der Störung wird die Mail nachträglich gesendet');
|
||||
$r = mail_queue_run($pdo);
|
||||
$check($r['sent'] === 0, 'Eine gesendete Mail wird nicht noch einmal gesendet');
|
||||
$received = is_file($log) ? substr_count((string) file_get_contents($log), 'MAIL kunde@firma.example') : 0;
|
||||
$check($received === 1, 'Der Server hat die Mail genau einmal erhalten (erhalten: ' . $received . ')');
|
||||
$check(!in_array('mail_queue_pending', array_column(system_health($pdo), 'code'), true), 'Statusanzeige ist wieder sauber');
|
||||
if (is_resource($proc)) { proc_terminate($proc); proc_close($proc); }
|
||||
usleep(300000);
|
||||
|
||||
// 6) Aufgeben nach zu vielen Versuchen und nach 48 Stunden
|
||||
$ins = $pdo->prepare("INSERT INTO mail_queue (to_email, subject, body, status, attempts, next_attempt_at, created_at) VALUES (?, 'S', '<p>a</p>', 'pending', ?, '2000-01-01 00:00:00', ?)");
|
||||
$ins->execute(['alt@firma.example', 9, db_now()]);
|
||||
$r = mail_queue_run($pdo);
|
||||
$check($r['gave_up'] === 1 && $pdo->query("SELECT status FROM mail_queue WHERE to_email = 'alt@firma.example'")->fetchColumn() === 'failed', 'Nach dem 10. Fehlversuch wird die Mail aufgegeben');
|
||||
$check(in_array('mail_queue_failed', array_column(system_health($pdo), 'code'), true), 'Statusanzeige nennt aufgegebene Mails');
|
||||
$ins->execute(['uralt@firma.example', 1, date('Y-m-d H:i:s', time() - 3 * 86400)]);
|
||||
mail_queue_run($pdo);
|
||||
$check($pdo->query("SELECT status FROM mail_queue WHERE to_email = 'uralt@firma.example'")->fetchColumn() === 'failed', 'Nach 48 Stunden wird die Mail aufgegeben');
|
||||
|
||||
// 7) Reservierung: Eine bereits reservierte (noch nicht fällige) Mail wird nicht bearbeitet
|
||||
$pdo->prepare("INSERT INTO mail_queue (to_email, subject, body, status, attempts, next_attempt_at, created_at) VALUES ('res@firma.example','Res','<p>r</p>','pending',1,?,?)")->execute([date('Y-m-d H:i:s', time() + 600), db_now()]);
|
||||
$r = mail_queue_run($pdo);
|
||||
$check($r['sent'] === 0 && $r['failed'] === 0, 'Eine reservierte Mail wird nicht doppelt bearbeitet');
|
||||
|
||||
// 8) Cron-Job und Menü
|
||||
require_once dirname(__DIR__) . '/cron/process_mail_queue.php';
|
||||
$res = run_process_mail_queue($pdo);
|
||||
$check(isset($res['log'][0]) && str_contains($res['log'][0], 'Mail-Warteschlange') && get_setting('cron_process_mail_queue_last_run', '') !== '', 'Cron-Job läuft und merkt sich den Zeitpunkt');
|
||||
$check(isset(pseudo_cron_job_definitions()['process_mail_queue']), 'Der eingebaute Hintergrundbetrieb kennt den Job');
|
||||
$check(str_contains((string) file_get_contents(dirname(__DIR__) . '/staff/_header.php'), '/staff/mail_queue.php'), 'Verwaltungsseite ist im Menü verlinkt');
|
||||
|
||||
@unlink($db);
|
||||
@unlink($log);
|
||||
if ($failures) {
|
||||
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
||||
exit(1);
|
||||
}
|
||||
echo "Mail queue checks passed.\n";
|
||||
@@ -43,6 +43,14 @@ $check($last !== '' && strtotime($last) > time() - 60, 'letzter Lauf ist vermerk
|
||||
$srcRow = $pdo->query("SELECT setting_key, setting_value FROM settings WHERE setting_key IN ('cron_run_plugins_source','cron_run_plugins_last_cli')")->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$check(($srcRow['cron_run_plugins_source'] ?? '') === 'cron' && strtotime((string) ($srcRow['cron_run_plugins_last_cli'] ?? '')) > time() - 60, 'der Lauf wird als „echter Cronjob“ vermerkt (Quelle und Zeit)');
|
||||
|
||||
// Der eingebaute Hintergrund-Betrieb hat die Minute schon belegt: Der Lauf wird übersprungen, der echte Cronjob gilt trotzdem als lebendig
|
||||
$pdo->prepare("DELETE FROM settings WHERE setting_key = 'cron_run_plugins_last_cli'")->execute();
|
||||
unset($out);
|
||||
exec("{$php} {$script} 2>&1", $out, $code);
|
||||
$lastCli = (string) $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'cron_run_plugins_last_cli'")->fetchColumn();
|
||||
$check(str_contains(implode("
|
||||
", $out), 'Übersprungen') && $lastCli !== '' && strtotime($lastCli) > time() - 60, 'auch ein übersprungener Cron-Lauf wird als „echter Cronjob lebt“ vermerkt');
|
||||
|
||||
// Eine Minute später: das Minuten-Ereignis kommt wieder, der 5-Minuten-Lauf noch nicht
|
||||
$setAgo = fn(string $key, int $sec) => $pdo->prepare('UPDATE settings SET setting_value = ? WHERE setting_key = ?')->execute([date('Y-m-d H:i:s', time() - $sec), $key]);
|
||||
$setAgo('cron_run_plugins_last_run', 70);
|
||||
|
||||
@@ -41,6 +41,7 @@ $zip->close();
|
||||
$r = plugin_install_zip($pdo, $zipPath);
|
||||
$check($r['ok'] && $r['slug'] === 'nas-backup', 'Plugin lässt sich installieren: ' . json_encode($r));
|
||||
$check(plugin_set_active($pdo, 'nas-backup', true, ['hooks.cron', 'ui.menu'])['ok'], 'Plugin lässt sich mit bestätigten Berechtigungen aktivieren');
|
||||
$GLOBALS['nasbk_sync'] = true; // Seitenaktionen laufen sonst erst nach dem Senden der Antwort; der Test braucht das Ergebnis sofort
|
||||
$state = &plugin_state();
|
||||
$state['booted'] = false;
|
||||
plugins_boot();
|
||||
|
||||
@@ -162,6 +162,17 @@ $check(plugin_filter('ticket.view.sidebar', '', ['ticketId' => 999999, 'ticket'
|
||||
$st = spamfilter_stats($pdo);
|
||||
$check($st['spam'] >= 2 && $st['ok'] >= 1 && $st['suspect'] >= 1, 'Kennzahlen zählen die Entscheidungen: ' . json_encode($st));
|
||||
|
||||
// Der Filter fand nichts (ok), das Ticket liegt aber anderswie als Spam ab (Sperrliste, Ticket-Regel, Postfach-Spamordner, von Hand):
|
||||
// Das Protokoll darf es nicht als „unauffällig“ führen
|
||||
$other = $mk('Anders', 'anders@harmlos.example', 'Freundliche Frage', 'Guten Tag, ich habe eine Frage zur Lieferung.', 'email');
|
||||
$before = spamfilter_stats($pdo);
|
||||
$check(spamfilter_handle_ticket($pdo, $other) === 'ok', 'harmloses Ticket wird vom Filter als „ok“ bewertet');
|
||||
$pdo->prepare("UPDATE tickets SET is_spam = 1, status = 'closed' WHERE id = ?")->execute([$other]);
|
||||
$after = spamfilter_stats($pdo);
|
||||
$check($after['other_spam'] === $before['other_spam'] + 1 && $after['ok'] === $before['ok'], 'Kennzahlen zählen ein anderweitig abgelegtes Ticket als „Spam abgelegt (nicht durch den Filter)“, nicht als unauffällig: ' . json_encode($after));
|
||||
$html = plugin_filter('ticket.view.sidebar', '', ['ticketId' => $other, 'ticket' => [], 'staff' => ['id' => 1, 'role' => 'admin']]);
|
||||
$check(str_contains($html, 'nicht durch den Filter') && !str_contains($html, 'unauffällig'), 'Seitenleiste nennt „als Spam abgelegt (nicht durch den Filter…)“ statt „unauffällig“');
|
||||
|
||||
foreach (scandir($pluginDir) ?: [] as $entry) {
|
||||
if ($entry === '.' || $entry === '..') continue;
|
||||
is_dir($pluginDir . '/' . $entry) ? plugin_rmdir($pluginDir . '/' . $entry) : @unlink($pluginDir . '/' . $entry);
|
||||
|
||||
@@ -201,7 +201,9 @@ try {
|
||||
[$s] = $http('POST', '/admin.php', ['jar' => $jar, 'data' => ['action' => 'login', 'user' => 'admin', 'password' => 'Sehr-Sicheres-Passwort-1']]);
|
||||
$check($s === 302, 'richtiges Passwort meldet an');
|
||||
[$s, $body] = $http('GET', '/admin.php', ['jar' => $jar]);
|
||||
$check($s === 200 && str_contains($body, 'Neues Paket veröffentlichen') && str_contains($body, 'Signierschlüssel eingerichtet'), 'Verwaltung nach der Anmeldung');
|
||||
$check($s === 200 && str_contains($body, 'Signierschlüssel') && str_contains($body, 'Eingerichtet'), 'Verwaltung nach der Anmeldung (Übersicht)');
|
||||
[$s, $body] = $http('GET', '/admin.php?tab=packages', ['jar' => $jar]);
|
||||
$check($s === 200 && str_contains($body, 'Neues Paket veröffentlichen'), 'Reiter „Pakete“ zeigt das Upload-Formular');
|
||||
preg_match('/name="csrf" value="([a-f0-9]+)"/', $body, $m);
|
||||
$csrf = $m[1] ?? '';
|
||||
[$s, $body] = $http('POST', '/admin.php', ['jar' => $jar, 'data' => ['action' => 'publish', 'type' => 'theme', 'csrf' => 'falsch', 'package' => new CURLFile($themeZip('2.0.0'), 'application/zip', 'x.zip')]]);
|
||||
@@ -215,8 +217,8 @@ try {
|
||||
[$s, $body] = $http('POST', '/admin.php', ['jar' => $jar, 'data' => ['action' => 'publish', 'type' => 'plugin', 'csrf' => $csrf, 'package' => new CURLFile($makeZip(['nur.txt' => 'x']), 'application/zip', 'k.zip')]]);
|
||||
$check(str_contains($body, 'plugin.json wurde im Paket nicht gefunden'), 'ungültiges Paket zeigt eine verständliche Meldung');
|
||||
// Löschen über die Verwaltung: Version entfernen, dann verschwindet auch der Eintrag samt Paketdatei und Katalogzeile
|
||||
[$s, $body] = $http('GET', '/admin.php', ['jar' => $jar]);
|
||||
$check(preg_match_all('/<form method="post" data-confirm="[^"]*"><input type="hidden" name="action" value="delete"><input type="hidden" name="csrf" value="[a-f0-9]+"><input type="hidden" name="id" value="(\d+)">/', $body, $dm) >= 1, 'die Verwaltung zeigt Entfernen-Schaltflächen');
|
||||
[$s, $body] = $http('GET', '/admin.php?tab=packages', ['jar' => $jar]);
|
||||
$check(preg_match_all('/<form method="post" class="inline" data-confirm="[^"]*"><input type="hidden" name="action" value="delete"><input type="hidden" name="csrf" value="[a-f0-9]+"><input type="hidden" name="id" value="(\d+)">/', $body, $dm) >= 1, 'die Verwaltung zeigt Entfernen-Schaltflächen');
|
||||
$abId = (int) site_db()->query("SELECT v.id FROM versions v JOIN items i ON i.id = v.item_id WHERE i.slug = 'abendrot'")->fetchColumn();
|
||||
$abFile = (string) site_db()->query("SELECT v.file FROM versions v JOIN items i ON i.id = v.item_id WHERE i.slug = 'abendrot'")->fetchColumn();
|
||||
$check($abId > 0 && is_file($tmp . '/storage/packages/' . $abFile), 'Paketdatei liegt vor dem Löschen im Speicher');
|
||||
|
||||
Reference in New Issue
Block a user