From a86036c657e37a0a5b80fff337dc09b66fa74c02 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Wed, 28 Oct 2026 07:54:52 +0000 Subject: [PATCH] Upload via GUI (346 Dateien) --- .../tests/fixtures/smtp_server.php | 27 +++++ DeskHub-Webseite/tests/i18n_plugin_langs.php | 102 +++++++++++++++++ DeskHub-Webseite/tests/mail_queue.php | 105 ++++++++++++++++++ DeskHub-Webseite/tests/plugin_cron_job.php | 8 ++ DeskHub-Webseite/tests/plugin_nas_backup.php | 1 + DeskHub-Webseite/tests/plugin_spamfilter.php | 11 ++ DeskHub-Webseite/tests/plugin_website.php | 8 +- 7 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 DeskHub-Webseite/tests/fixtures/smtp_server.php create mode 100644 DeskHub-Webseite/tests/i18n_plugin_langs.php create mode 100644 DeskHub-Webseite/tests/mail_queue.php diff --git a/DeskHub-Webseite/tests/fixtures/smtp_server.php b/DeskHub-Webseite/tests/fixtures/smtp_server.php new file mode 100644 index 00000000..81fae250 --- /dev/null +++ b/DeskHub-Webseite/tests/fixtures/smtp_server.php @@ -0,0 +1,27 @@ + . Nimmt jede Mail an und schreibt "MAIL " 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); +} diff --git a/DeskHub-Webseite/tests/i18n_plugin_langs.php b/DeskHub-Webseite/tests/i18n_plugin_langs.php new file mode 100644 index 00000000..f73fdf5a --- /dev/null +++ b/DeskHub-Webseite/tests/i18n_plugin_langs.php @@ -0,0 +1,102 @@ +.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 = ''; + +// 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' => " $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' => " $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('

Plugin-Text

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

Plugin-Text

Hallo, Anna

Gemeinsamer Text

Nicht übersetzt

'); +$check(str_contains($page, '

Plugin text

') && 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"; diff --git a/DeskHub-Webseite/tests/mail_queue.php b/DeskHub-Webseite/tests/mail_queue.php new file mode 100644 index 00000000..ece99b2a --- /dev/null +++ b/DeskHub-Webseite/tests/mail_queue.php @@ -0,0 +1,105 @@ + (int) $pdo->query('SELECT COUNT(*) FROM mail_queue')->fetchColumn(); + +// 1) Server nicht erreichbar: Mail wird gespeichert +$ok = send_mail('kunde@firma.example', 'Ihre Antwort', '

Hallo

'); +$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'] === '

Hallo

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

t

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

x

'); +$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', '

a

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

r

','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"; diff --git a/DeskHub-Webseite/tests/plugin_cron_job.php b/DeskHub-Webseite/tests/plugin_cron_job.php index 0687d2b4..0686d726 100644 --- a/DeskHub-Webseite/tests/plugin_cron_job.php +++ b/DeskHub-Webseite/tests/plugin_cron_job.php @@ -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); diff --git a/DeskHub-Webseite/tests/plugin_nas_backup.php b/DeskHub-Webseite/tests/plugin_nas_backup.php index d1ce621b..fb2f962b 100644 --- a/DeskHub-Webseite/tests/plugin_nas_backup.php +++ b/DeskHub-Webseite/tests/plugin_nas_backup.php @@ -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(); diff --git a/DeskHub-Webseite/tests/plugin_spamfilter.php b/DeskHub-Webseite/tests/plugin_spamfilter.php index cd9edb71..7ce767e4 100644 --- a/DeskHub-Webseite/tests/plugin_spamfilter.php +++ b/DeskHub-Webseite/tests/plugin_spamfilter.php @@ -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); diff --git a/DeskHub-Webseite/tests/plugin_website.php b/DeskHub-Webseite/tests/plugin_website.php index 0bfdfcf7..ed3d6e47 100644 --- a/DeskHub-Webseite/tests/plugin_website.php +++ b/DeskHub-Webseite/tests/plugin_website.php @@ -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('/
/', $body, $dm) >= 1, 'die Verwaltung zeigt Entfernen-Schaltflächen'); + [$s, $body] = $http('GET', '/admin.php?tab=packages', ['jar' => $jar]); + $check(preg_match_all('//', $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');