Files
deskhub/DeskHub-Webseite/tests/catalog_preview.php
T
2026-10-16 19:12:24 +00:00

177 lines
12 KiB
PHP

<?php
declare(strict_types=1);
/** Prüft die Mini-Vorschau im Katalog: Adresse aus dem Katalog, nur gleiche Domain, nur echte Bilder, Zwischenspeicher. */
putenv('DESKHUB_TESTING=1');
putenv('DESKHUB_CATALOG_DEFAULTS=' . __DIR__ . '/fixtures/no-catalog.php');
putenv('DESKHUB_SQLITE_PATH=:memory:');
putenv('PLUGIN_SITE_CONFIG=' . sys_get_temp_dir() . '/gibtesnicht.php');
putenv('PLUGIN_SITE_STORAGE=' . ($tmp = sys_get_temp_dir() . '/dh-prev-' . bin2hex(random_bytes(4))));
putenv('PLUGIN_SITE_BASE_URL=https://plugins.example.test');
require_once dirname(__DIR__) . '/includes/bootstrap.php';
require_once __DIR__ . '/fixtures/paths.php';
$siteDir = website_dir_or_skip('catalog_preview');
require_once $siteDir . '/inc/lib.php';
$failures = [];
$total = 0;
$check = function (bool $ok, string $what) use (&$failures, &$total): void {
$total++;
if (!$ok) $failures[] = $what;
};
// Ein echtes PNG (1x1) als Vorschau
$png = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==');
$pngFile = tempnam(sys_get_temp_dir(), 'pv') . '.png';
file_put_contents($pngFile, $png);
$z = tempnam(sys_get_temp_dir(), 'pk') . '.zip';
$zip = new ZipArchive();
$zip->open($z, ZipArchive::CREATE);
$zip->addFromString('theme.css', "/*\n * Version: 1.0.0\n */\na{}");
$zip->close();
// Webseite: Vorschau-Adresse im Katalog nur bei Einträgen mit Bild
$check(site_publish('theme', $z, ['slug' => 'mitbild', 'name' => 'Mit Bild'], $pngFile, 'vorschau.png')['ok'], 'Theme mit Vorschaubild wird veröffentlicht');
$zip2 = tempnam(sys_get_temp_dir(), 'pk') . '.zip';
$zz = new ZipArchive(); $zz->open($zip2, ZipArchive::CREATE); $zz->addFromString('theme.css', "/*\n * Version: 1.0.0\n */\nb{}"); $zz->close();
$check(site_publish('theme', $zip2, ['slug' => 'ohnebild', 'name' => 'Ohne Bild'])['ok'], 'Theme ohne Vorschaubild wird veröffentlicht');
$cat = site_catalog('theme');
$byslug = array_column($cat['themes'], null, 'slug');
$check(isset($byslug['mitbild']['previewUrl']) && str_starts_with($byslug['mitbild']['previewUrl'], 'https://plugins.example.test/preview.php?file=') && !isset($byslug['ohnebild']['previewUrl']), 'Katalog nennt die Vorschau-Adresse nur für Einträge mit Bild');
// DeskHub: Adresse prüfen
$base = 'https://plugins.example.test/catalog.php?type=themes';
$check(catalog_preview_url($base, 'https://plugins.example.test/preview.php?file=a.png') !== '', 'Vorschau auf derselben Domain wird übernommen');
$check(catalog_preview_url($base, 'https://anderer.example.test/a.png') === '', 'Vorschau auf einer anderen Domain wird verworfen');
$check(catalog_preview_url($base, 'http://plugins.example.test/a.png') === '' && catalog_preview_url($base, '') === '', 'http und leere Adressen werden verworfen');
// DeskHub: Katalog lesen, Bild laden, prüfen, zwischenspeichern
set_setting('theme_catalog_url', $base);
$requests = [];
$serve = ['body' => $png];
$GLOBALS['deskhub_http_get'] = function (string $url, int $max) use ($cat, &$requests, &$serve): array {
$requests[] = $url;
if (str_contains($url, 'catalog.php')) return [200, json_encode($cat), null];
if (str_contains($url, 'preview.php')) return [200, $serve['body'], null];
return [404, null, 'HTTP 404'];
};
$tc = theme_catalog_fetch();
$by = array_column($tc['themes'], null, 'slug');
$check(($by['mitbild']['preview'] ?? '') !== '' && ($by['ohnebild']['preview'] ?? 'x') === '', 'DeskHub übernimmt die Vorschau-Adresse aus dem Theme-Katalog');
foreach (glob(sys_get_temp_dir() . '/deskhub_preview_*.bin') ?: [] as $f) @unlink($f);
$img = catalog_preview_image('theme', 'mitbild');
$check($img !== null && $img['mime'] === 'image/png' && $img['data'] === $png, 'Vorschaubild wird geladen und mit dem richtigen Typ geliefert');
$n = count($requests);
$img2 = catalog_preview_image('theme', 'mitbild');
$check($img2 !== null && count($requests) === $n, 'zweiter Aufruf kommt aus dem Zwischenspeicher (kein neuer Abruf)');
$check(catalog_preview_image('theme', 'ohnebild') === null, 'Eintrag ohne Vorschau liefert kein Bild');
$check(catalog_preview_image('theme', 'gibtesnicht') === null && catalog_preview_image('theme', '../x') === null && catalog_preview_image('auto', 'mitbild') === null, 'unbekannte Einträge, Pfadtricks und falsche Arten liefern nichts');
foreach (glob(sys_get_temp_dir() . '/deskhub_preview_*.bin') ?: [] as $f) @unlink($f);
$serve['body'] = '<html><script>alert(1)</script></html>';
$check(catalog_preview_image('theme', 'mitbild') === null, 'eine Datei, die kein Bild ist, wird nicht ausgeliefert');
foreach (glob(sys_get_temp_dir() . '/deskhub_preview_*.bin') ?: [] as $f) @unlink($f);
$serve['body'] = "GIF89a" . str_repeat("\0", 30);
$check(catalog_preview_image('theme', 'mitbild') === null, 'andere Bildtypen (z. B. GIF) werden nicht ausgeliefert');
// Ein fehlgeschlagener Abruf wird nur 5 Minuten gemerkt (früher einen Tag): Sobald das Bild da ist, erscheint es
foreach (glob(sys_get_temp_dir() . '/deskhub_preview_*.bin') ?: [] as $f) @unlink($f);
$serve['body'] = 'kein Bild';
$check(catalog_preview_image('theme', 'mitbild') === null, 'Abruf ohne gültiges Bild liefert nichts');
$serve['body'] = $png;
$check(catalog_preview_image('theme', 'mitbild') === null, 'der Fehlschlag wird kurz gemerkt (kein Dauerabruf bei kaputtem Bild)');
foreach (glob(sys_get_temp_dir() . '/deskhub_preview_*.bin') ?: [] as $f) touch($f, time() - 400);
$check(catalog_preview_image('theme', 'mitbild') !== null, 'nach 5 Minuten wird erneut geladen und das Bild erscheint');
foreach (glob(sys_get_temp_dir() . '/deskhub_preview_*.bin') ?: [] as $f) touch($f, time() - 400);
$check(catalog_preview_image('theme', 'mitbild') !== null, 'ein erfolgreich geladenes Bild bleibt länger als 5 Minuten im Zwischenspeicher');
// Installierte Themes: Vorschau aus dem Katalog (gleicher Name) oder aus preview.png im Theme-Ordner
$themesDir = sys_get_temp_dir() . '/dh-thumb-themes-' . bin2hex(random_bytes(3));
mkdir($themesDir, 0755, true);
putenv('DESKHUB_THEMES_DIR=' . $themesDir);
$zt = tempnam(sys_get_temp_dir(), 'th') . '.zip';
$q = new ZipArchive(); $q->open($zt, ZipArchive::CREATE); $q->addFromString('theme.css', "/*
* Version: 1.0.0
*/
a{}"); $q->addFromString('preview.png', $png); $q->close();
$inst = install_custom_theme_archive($zt, 'Mit Bild');
$ohne = tempnam(sys_get_temp_dir(), 'th') . '.zip';
$q = new ZipArchive(); $q->open($ohne, ZipArchive::CREATE); $q->addFromString('theme.css', "/*
* Version: 1.0.0
*/
c{}"); $q->close();
$inst2 = install_custom_theme_archive($ohne, 'Ganz Ohne');
$catThemes = [['slug' => 'mitbild', 'name' => 'Mit-Bild', 'preview' => 'https://plugins.example.test/preview.php?file=a.png']];
$p1 = theme_installed_preview(['id' => $inst['id'], 'name' => 'Mit Bild'], $catThemes);
$check($p1 && $p1['kind'] === 'catalog' && $p1['slug'] === 'mitbild', 'installiertes Theme bekommt die Vorschau aus dem Katalog (Name "Mit Bild" = "Mit-Bild")');
$p2 = theme_installed_preview(['id' => $inst['id'], 'name' => 'Mit Bild'], []);
$check($p2 && $p2['kind'] === 'file' && str_ends_with($p2['url'], '/preview.png'), 'ohne Katalog gilt die preview.png aus dem Theme-Ordner');
$check(theme_installed_preview(['id' => $inst2['id'], 'name' => 'Ganz Ohne'], $catThemes) === null, 'ohne Katalogbild und ohne preview.png gibt es keine Vorschau');
$check(str_contains(installed_theme_thumb(['id' => $inst2['id'], 'name' => 'Ganz Ohne'], []), '>G<'), 'Platzhalter zeigt den Anfangsbuchstaben');
$check(str_contains(installed_theme_thumb(['id' => $inst['id'], 'name' => 'Mit Bild'], $catThemes), 'catalog_preview.php?kind=theme&amp;slug=mitbild'), 'Mini-Vorschau verweist auf die Auslieferung durch DeskHub');
$rmT = function (string $d) use (&$rmT) { foreach (scandir($d) ?: [] as $e) { if ($e === '.' || $e === '..') continue; is_dir("$d/$e") ? $rmT("$d/$e") : @unlink("$d/$e"); } @rmdir($d); };
$rmT($themesDir);
putenv('DESKHUB_THEMES_DIR');
// Platzhalter und Bild-Markup
$check(str_contains(catalog_thumb('theme', ['slug' => 'mitbild', 'name' => 'Mit Bild', 'preview' => 'https://x']), 'catalog_preview.php?kind=theme&amp;slug=mitbild'), 'Mini-Vorschau zeigt das Bild über die DeskHub-Adresse');
$ph = catalog_thumb('plugin', ['slug' => 'x', 'name' => '<script>', 'preview' => '']);
$check(str_contains($ph, '&lt;') && !str_contains($ph, '<script>') && str_contains($ph, 'aria-hidden'), 'Platzhalter zeigt den Anfangsbuchstaben und maskiert Sonderzeichen');
// Automatisches Laden: Zwischenspeicher, Hintergrundaktualisierung, Update-Hinweis
set_setting('theme_catalog_cache', '');
$before = count($requests);
$GLOBALS['deskhub_catalog_defaults'] = ['plugins_url' => 'https://plugins.example.test/catalog.php?type=plugins', 'themes_url' => $base, 'publisher_key' => 'x'];
set_setting('plugin_catalog_cache', '');
$pluginCat = ['plugins' => [['slug' => 'tagesbericht', 'name' => 'Tagesbericht', 'version' => '1.2.0', 'description' => '', 'author' => '', 'permissions' => [], 'downloadUrl' => 'https://plugins.example.test/d', 'sha256' => str_repeat('a', 64), 'signature' => '']]];
$GLOBALS['deskhub_http_get'] = function (string $url, int $max) use ($cat, $pluginCat, &$requests): array {
$requests[] = $url;
if (str_contains($url, 'type=plugins')) return [200, json_encode($pluginCat), null];
if (str_contains($url, 'catalog.php')) return [200, json_encode($cat), null];
return [404, null, 'HTTP 404'];
};
$t1 = theme_catalog_cached();
$n1 = count($requests);
$t2 = theme_catalog_cached();
$check($t1['ok'] && $t2['ok'] && count($requests) === $n1 && $n1 === $before + 1, 'Theme-Katalog wird beim ersten Aufruf geladen und danach aus dem Zwischenspeicher gezeigt');
$check(plugin_updates_available($pdo = get_db()) === [], 'ohne installierte Plugins gibt es keine Update-Hinweise');
catalog_refresh_background();
$cached = json_decode((string) get_setting('plugin_catalog_cache', ''), true);
$check(is_array($cached) && count($cached['plugins']) === 1, 'Hintergrundaktualisierung füllt den Plugin-Katalog ohne Seitenaufruf');
$after = count($requests);
catalog_refresh_background();
$check(count($requests) === $after + 2, 'jede Hintergrundaktualisierung holt beide Kataloge neu');
$pdo->exec("INSERT INTO plugins (slug, name, version, is_active, installed_at) VALUES ('tagesbericht', 'Tagesbericht', '1.0.0', 0, datetime('now'))");
$upd = plugin_updates_available($pdo);
$check(count($upd) === 1 && $upd[0]['installed'] === '1.0.0' && $upd[0]['available'] === '1.2.0', 'neuere Version im Katalog wird als Update gemeldet');
$pdo->exec("UPDATE plugins SET version = '1.2.0' WHERE slug = 'tagesbericht'");
$check(plugin_updates_available($pdo) === [], 'aktuelle Plugins werden nicht gemeldet');
$before = count($requests);
plugin_updates_available($pdo);
$check(count($requests) === $before, 'der Update-Hinweis fragt nie den Server ab (nur den Zwischenspeicher)');
// Fällt der Katalog-Server aus, bleibt der alte Stand erhalten
$GLOBALS['deskhub_http_get'] = fn(string $url, int $max): array => [0, null, 'Verbindung fehlgeschlagen'];
catalog_refresh_background();
$kept = json_decode((string) get_setting('plugin_catalog_cache', ''), true);
$check(is_array($kept) && count($kept['plugins']) === 1, 'bei Serverausfall bleibt der zuletzt geladene Katalog erhalten');
unset($GLOBALS['deskhub_catalog_defaults']);
unset($GLOBALS['deskhub_http_get']);
foreach (glob(sys_get_temp_dir() . '/deskhub_preview_*.bin') ?: [] as $f) @unlink($f);
$rm = function (string $d) use (&$rm) {
foreach (scandir($d) ?: [] as $e) { if ($e === '.' || $e === '..') continue; is_dir("$d/$e") ? $rm("$d/$e") : @unlink("$d/$e"); }
@rmdir($d);
};
$rm($tmp);
if ($failures) {
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
exit(1);
}
echo "Catalog preview checks passed ({$total} Prüfungen).\n";