mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-04-21 03:30:38 +00:00
ZIP imports: Started actual import logic
This commit is contained in:
parent
7b84558ca1
commit
d13e4d2eef
6 changed files with 124 additions and 24 deletions
app
Entities/Tools
Exports/ZipExports
dev/docs
|
@ -18,17 +18,12 @@ use Illuminate\Http\UploadedFile;
|
||||||
|
|
||||||
class Cloner
|
class Cloner
|
||||||
{
|
{
|
||||||
protected PageRepo $pageRepo;
|
public function __construct(
|
||||||
protected ChapterRepo $chapterRepo;
|
protected PageRepo $pageRepo,
|
||||||
protected BookRepo $bookRepo;
|
protected ChapterRepo $chapterRepo,
|
||||||
protected ImageService $imageService;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -10,13 +10,12 @@ class ZipExportAttachment extends ZipExportModel
|
||||||
{
|
{
|
||||||
public ?int $id = null;
|
public ?int $id = null;
|
||||||
public string $name;
|
public string $name;
|
||||||
public ?int $order = null;
|
|
||||||
public ?string $link = null;
|
public ?string $link = null;
|
||||||
public ?string $file = null;
|
public ?string $file = null;
|
||||||
|
|
||||||
public function metadataOnly(): void
|
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
|
public static function fromModel(Attachment $model, ZipExportFiles $files): self
|
||||||
|
@ -24,7 +23,6 @@ class ZipExportAttachment extends ZipExportModel
|
||||||
$instance = new self();
|
$instance = new self();
|
||||||
$instance->id = $model->id;
|
$instance->id = $model->id;
|
||||||
$instance->name = $model->name;
|
$instance->name = $model->name;
|
||||||
$instance->order = $model->order;
|
|
||||||
|
|
||||||
if ($model->external) {
|
if ($model->external) {
|
||||||
$instance->link = $model->path;
|
$instance->link = $model->path;
|
||||||
|
@ -47,7 +45,6 @@ class ZipExportAttachment extends ZipExportModel
|
||||||
$rules = [
|
$rules = [
|
||||||
'id' => ['nullable', 'int'],
|
'id' => ['nullable', 'int'],
|
||||||
'name' => ['required', 'string', 'min:1'],
|
'name' => ['required', 'string', 'min:1'],
|
||||||
'order' => ['nullable', 'integer'],
|
|
||||||
'link' => ['required_without:file', 'nullable', 'string'],
|
'link' => ['required_without:file', 'nullable', 'string'],
|
||||||
'file' => ['required_without:link', 'nullable', 'string', $context->fileReferenceRule()],
|
'file' => ['required_without:link', 'nullable', 'string', $context->fileReferenceRule()],
|
||||||
];
|
];
|
||||||
|
@ -61,7 +58,6 @@ class ZipExportAttachment extends ZipExportModel
|
||||||
|
|
||||||
$model->id = $data['id'] ?? null;
|
$model->id = $data['id'] ?? null;
|
||||||
$model->name = $data['name'];
|
$model->name = $data['name'];
|
||||||
$model->order = isset($data['order']) ? intval($data['order']) : null;
|
|
||||||
$model->link = $data['link'] ?? null;
|
$model->link = $data['link'] ?? null;
|
||||||
$model->file = $data['file'] ?? null;
|
$model->file = $data['file'] ?? null;
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,10 @@ class ZipExportTag extends ZipExportModel
|
||||||
{
|
{
|
||||||
public string $name;
|
public string $name;
|
||||||
public ?string $value = null;
|
public ?string $value = null;
|
||||||
public ?int $order = null;
|
|
||||||
|
|
||||||
public function metadataOnly(): void
|
public function metadataOnly(): void
|
||||||
{
|
{
|
||||||
$this->value = $this->order = null;
|
$this->value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromModel(Tag $model): self
|
public static function fromModel(Tag $model): self
|
||||||
|
@ -21,7 +20,6 @@ class ZipExportTag extends ZipExportModel
|
||||||
$instance = new self();
|
$instance = new self();
|
||||||
$instance->name = $model->name;
|
$instance->name = $model->name;
|
||||||
$instance->value = $model->value;
|
$instance->value = $model->value;
|
||||||
$instance->order = $model->order;
|
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +34,6 @@ class ZipExportTag extends ZipExportModel
|
||||||
$rules = [
|
$rules = [
|
||||||
'name' => ['required', 'string', 'min:1'],
|
'name' => ['required', 'string', 'min:1'],
|
||||||
'value' => ['nullable', 'string'],
|
'value' => ['nullable', 'string'],
|
||||||
'order' => ['nullable', 'integer'],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return $context->validateData($data, $rules);
|
return $context->validateData($data, $rules);
|
||||||
|
@ -48,7 +45,6 @@ class ZipExportTag extends ZipExportModel
|
||||||
|
|
||||||
$model->name = $data['name'];
|
$model->name = $data['name'];
|
||||||
$model->value = $data['value'] ?? null;
|
$model->value = $data['value'] ?? null;
|
||||||
$model->order = isset($data['order']) ? intval($data['order']) : null;
|
|
||||||
|
|
||||||
return $model;
|
return $model;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,14 @@ class ZipExportReader
|
||||||
return $this->zip->statName("files/{$fileName}") !== false;
|
return $this->zip->statName("files/{$fileName}") !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return false|resource
|
||||||
|
*/
|
||||||
|
public function streamFile(string $fileName)
|
||||||
|
{
|
||||||
|
return $this->zip->getStream("files/{$fileName}");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws ZipExportException
|
* @throws ZipExportException
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,18 +5,33 @@ namespace BookStack\Exports\ZipExports;
|
||||||
use BookStack\Entities\Models\Book;
|
use BookStack\Entities\Models\Book;
|
||||||
use BookStack\Entities\Models\Chapter;
|
use BookStack\Entities\Models\Chapter;
|
||||||
use BookStack\Entities\Models\Entity;
|
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\ZipExportException;
|
||||||
use BookStack\Exceptions\ZipImportException;
|
use BookStack\Exceptions\ZipImportException;
|
||||||
use BookStack\Exports\Import;
|
use BookStack\Exports\Import;
|
||||||
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\ZipExportPage;
|
use BookStack\Exports\ZipExports\Models\ZipExportPage;
|
||||||
|
use BookStack\Exports\ZipExports\Models\ZipExportTag;
|
||||||
use BookStack\Uploads\FileStorage;
|
use BookStack\Uploads\FileStorage;
|
||||||
|
use BookStack\Uploads\ImageService;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
|
||||||
class ZipImportRunner
|
class ZipImportRunner
|
||||||
{
|
{
|
||||||
|
protected array $tempFilesToCleanup = []; // TODO
|
||||||
|
protected array $createdImages = []; // TODO
|
||||||
|
protected array $createdAttachments = []; // TODO
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected FileStorage $storage,
|
protected FileStorage $storage,
|
||||||
|
protected PageRepo $pageRepo,
|
||||||
|
protected ChapterRepo $chapterRepo,
|
||||||
|
protected BookRepo $bookRepo,
|
||||||
|
protected ImageService $imageService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +66,98 @@ class ZipImportRunner
|
||||||
$this->ensurePermissionsPermitImport($exportModel);
|
$this->ensurePermissionsPermitImport($exportModel);
|
||||||
|
|
||||||
// TODO - Run import
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -135,12 +135,10 @@ embedded within it.
|
||||||
- `name` - String, required, name of attachment.
|
- `name` - String, required, name of attachment.
|
||||||
- `link` - String, semi-optional, URL of attachment.
|
- `link` - String, semi-optional, URL of attachment.
|
||||||
- `file` - String reference, semi-optional, reference to attachment file.
|
- `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.
|
Either `link` or `file` must be present, as that will determine the type of attachment.
|
||||||
|
|
||||||
#### Tag
|
#### Tag
|
||||||
|
|
||||||
- `name` - String, required, name of the tag.
|
- `name` - String, required, name of the tag.
|
||||||
- `value` - String, optional, value of the tag (can be empty).
|
- `value` - String, optional, value of the tag (can be empty).
|
||||||
- `order` - Number, optional, integer order of the tags (shown low to high).
|
|
Loading…
Add table
Reference in a new issue