Theme Modules: Updated install command to handle nested folder

Theme module ZIPs will now support their files being in a single nested
directory within a ZIP, to support common ZIP structure approaches.
Added test to cover.
For #6066
This commit is contained in:
Dan Brown
2026-04-11 14:34:54 +01:00
parent 5e78dc6ed5
commit 3d9d5fef51
3 changed files with 83 additions and 2 deletions

View File

@@ -15,7 +15,41 @@ readonly class ThemeModuleZip
{
$zip = new ZipArchive();
$zip->open($this->path);
$zip->extractTo($destinationPath);
$prefix = $this->getZipContentPrefix($zip);
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
$entryIsDir = str_ends_with($name, "/");
if ($entryIsDir) {
continue;
}
$stream = $zip->getStreamIndex($i);
if ($prefix) {
if (!str_starts_with($name, $prefix) || $name === $prefix) {
continue;
}
$name = str_replace($prefix, '', $name);
}
$targetPath = $destinationPath . DIRECTORY_SEPARATOR . $name;
$targetPathDir = dirname($targetPath);
if (!is_dir($targetPathDir)) {
$dirCreated = mkdir($targetPathDir, 0777, true);
if (!$dirCreated) {
throw new ThemeModuleException("Failed to create directory {$targetPathDir} when extracting module files");
}
}
$targetFile = fopen($targetPath, 'w');
$written = stream_copy_to_stream($stream, $targetFile);
if (!$written) {
throw new ThemeModuleException("Failed to write to {$targetPath} when extracting module files");
}
fclose($targetFile);
}
$zip->close();
}
@@ -31,7 +65,8 @@ readonly class ThemeModuleZip
throw new ThemeModuleException("Unable to open zip file at {$this->path}");
}
$moduleJsonText = $zip->getFromName('bookstack-module.json');
$prefix = $this->getZipContentPrefix($zip);
$moduleJsonText = $zip->getFromName("{$prefix}bookstack-module.json");
$zip->close();
if ($moduleJsonText === false) {
@@ -95,4 +130,20 @@ readonly class ThemeModuleZip
return $totalSize;
}
protected function getZipContentPrefix(ZipArchive $zip): string
{
$index = $zip->locateName('bookstack-module.json', ZipArchive::FL_NODIR);
if ($index === false) {
return '';
}
$location = $zip->getNameIndex($index);
$pathParts = explode('/', $location);
if (count($pathParts) !== 2) {
return '';
}
return $pathParts[0] . '/';
}
}