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'] = '';
$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&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&slug=mitbild'), 'Mini-Vorschau zeigt das Bild über die DeskHub-Adresse');
$ph = catalog_thumb('plugin', ['slug' => 'x', 'name' => '