mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-24 19:07:20 +03:00
- Added (limited) redirect handling to module downloads. - Adjusted wording/text for consistency and clarity. - Fixed scenarios where process was not stopped on error. - Fixed module folder creation check/logic. - Added better failed request handling to module downloads. - Updated download response streaming to monitor/limit download size.
134 lines
4.0 KiB
PHP
134 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace BookStack\Theming;
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
class ThemeModuleManager
|
|
{
|
|
/** @var array<string, ThemeModule>|null */
|
|
protected array|null $loadedModules = null;
|
|
|
|
public function __construct(
|
|
protected string $modulesFolderPath
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @return array<string, ThemeModule>
|
|
*/
|
|
public function getByName(string $name): array
|
|
{
|
|
return array_filter($this->load(), fn(ThemeModule $module) => $module->name === $name);
|
|
}
|
|
|
|
public function deleteModuleFolder(string $moduleFolderName): void
|
|
{
|
|
$modules = $this->load();
|
|
$module = $modules[$moduleFolderName] ?? null;
|
|
if (!$module) {
|
|
return;
|
|
}
|
|
|
|
$moduleFolderPath = $module->path('');
|
|
if (!file_exists($moduleFolderPath)) {
|
|
return;
|
|
}
|
|
|
|
$this->deleteDirectoryRecursively($moduleFolderPath);
|
|
unset($this->loadedModules[$moduleFolderName]);
|
|
}
|
|
|
|
/**
|
|
* @throws ThemeModuleException
|
|
*/
|
|
public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule
|
|
{
|
|
$baseFolderName = Str::limit(Str::slug($name), 20);
|
|
$folderName = $baseFolderName;
|
|
while (!$baseFolderName || file_exists($this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName)) {
|
|
$folderName = ($baseFolderName ?: 'mod') . '-' . Str::random(4);
|
|
}
|
|
|
|
$folderPath = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName;
|
|
$zip->extractTo($folderPath);
|
|
|
|
$module = $this->loadFromFolder($folderName);
|
|
if (!$module) {
|
|
throw new ThemeModuleException("Failed to load module from zip file after extraction");
|
|
}
|
|
|
|
return $module;
|
|
}
|
|
|
|
protected function deleteDirectoryRecursively(string $path): void
|
|
{
|
|
$items = array_diff(scandir($path), ['.', '..']);
|
|
foreach ($items as $item) {
|
|
$itemPath = $path . DIRECTORY_SEPARATOR . $item;
|
|
if (is_dir($itemPath)) {
|
|
$this->deleteDirectoryRecursively($itemPath);
|
|
} else {
|
|
$deleted = unlink($itemPath);
|
|
if (!$deleted) {
|
|
throw new ThemeModuleException("Failed to delete file at \"{$itemPath}\"");
|
|
}
|
|
}
|
|
}
|
|
rmdir($path);
|
|
}
|
|
|
|
public function load(): array
|
|
{
|
|
if ($this->loadedModules !== null) {
|
|
return $this->loadedModules;
|
|
}
|
|
|
|
if (!is_dir($this->modulesFolderPath)) {
|
|
return [];
|
|
}
|
|
|
|
$subFolders = array_filter(scandir($this->modulesFolderPath), function ($item) {
|
|
return $item !== '.' && $item !== '..' && is_dir($this->modulesFolderPath . DIRECTORY_SEPARATOR . $item);
|
|
});
|
|
|
|
$modules = [];
|
|
|
|
foreach ($subFolders as $folderName) {
|
|
$module = $this->loadFromFolder($folderName);
|
|
if ($module) {
|
|
$modules[$folderName] = $module;
|
|
}
|
|
}
|
|
|
|
$this->loadedModules = $modules;
|
|
|
|
return $modules;
|
|
}
|
|
|
|
protected function loadFromFolder(string $folderName): ThemeModule|null
|
|
{
|
|
$moduleJsonFile = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName . DIRECTORY_SEPARATOR . 'bookstack-module.json';
|
|
if (!file_exists($moduleJsonFile)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$jsonContent = file_get_contents($moduleJsonFile);
|
|
$jsonData = json_decode($jsonContent, true);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
throw new ThemeModuleException("Invalid JSON in module file at \"{$moduleJsonFile}\": " . json_last_error_msg());
|
|
}
|
|
|
|
$module = ThemeModule::fromJson($jsonData, $folderName);
|
|
} catch (ThemeModuleException $exception) {
|
|
throw $exception;
|
|
} catch (\Exception $exception) {
|
|
throw new ThemeModuleException("Failed loading module from \"{$moduleJsonFile}\" with error: {$exception->getMessage()}");
|
|
}
|
|
|
|
return $module;
|
|
}
|
|
}
|