"/*\n * Author: Test Autor\n * Version: 2.3.1\n */\n:root { --primary: #123456; }", 'images/background.png' => 'not-a-real-image', ]); $theme = install_custom_theme_archive($validArchive, 'Test Theme', $themesDirectory); $themes = list_custom_themes($themesDirectory); assert_true(count($themes) === 1, 'Das installierte Theme wird nicht aufgelistet.'); assert_true($themes[0]['id'] === $theme['id'], 'Die Theme-ID stimmt nicht überein.'); assert_true($themes[0]['author'] === 'Test Autor', 'Der Theme-Autor wurde nicht aus theme.css übernommen.'); assert_true($themes[0]['theme_version'] === '2.3.1', 'Die Theme-Version wurde nicht aus theme.css übernommen.'); assert_true( is_file($themesDirectory . DIRECTORY_SEPARATOR . $theme['id'] . DIRECTORY_SEPARATOR . 'theme.css'), 'theme.css wurde nicht installiert.' ); $namedUpload = $archiveDirectory . DIRECTORY_SEPARATOR . 'My-Theme_Name.zip'; copy($validArchive, $namedUpload); $uploadedTheme = install_custom_theme_upload([ 'name' => 'My-Theme_Name.zip', 'tmp_name' => $namedUpload, 'size' => filesize($namedUpload), 'error' => UPLOAD_ERR_OK, ], $themesDirectory); assert_true($uploadedTheme['name'] === 'My Theme Name', 'Der Theme-Name wird nicht aus dem ZIP-Dateinamen übernommen.'); assert_true(!$uploadedTheme['replaced'], 'Der erste Upload wurde fälschlich als Aktualisierung markiert.'); $replacementArchive = $archiveDirectory . DIRECTORY_SEPARATOR . 'replacement.zip'; create_test_theme_archive($replacementArchive, [ 'theme.css' => ':root { --primary: #abcdef; }', ]); $replacementTheme = install_custom_theme_upload([ 'name' => 'My-Theme_Name.zip', 'tmp_name' => $replacementArchive, 'size' => filesize($replacementArchive), 'error' => UPLOAD_ERR_OK, ], $themesDirectory); assert_true($replacementTheme['replaced'], 'Der erneute Upload wurde nicht als Aktualisierung erkannt.'); assert_true($replacementTheme['id'] === $uploadedTheme['id'], 'Beim Aktualisieren wurde die Theme-ID geändert.'); assert_true( count(list_custom_themes($themesDirectory)) === 2, 'Der erneute Upload hat einen doppelten Listeneintrag erzeugt.' ); assert_true( file_get_contents( $themesDirectory . DIRECTORY_SEPARATOR . $replacementTheme['id'] . DIRECTORY_SEPARATOR . 'theme.css' ) === ':root { --primary: #abcdef; }', 'Die Theme-Dateien wurden beim erneuten Upload nicht ersetzt.' ); $traversalArchive = $archiveDirectory . DIRECTORY_SEPARATOR . 'traversal.zip'; create_test_theme_archive($traversalArchive, [ 'theme.css' => ':root {}', '../escape.css' => 'body {}', ]); assert_install_fails($traversalArchive, $themesDirectory, 'unsicheren Dateipfad'); assert_true(!file_exists($testDirectory . DIRECTORY_SEPARATOR . 'escape.css'), 'ZIP-Pfad hat das Zielverzeichnis verlassen.'); $executableArchive = $archiveDirectory . DIRECTORY_SEPARATOR . 'executable.zip'; create_test_theme_archive($executableArchive, [ 'theme.css' => ':root {}', 'payload.php' => ' 'image']); assert_install_fails($missingStylesheetArchive, $themesDirectory, 'fehlt die Datei theme.css'); assert_true(delete_custom_theme($replacementTheme['id'], $themesDirectory), 'Das aktualisierte Theme konnte nicht gelöscht werden.'); assert_true(delete_custom_theme($theme['id'], $themesDirectory), 'Das Theme konnte nicht gelöscht werden.'); assert_true(list_custom_themes($themesDirectory) === [], 'Das gelöschte Theme wird weiterhin aufgelistet.'); echo "Theme-Upload, Aktualisierung, automatische Namensgebung, Schutzprüfungen und Löschen funktionieren.\n"; } finally { if (is_dir($testDirectory)) { remove_custom_theme_directory($testDirectory); } } function create_test_theme_archive(string $path, array $files): void { $zip = new ZipArchive(); if ($zip->open($path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { throw new RuntimeException("Test-ZIP konnte nicht erstellt werden: {$path}"); } foreach ($files as $name => $contents) { $zip->addFromString($name, $contents); } $zip->close(); } function assert_install_fails(string $archive, string $themesDirectory, string $expectedMessage): void { try { install_custom_theme_archive($archive, 'Ungültiges Theme', $themesDirectory); } catch (RuntimeException $e) { assert_true( str_contains($e->getMessage(), $expectedMessage), "Unerwartete Fehlermeldung: {$e->getMessage()}" ); return; } throw new RuntimeException("Ungültiges Archiv wurde akzeptiert: {$archive}"); } function assert_true(bool $condition, string $message): void { if (!$condition) { throw new RuntimeException($message); } }