getZipPath($import); $reader = new ZipExportReader($zipPath); $errors = (new ZipExportValidator($reader))->validate(); if ($errors) { throw new ZipImportException(["ZIP failed to validate"]); } try { $exportModel = $reader->decodeDataToExportModel(); } catch (ZipExportException $e) { throw new ZipImportException([$e->getMessage()]); } // Validate parent type if ($exportModel instanceof ZipExportBook && ($parent !== null)) { throw new ZipImportException(["Must not have a parent set for a Book import"]); } else if ($exportModel instanceof ZipExportChapter && (!$parent instanceof Book)) { throw new ZipImportException(["Parent book required for chapter import"]); } else if ($exportModel instanceof ZipExportPage && !($parent instanceof Book || $parent instanceof Chapter)) { throw new ZipImportException(["Parent book or chapter required for page import"]); } $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 - In transaction? // TODO - Revert uploaded files if goes wrong // TODO - Attachments // TODO - Images // (Both listed/stored in references) $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 { $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 ?? []), ]); if ($book->cover) { $this->references->addImage($book->cover, null); } $children = [ ...$exportBook->chapters, ...$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); } } $this->references->addBook($book, $exportBook); 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); $exportPages = $exportChapter->pages; usort($exportPages, function (ZipExportPage $a, ZipExportPage $b) { return ($a->priority ?? 0) - ($b->priority ?? 0); }); foreach ($exportPages as $exportPage) { $this->importPage($exportPage, $chapter, $reader); } $this->references->addChapter($chapter, $exportChapter); return $chapter; } protected function importPage(ZipExportPage $exportPage, Book|Chapter $parent, ZipExportReader $reader): Page { $page = $this->pageRepo->getNewDraftPage($parent); foreach ($exportPage->attachments as $exportAttachment) { $this->importAttachment($exportAttachment, $page, $reader); } foreach ($exportPage->images as $exportImage) { $this->importImage($exportImage, $page, $reader); } $this->pageRepo->publishDraft($page, [ 'name' => $exportPage->name, 'markdown' => $exportPage->markdown, 'html' => $exportPage->html, 'tags' => $this->exportTagsToInputArray($exportPage->tags ?? []), ]); $this->references->addPage($page, $exportPage); 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 { $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); } /** * @throws ZipImportException */ protected function ensurePermissionsPermitImport(ZipExportPage|ZipExportChapter|ZipExportBook $exportModel, Book|Chapter|null $parent = null): void { $errors = []; // TODO - Extract messages to language files // TODO - Ensure these are shown to users on failure $chapters = []; $pages = []; $images = []; $attachments = []; if ($exportModel instanceof ZipExportBook) { if (!userCan('book-create-all')) { $errors[] = 'You are lacking the required permission to create books.'; } array_push($pages, ...$exportModel->pages); array_push($chapters, ...$exportModel->chapters); } else if ($exportModel instanceof ZipExportChapter) { $chapters[] = $exportModel; } else if ($exportModel instanceof ZipExportPage) { $pages[] = $exportModel; } foreach ($chapters as $chapter) { array_push($pages, ...$chapter->pages); } if (count($chapters) > 0) { $permission = 'chapter-create' . ($parent ? '' : '-all'); if (!userCan($permission, $parent)) { $errors[] = 'You are lacking the required permission to create chapters.'; } } foreach ($pages as $page) { array_push($attachments, ...$page->attachments); array_push($images, ...$page->images); } if (count($pages) > 0) { if ($parent) { if (!userCan('page-create', $parent)) { $errors[] = 'You are lacking the required permission to create pages.'; } } else { $hasPermission = userCan('page-create-all') || userCan('page-create-own'); if (!$hasPermission) { $errors[] = 'You are lacking the required permission to create pages.'; } } } if (count($images) > 0) { if (!userCan('image-create-all')) { $errors[] = 'You are lacking the required permissions to create images.'; } } if (count($attachments) > 0) { if (!userCan('attachment-create-all')) { $errors[] = 'You are lacking the required permissions to create attachments.'; } } if (count($errors)) { throw new ZipImportException($errors); } } protected function getZipPath(Import $import): string { if (!$this->storage->isRemote()) { return $this->storage->getSystemPath($import->path); } $tempFilePath = tempnam(sys_get_temp_dir(), 'bszip-import-'); $tempFile = fopen($tempFilePath, 'wb'); $stream = $this->storage->getReadStream($import->path); stream_copy_to_stream($stream, $tempFile); fclose($tempFile); $this->tempFilesToCleanup[] = $tempFilePath; return $tempFilePath; } }