0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-04-30 14:40:03 +00:00

ZIP Imports: Finished off core import logic

This commit is contained in:
Dan Brown 2024-11-11 15:06:46 +00:00
parent 378f0d595f
commit 48c101aa7a
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
5 changed files with 113 additions and 28 deletions

View file

@ -7,6 +7,7 @@ class ZipImportException extends \Exception
public function __construct( public function __construct(
public array $errors public array $errors
) { ) {
parent::__construct(); $message = "Import failed with errors:" . implode("\n", $this->errors);
parent::__construct($message);
} }
} }

View file

@ -79,18 +79,21 @@ class ImportController extends Controller
$import = $this->imports->findVisible($id); $import = $this->imports->findVisible($id);
$parent = null; $parent = null;
if ($import->getType() === 'page' || $import->getType() === 'chapter') { if ($import->type === 'page' || $import->type === 'chapter') {
$data = $this->validate($request, [ $data = $this->validate($request, [
'parent' => ['required', 'string'] 'parent' => ['required', 'string']
]); ]);
$parent = $data['parent']; $parent = $data['parent'];
} }
// TODO - Run import $entity = $this->imports->runImport($import, $parent);
// TODO - Validate again before if ($entity) {
// TODO - Check permissions before (create for main item, create for children, create for related items [image, attachments]) $this->logActivity(ActivityType::IMPORT_RUN, $import);
return redirect($entity->getUrl());
}
// TODO - Redirect to result // TODO - Redirect to result
// TODO - Or redirect back with errors // TODO - Or redirect back with errors
return 'failed';
} }
/** /**

View file

@ -2,9 +2,11 @@
namespace BookStack\Exports; namespace BookStack\Exports;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\EntityQueries;
use BookStack\Exceptions\FileUploadException; use BookStack\Exceptions\FileUploadException;
use BookStack\Exceptions\ZipExportException; use BookStack\Exceptions\ZipExportException;
use BookStack\Exceptions\ZipImportException;
use BookStack\Exceptions\ZipValidationException; use BookStack\Exceptions\ZipValidationException;
use BookStack\Exports\ZipExports\Models\ZipExportBook; use BookStack\Exports\ZipExports\Models\ZipExportBook;
use BookStack\Exports\ZipExports\Models\ZipExportChapter; use BookStack\Exports\ZipExports\Models\ZipExportChapter;
@ -95,9 +97,9 @@ class ImportRepo
} }
/** /**
* @throws ZipValidationException * @throws ZipValidationException|ZipImportException
*/ */
public function runImport(Import $import, ?string $parent = null) public function runImport(Import $import, ?string $parent = null): ?Entity
{ {
$parentModel = null; $parentModel = null;
if ($import->type === 'page' || $import->type === 'chapter') { if ($import->type === 'page' || $import->type === 'chapter') {

View file

@ -110,7 +110,7 @@ class ZipImportReferences
{ {
foreach ($this->books as $book) { foreach ($this->books as $book) {
$exportBook = $this->zipExportBookMap[$book->id]; $exportBook = $this->zipExportBookMap[$book->id];
$content = $exportBook->description_html || ''; $content = $exportBook->description_html ?? '';
$parsed = $this->parser->parseReferences($content, $this->handleReference(...)); $parsed = $this->parser->parseReferences($content, $this->handleReference(...));
$this->baseRepo->update($book, [ $this->baseRepo->update($book, [
@ -120,7 +120,7 @@ class ZipImportReferences
foreach ($this->chapters as $chapter) { foreach ($this->chapters as $chapter) {
$exportChapter = $this->zipExportChapterMap[$chapter->id]; $exportChapter = $this->zipExportChapterMap[$chapter->id];
$content = $exportChapter->description_html || ''; $content = $exportChapter->description_html ?? '';
$parsed = $this->parser->parseReferences($content, $this->handleReference(...)); $parsed = $this->parser->parseReferences($content, $this->handleReference(...));
$this->baseRepo->update($chapter, [ $this->baseRepo->update($chapter, [

View file

@ -12,17 +12,22 @@ use BookStack\Entities\Repos\PageRepo;
use BookStack\Exceptions\ZipExportException; use BookStack\Exceptions\ZipExportException;
use BookStack\Exceptions\ZipImportException; use BookStack\Exceptions\ZipImportException;
use BookStack\Exports\Import; use BookStack\Exports\Import;
use BookStack\Exports\ZipExports\Models\ZipExportAttachment;
use BookStack\Exports\ZipExports\Models\ZipExportBook; use BookStack\Exports\ZipExports\Models\ZipExportBook;
use BookStack\Exports\ZipExports\Models\ZipExportChapter; use BookStack\Exports\ZipExports\Models\ZipExportChapter;
use BookStack\Exports\ZipExports\Models\ZipExportImage;
use BookStack\Exports\ZipExports\Models\ZipExportPage; use BookStack\Exports\ZipExports\Models\ZipExportPage;
use BookStack\Exports\ZipExports\Models\ZipExportTag; use BookStack\Exports\ZipExports\Models\ZipExportTag;
use BookStack\Uploads\Attachment;
use BookStack\Uploads\AttachmentService;
use BookStack\Uploads\FileStorage; use BookStack\Uploads\FileStorage;
use BookStack\Uploads\Image;
use BookStack\Uploads\ImageService; use BookStack\Uploads\ImageService;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
class ZipImportRunner class ZipImportRunner
{ {
protected array $tempFilesToCleanup = []; // TODO protected array $tempFilesToCleanup = [];
public function __construct( public function __construct(
protected FileStorage $storage, protected FileStorage $storage,
@ -30,14 +35,19 @@ class ZipImportRunner
protected ChapterRepo $chapterRepo, protected ChapterRepo $chapterRepo,
protected BookRepo $bookRepo, protected BookRepo $bookRepo,
protected ImageService $imageService, protected ImageService $imageService,
protected AttachmentService $attachmentService,
protected ZipImportReferences $references, protected ZipImportReferences $references,
) { ) {
} }
/** /**
* Run the import.
* Performs re-validation on zip, validation on parent provided, and permissions for importing
* the planned content, before running the import process.
* Returns the top-level entity item which was imported.
* @throws ZipImportException * @throws ZipImportException
*/ */
public function run(Import $import, ?Entity $parent = null): void public function run(Import $import, ?Entity $parent = null): ?Entity
{ {
$zipPath = $this->getZipPath($import); $zipPath = $this->getZipPath($import);
$reader = new ZipExportReader($zipPath); $reader = new ZipExportReader($zipPath);
@ -63,8 +73,16 @@ class ZipImportRunner
} }
$this->ensurePermissionsPermitImport($exportModel); $this->ensurePermissionsPermitImport($exportModel);
$entity = null;
if ($exportModel instanceof ZipExportBook) {
$entity = $this->importBook($exportModel, $reader);
} else if ($exportModel instanceof ZipExportChapter) {
$entity = $this->importChapter($exportModel, $parent, $reader);
} else if ($exportModel instanceof ZipExportPage) {
$entity = $this->importPage($exportModel, $parent, $reader);
}
// TODO - Run import
// TODO - In transaction? // TODO - In transaction?
// TODO - Revert uploaded files if goes wrong // TODO - Revert uploaded files if goes wrong
// TODO - Attachments // TODO - Attachments
@ -72,6 +90,23 @@ class ZipImportRunner
// (Both listed/stored in references) // (Both listed/stored in references)
$this->references->replaceReferences(); $this->references->replaceReferences();
$reader->close();
$this->cleanup();
dd('stop');
// TODO - Delete import/zip after import?
// Do this in parent repo?
return $entity;
}
protected function cleanup()
{
foreach ($this->tempFilesToCleanup as $file) {
unlink($file);
}
} }
protected function importBook(ZipExportBook $exportBook, ZipExportReader $reader): Book protected function importBook(ZipExportBook $exportBook, ZipExportReader $reader): Book
@ -83,17 +118,26 @@ class ZipImportRunner
'tags' => $this->exportTagsToInputArray($exportBook->tags ?? []), 'tags' => $this->exportTagsToInputArray($exportBook->tags ?? []),
]); ]);
// TODO - Parse/format description_html references
if ($book->cover) { if ($book->cover) {
$this->references->addImage($book->cover, null); $this->references->addImage($book->cover, null);
} }
// TODO - Pages $children = [
foreach ($exportBook->chapters as $exportChapter) { ...$exportBook->chapters,
$this->importChapter($exportChapter, $book, $reader); ...$exportBook->pages,
];
usort($children, function (ZipExportPage|ZipExportChapter $a, ZipExportPage|ZipExportChapter $b) {
return ($a->priority ?? 0) - ($b->priority ?? 0);
});
foreach ($children as $child) {
if ($child instanceof ZipExportChapter) {
$this->importChapter($child, $book, $reader);
} else if ($child instanceof ZipExportPage) {
$this->importPage($child, $book, $reader);
}
} }
// TODO - Sort chapters/pages by order
$this->references->addBook($book, $exportBook); $this->references->addBook($book, $exportBook);
@ -108,17 +152,14 @@ class ZipImportRunner
'tags' => $this->exportTagsToInputArray($exportChapter->tags ?? []), 'tags' => $this->exportTagsToInputArray($exportChapter->tags ?? []),
], $parent); ], $parent);
// TODO - Parse/format description_html references
$exportPages = $exportChapter->pages; $exportPages = $exportChapter->pages;
usort($exportPages, function (ZipExportPage $a, ZipExportPage $b) { usort($exportPages, function (ZipExportPage $a, ZipExportPage $b) {
return ($a->priority ?? 0) - ($b->priority ?? 0); return ($a->priority ?? 0) - ($b->priority ?? 0);
}); });
foreach ($exportPages as $exportPage) { foreach ($exportPages as $exportPage) {
// $this->importPage($exportPage, $chapter, $reader);
} }
// TODO - Pages
$this->references->addChapter($chapter, $exportChapter); $this->references->addChapter($chapter, $exportChapter);
@ -129,11 +170,13 @@ class ZipImportRunner
{ {
$page = $this->pageRepo->getNewDraftPage($parent); $page = $this->pageRepo->getNewDraftPage($parent);
// TODO - Import attachments foreach ($exportPage->attachments as $exportAttachment) {
// TODO - Add attachment references $this->importAttachment($exportAttachment, $page, $reader);
// TODO - Import images }
// TODO - Add image references
// TODO - Parse/format HTML foreach ($exportPage->images as $exportImage) {
$this->importImage($exportImage, $page, $reader);
}
$this->pageRepo->publishDraft($page, [ $this->pageRepo->publishDraft($page, [
'name' => $exportPage->name, 'name' => $exportPage->name,
@ -147,6 +190,40 @@ class ZipImportRunner
return $page; return $page;
} }
protected function importAttachment(ZipExportAttachment $exportAttachment, Page $page, ZipExportReader $reader): Attachment
{
if ($exportAttachment->file) {
$file = $this->zipFileToUploadedFile($exportAttachment->file, $reader);
$attachment = $this->attachmentService->saveNewUpload($file, $page->id);
$attachment->name = $exportAttachment->name;
$attachment->save();
} else {
$attachment = $this->attachmentService->saveNewFromLink(
$exportAttachment->name,
$exportAttachment->link ?? '',
$page->id,
);
}
$this->references->addAttachment($attachment, $exportAttachment->id);
return $attachment;
}
protected function importImage(ZipExportImage $exportImage, Page $page, ZipExportReader $reader): Image
{
$file = $this->zipFileToUploadedFile($exportImage->file, $reader);
$image = $this->imageService->saveNewFromUpload(
$file,
$exportImage->type,
$page->id,
);
$this->references->addImage($image, $exportImage->id);
return $image;
}
protected function exportTagsToInputArray(array $exportTags): array protected function exportTagsToInputArray(array $exportTags): array
{ {
$tags = []; $tags = [];
@ -235,7 +312,7 @@ class ZipImportRunner
} }
if (count($attachments) > 0) { if (count($attachments) > 0) {
if (userCan('attachment-create-all')) { if (!userCan('attachment-create-all')) {
$errors[] = 'You are lacking the required permissions to create attachments.'; $errors[] = 'You are lacking the required permissions to create attachments.';
} }
} }
@ -257,6 +334,8 @@ class ZipImportRunner
stream_copy_to_stream($stream, $tempFile); stream_copy_to_stream($stream, $tempFile);
fclose($tempFile); fclose($tempFile);
$this->tempFilesToCleanup[] = $tempFilePath;
return $tempFilePath; return $tempFilePath;
} }
} }