mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-05-04 18:08:46 +03:00
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:
@@ -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] . '/';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ Here are some general best practices when it comes to creating modules:
|
||||
### Distribution Format
|
||||
|
||||
Modules are expected to be distributed as a compressed ZIP file, where the ZIP contents follow that of a module folder.
|
||||
Contents may optionally be placed within a nested folder inside the ZIP.
|
||||
BookStack provides a `php artisan bookstack:install-module` command which allows modules to be installed from these ZIP files, either from a local path or from a web URL.
|
||||
Currently, there's a hardcoded total filesize limit of 50MB for module contents installed via this method.
|
||||
|
||||
|
||||
@@ -175,6 +175,35 @@ class InstallModuleCommandTest extends TestCase
|
||||
->assertExitCode(1);
|
||||
}
|
||||
|
||||
public function test_module_zip_when_files_in_nested_directory()
|
||||
{
|
||||
$this->usingThemeFolder(function ($themeFolder) {
|
||||
$zip = new ZipArchive();
|
||||
$zipFile = tempnam(sys_get_temp_dir(), 'bs-test-module');
|
||||
$zip->open($zipFile, ZipArchive::CREATE);
|
||||
|
||||
$zip->addEmptyDir('mod');
|
||||
$zip->addFromString('mod/bookstack-module.json', json_encode($metadata ?? [
|
||||
'name' => 'Test Module',
|
||||
'description' => 'A test module for BookStack',
|
||||
'version' => '1.0.0',
|
||||
]));
|
||||
$zip->addFromString('mod/functions.php', '<?php $a = "cat";');
|
||||
$zip->addEmptyDir('mod/a');
|
||||
$zip->addFromString('mod/a/cat.txt', 'Meow');
|
||||
$zip->close();
|
||||
|
||||
$this->artisan('bookstack:install-module', ['location' => $zipFile])
|
||||
->expectsConfirmation('Are you sure you want to install this module?', 'yes')
|
||||
->assertExitCode(0);
|
||||
|
||||
$modulePath = glob(theme_path('modules/*'), GLOB_ONLYDIR)[0];
|
||||
$this->assertFileExists($modulePath . '/a/cat.txt');
|
||||
$contents = file_get_contents($modulePath . '/a/cat.txt');
|
||||
$this->assertEquals('Meow', $contents);
|
||||
});
|
||||
}
|
||||
|
||||
public function test_local_module_install_without_active_theme_can_setup_theme_folder()
|
||||
{
|
||||
$zip = $this->getModuleZipPath();
|
||||
|
||||
Reference in New Issue
Block a user