diff --git a/app/Entities/Tools/Cloner.php b/app/Entities/Tools/Cloner.php index 2030b050c..2be6083e3 100644 --- a/app/Entities/Tools/Cloner.php +++ b/app/Entities/Tools/Cloner.php @@ -18,17 +18,12 @@ use Illuminate\Http\UploadedFile; class Cloner { - protected PageRepo $pageRepo; - protected ChapterRepo $chapterRepo; - protected BookRepo $bookRepo; - protected ImageService $imageService; - - public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo, BookRepo $bookRepo, ImageService $imageService) - { - $this->pageRepo = $pageRepo; - $this->chapterRepo = $chapterRepo; - $this->bookRepo = $bookRepo; - $this->imageService = $imageService; + public function __construct( + protected PageRepo $pageRepo, + protected ChapterRepo $chapterRepo, + protected BookRepo $bookRepo, + protected ImageService $imageService, + ) { } /** diff --git a/app/Exports/ZipExports/Models/ZipExportAttachment.php b/app/Exports/ZipExports/Models/ZipExportAttachment.php index 1dbdc7333..c6615e1dc 100644 --- a/app/Exports/ZipExports/Models/ZipExportAttachment.php +++ b/app/Exports/ZipExports/Models/ZipExportAttachment.php @@ -10,13 +10,12 @@ class ZipExportAttachment extends ZipExportModel { public ?int $id = null; public string $name; - public ?int $order = null; public ?string $link = null; public ?string $file = null; public function metadataOnly(): void { - $this->order = $this->link = $this->file = null; + $this->link = $this->file = null; } public static function fromModel(Attachment $model, ZipExportFiles $files): self @@ -24,7 +23,6 @@ class ZipExportAttachment extends ZipExportModel $instance = new self(); $instance->id = $model->id; $instance->name = $model->name; - $instance->order = $model->order; if ($model->external) { $instance->link = $model->path; @@ -47,7 +45,6 @@ class ZipExportAttachment extends ZipExportModel $rules = [ 'id' => ['nullable', 'int'], 'name' => ['required', 'string', 'min:1'], - 'order' => ['nullable', 'integer'], 'link' => ['required_without:file', 'nullable', 'string'], 'file' => ['required_without:link', 'nullable', 'string', $context->fileReferenceRule()], ]; @@ -61,7 +58,6 @@ class ZipExportAttachment extends ZipExportModel $model->id = $data['id'] ?? null; $model->name = $data['name']; - $model->order = isset($data['order']) ? intval($data['order']) : null; $model->link = $data['link'] ?? null; $model->file = $data['file'] ?? null; diff --git a/app/Exports/ZipExports/Models/ZipExportTag.php b/app/Exports/ZipExports/Models/ZipExportTag.php index b6c9e338a..6b4720fca 100644 --- a/app/Exports/ZipExports/Models/ZipExportTag.php +++ b/app/Exports/ZipExports/Models/ZipExportTag.php @@ -9,11 +9,10 @@ class ZipExportTag extends ZipExportModel { public string $name; public ?string $value = null; - public ?int $order = null; public function metadataOnly(): void { - $this->value = $this->order = null; + $this->value = null; } public static function fromModel(Tag $model): self @@ -21,7 +20,6 @@ class ZipExportTag extends ZipExportModel $instance = new self(); $instance->name = $model->name; $instance->value = $model->value; - $instance->order = $model->order; return $instance; } @@ -36,7 +34,6 @@ class ZipExportTag extends ZipExportModel $rules = [ 'name' => ['required', 'string', 'min:1'], 'value' => ['nullable', 'string'], - 'order' => ['nullable', 'integer'], ]; return $context->validateData($data, $rules); @@ -48,7 +45,6 @@ class ZipExportTag extends ZipExportModel $model->name = $data['name']; $model->value = $data['value'] ?? null; - $model->order = isset($data['order']) ? intval($data['order']) : null; return $model; } diff --git a/app/Exports/ZipExports/ZipExportReader.php b/app/Exports/ZipExports/ZipExportReader.php index c3e47da04..ebc2fbbc9 100644 --- a/app/Exports/ZipExports/ZipExportReader.php +++ b/app/Exports/ZipExports/ZipExportReader.php @@ -73,6 +73,14 @@ class ZipExportReader return $this->zip->statName("files/{$fileName}") !== false; } + /** + * @return false|resource + */ + public function streamFile(string $fileName) + { + return $this->zip->getStream("files/{$fileName}"); + } + /** * @throws ZipExportException */ diff --git a/app/Exports/ZipExports/ZipImportRunner.php b/app/Exports/ZipExports/ZipImportRunner.php index 2f784ebea..2b897ff91 100644 --- a/app/Exports/ZipExports/ZipImportRunner.php +++ b/app/Exports/ZipExports/ZipImportRunner.php @@ -5,18 +5,33 @@ namespace BookStack\Exports\ZipExports; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; +use BookStack\Entities\Models\Page; +use BookStack\Entities\Repos\BookRepo; +use BookStack\Entities\Repos\ChapterRepo; +use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\ZipExportException; use BookStack\Exceptions\ZipImportException; use BookStack\Exports\Import; use BookStack\Exports\ZipExports\Models\ZipExportBook; use BookStack\Exports\ZipExports\Models\ZipExportChapter; use BookStack\Exports\ZipExports\Models\ZipExportPage; +use BookStack\Exports\ZipExports\Models\ZipExportTag; use BookStack\Uploads\FileStorage; +use BookStack\Uploads\ImageService; +use Illuminate\Http\UploadedFile; class ZipImportRunner { + protected array $tempFilesToCleanup = []; // TODO + protected array $createdImages = []; // TODO + protected array $createdAttachments = []; // TODO + public function __construct( protected FileStorage $storage, + protected PageRepo $pageRepo, + protected ChapterRepo $chapterRepo, + protected BookRepo $bookRepo, + protected ImageService $imageService, ) { } @@ -51,6 +66,98 @@ class ZipImportRunner $this->ensurePermissionsPermitImport($exportModel); // TODO - Run import + // TODO - In transaction? + // TODO - Revert uploaded files if goes wrong + } + + protected function importBook(ZipExportBook $exportBook, ZipExportReader $reader): Book + { + $book = $this->bookRepo->create([ + 'name' => $exportBook->name, + 'description_html' => $exportBook->description_html ?? '', + 'image' => $exportBook->cover ? $this->zipFileToUploadedFile($exportBook->cover, $reader) : null, + 'tags' => $this->exportTagsToInputArray($exportBook->tags ?? []), + ]); + + // TODO - Parse/format description_html references + + if ($book->cover) { + $this->createdImages[] = $book->cover; + } + + // TODO - Pages + foreach ($exportBook->chapters as $exportChapter) { + $this->importChapter($exportChapter, $book); + } + // TODO - Sort chapters/pages by order + + return $book; + } + + protected function importChapter(ZipExportChapter $exportChapter, Book $parent, ZipExportReader $reader): Chapter + { + $chapter = $this->chapterRepo->create([ + 'name' => $exportChapter->name, + 'description_html' => $exportChapter->description_html ?? '', + 'tags' => $this->exportTagsToInputArray($exportChapter->tags ?? []), + ], $parent); + + // TODO - Parse/format description_html references + + $exportPages = $exportChapter->pages; + usort($exportPages, function (ZipExportPage $a, ZipExportPage $b) { + return ($a->priority ?? 0) - ($b->priority ?? 0); + }); + + foreach ($exportPages as $exportPage) { + // + } + // TODO - Pages + + return $chapter; + } + + protected function importPage(ZipExportPage $exportPage, Book|Chapter $parent, ZipExportReader $reader): Page + { + $page = $this->pageRepo->getNewDraftPage($parent); + + // TODO - Import attachments + // TODO - Import images + // TODO - Parse/format HTML + + $this->pageRepo->publishDraft($page, [ + 'name' => $exportPage->name, + 'markdown' => $exportPage->markdown, + 'html' => $exportPage->html, + 'tags' => $this->exportTagsToInputArray($exportPage->tags ?? []), + ]); + + return $page; + } + + protected function exportTagsToInputArray(array $exportTags): array + { + $tags = []; + + /** @var ZipExportTag $tag */ + foreach ($exportTags as $tag) { + $tags[] = ['name' => $tag->name, 'value' => $tag->value ?? '']; + } + + return $tags; + } + + protected function zipFileToUploadedFile(string $fileName, ZipExportReader $reader): UploadedFile + { + $tempPath = tempnam(sys_get_temp_dir(), 'bszipextract'); + $fileStream = $reader->streamFile($fileName); + $tempStream = fopen($tempPath, 'wb'); + stream_copy_to_stream($fileStream, $tempStream); + fclose($tempStream); + + $this->tempFilesToCleanup[] = $tempPath; + + return new UploadedFile($tempPath, $fileName); } /** diff --git a/dev/docs/portable-zip-file-format.md b/dev/docs/portable-zip-file-format.md index 6cee7356d..7e5df3f01 100644 --- a/dev/docs/portable-zip-file-format.md +++ b/dev/docs/portable-zip-file-format.md @@ -135,12 +135,10 @@ embedded within it. - `name` - String, required, name of attachment. - `link` - String, semi-optional, URL of attachment. - `file` - String reference, semi-optional, reference to attachment file. -- `order` - Number, optional, integer order of the attachments (shown low to high). Either `link` or `file` must be present, as that will determine the type of attachment. #### Tag - `name` - String, required, name of the tag. -- `value` - String, optional, value of the tag (can be empty). -- `order` - Number, optional, integer order of the tags (shown low to high). \ No newline at end of file +- `value` - String, optional, value of the tag (can be empty). \ No newline at end of file