mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-02-21 17:04:43 +08:00
ZIP Imports: Built out reference parsing/updating logic
This commit is contained in:
parent
d13e4d2eef
commit
378f0d595f
@ -87,6 +87,17 @@ class PageRepo
|
||||
return $draft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly update the content for the given page from the provided input.
|
||||
* Used for direct content access in a way that performs required changes
|
||||
* (Search index & reference regen) without performing an official update.
|
||||
*/
|
||||
public function setContentFromInput(Page $page, array $input): void
|
||||
{
|
||||
$this->updateTemplateStatusAndContentFromInput($page, $input);
|
||||
$this->baseRepo->update($page, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a page in the system.
|
||||
*/
|
||||
@ -121,7 +132,7 @@ class PageRepo
|
||||
return $page;
|
||||
}
|
||||
|
||||
protected function updateTemplateStatusAndContentFromInput(Page $page, array $input)
|
||||
protected function updateTemplateStatusAndContentFromInput(Page $page, array $input): void
|
||||
{
|
||||
if (isset($input['template']) && userCan('templates-manage')) {
|
||||
$page->template = ($input['template'] === 'true');
|
||||
|
@ -85,9 +85,9 @@ class ZipExportReferences
|
||||
// Parse page content first
|
||||
foreach ($this->pages as $page) {
|
||||
$handler = $createHandler($page);
|
||||
$page->html = $this->parser->parse($page->html ?? '', $handler);
|
||||
$page->html = $this->parser->parseLinks($page->html ?? '', $handler);
|
||||
if ($page->markdown) {
|
||||
$page->markdown = $this->parser->parse($page->markdown, $handler);
|
||||
$page->markdown = $this->parser->parseLinks($page->markdown, $handler);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ class ZipExportReferences
|
||||
foreach ($this->chapters as $chapter) {
|
||||
if ($chapter->description_html) {
|
||||
$handler = $createHandler($chapter);
|
||||
$chapter->description_html = $this->parser->parse($chapter->description_html, $handler);
|
||||
$chapter->description_html = $this->parser->parseLinks($chapter->description_html, $handler);
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ class ZipExportReferences
|
||||
foreach ($this->books as $book) {
|
||||
if ($book->description_html) {
|
||||
$handler = $createHandler($book);
|
||||
$book->description_html = $this->parser->parse($book->description_html, $handler);
|
||||
$book->description_html = $this->parser->parseLinks($book->description_html, $handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
142
app/Exports/ZipExports/ZipImportReferences.php
Normal file
142
app/Exports/ZipExports/ZipImportReferences.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Exports\ZipExports;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\BaseRepo;
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportBook;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportChapter;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportPage;
|
||||
use BookStack\Uploads\Attachment;
|
||||
use BookStack\Uploads\Image;
|
||||
use BookStack\Uploads\ImageResizer;
|
||||
|
||||
class ZipImportReferences
|
||||
{
|
||||
/** @var Page[] */
|
||||
protected array $pages = [];
|
||||
/** @var Chapter[] */
|
||||
protected array $chapters = [];
|
||||
/** @var Book[] */
|
||||
protected array $books = [];
|
||||
/** @var Attachment[] */
|
||||
protected array $attachments = [];
|
||||
/** @var Image[] */
|
||||
protected array $images = [];
|
||||
|
||||
/** @var array<string, Model> */
|
||||
protected array $referenceMap = [];
|
||||
|
||||
/** @var array<int, ZipExportPage> */
|
||||
protected array $zipExportPageMap = [];
|
||||
/** @var array<int, ZipExportChapter> */
|
||||
protected array $zipExportChapterMap = [];
|
||||
/** @var array<int, ZipExportBook> */
|
||||
protected array $zipExportBookMap = [];
|
||||
|
||||
public function __construct(
|
||||
protected ZipReferenceParser $parser,
|
||||
protected BaseRepo $baseRepo,
|
||||
protected PageRepo $pageRepo,
|
||||
protected ImageResizer $imageResizer,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function addReference(string $type, Model $model, ?int $importId): void
|
||||
{
|
||||
if ($importId) {
|
||||
$key = $type . ':' . $importId;
|
||||
$this->referenceMap[$key] = $model;
|
||||
}
|
||||
}
|
||||
|
||||
public function addPage(Page $page, ZipExportPage $exportPage): void
|
||||
{
|
||||
$this->pages[] = $page;
|
||||
$this->zipExportPageMap[$page->id] = $exportPage;
|
||||
$this->addReference('page', $page, $exportPage->id);
|
||||
}
|
||||
|
||||
public function addChapter(Chapter $chapter, ZipExportChapter $exportChapter): void
|
||||
{
|
||||
$this->chapters[] = $chapter;
|
||||
$this->zipExportChapterMap[$chapter->id] = $exportChapter;
|
||||
$this->addReference('chapter', $chapter, $exportChapter->id);
|
||||
}
|
||||
|
||||
public function addBook(Book $book, ZipExportBook $exportBook): void
|
||||
{
|
||||
$this->books[] = $book;
|
||||
$this->zipExportBookMap[$book->id] = $exportBook;
|
||||
$this->addReference('book', $book, $exportBook->id);
|
||||
}
|
||||
|
||||
public function addAttachment(Attachment $attachment, ?int $importId): void
|
||||
{
|
||||
$this->attachments[] = $attachment;
|
||||
$this->addReference('attachment', $attachment, $importId);
|
||||
}
|
||||
|
||||
public function addImage(Image $image, ?int $importId): void
|
||||
{
|
||||
$this->images[] = $image;
|
||||
$this->addReference('image', $image, $importId);
|
||||
}
|
||||
|
||||
protected function handleReference(string $type, int $id): ?string
|
||||
{
|
||||
$key = $type . ':' . $id;
|
||||
$model = $this->referenceMap[$key] ?? null;
|
||||
if ($model instanceof Entity) {
|
||||
return $model->getUrl();
|
||||
} else if ($model instanceof Image) {
|
||||
if ($model->type === 'gallery') {
|
||||
$this->imageResizer->loadGalleryThumbnailsForImage($model, false);
|
||||
return $model->thumbs['gallery'] ?? $model->url;
|
||||
}
|
||||
|
||||
return $model->url;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function replaceReferences(): void
|
||||
{
|
||||
foreach ($this->books as $book) {
|
||||
$exportBook = $this->zipExportBookMap[$book->id];
|
||||
$content = $exportBook->description_html || '';
|
||||
$parsed = $this->parser->parseReferences($content, $this->handleReference(...));
|
||||
|
||||
$this->baseRepo->update($book, [
|
||||
'description_html' => $parsed,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($this->chapters as $chapter) {
|
||||
$exportChapter = $this->zipExportChapterMap[$chapter->id];
|
||||
$content = $exportChapter->description_html || '';
|
||||
$parsed = $this->parser->parseReferences($content, $this->handleReference(...));
|
||||
|
||||
$this->baseRepo->update($chapter, [
|
||||
'description_html' => $parsed,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($this->pages as $page) {
|
||||
$exportPage = $this->zipExportPageMap[$page->id];
|
||||
$contentType = $exportPage->markdown ? 'markdown' : 'html';
|
||||
$content = $exportPage->markdown ?: ($exportPage->html ?: '');
|
||||
$parsed = $this->parser->parseReferences($content, $this->handleReference(...));
|
||||
|
||||
$this->pageRepo->setContentFromInput($page, [
|
||||
$contentType => $parsed,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,8 +23,6 @@ use Illuminate\Http\UploadedFile;
|
||||
class ZipImportRunner
|
||||
{
|
||||
protected array $tempFilesToCleanup = []; // TODO
|
||||
protected array $createdImages = []; // TODO
|
||||
protected array $createdAttachments = []; // TODO
|
||||
|
||||
public function __construct(
|
||||
protected FileStorage $storage,
|
||||
@ -32,6 +30,7 @@ class ZipImportRunner
|
||||
protected ChapterRepo $chapterRepo,
|
||||
protected BookRepo $bookRepo,
|
||||
protected ImageService $imageService,
|
||||
protected ZipImportReferences $references,
|
||||
) {
|
||||
}
|
||||
|
||||
@ -68,6 +67,11 @@ class ZipImportRunner
|
||||
// TODO - Run import
|
||||
// TODO - In transaction?
|
||||
// TODO - Revert uploaded files if goes wrong
|
||||
// TODO - Attachments
|
||||
// TODO - Images
|
||||
// (Both listed/stored in references)
|
||||
|
||||
$this->references->replaceReferences();
|
||||
}
|
||||
|
||||
protected function importBook(ZipExportBook $exportBook, ZipExportReader $reader): Book
|
||||
@ -82,15 +86,17 @@ class ZipImportRunner
|
||||
// TODO - Parse/format description_html references
|
||||
|
||||
if ($book->cover) {
|
||||
$this->createdImages[] = $book->cover;
|
||||
$this->references->addImage($book->cover, null);
|
||||
}
|
||||
|
||||
// TODO - Pages
|
||||
foreach ($exportBook->chapters as $exportChapter) {
|
||||
$this->importChapter($exportChapter, $book);
|
||||
$this->importChapter($exportChapter, $book, $reader);
|
||||
}
|
||||
// TODO - Sort chapters/pages by order
|
||||
|
||||
$this->references->addBook($book, $exportBook);
|
||||
|
||||
return $book;
|
||||
}
|
||||
|
||||
@ -114,6 +120,8 @@ class ZipImportRunner
|
||||
}
|
||||
// TODO - Pages
|
||||
|
||||
$this->references->addChapter($chapter, $exportChapter);
|
||||
|
||||
return $chapter;
|
||||
}
|
||||
|
||||
@ -122,7 +130,9 @@ class ZipImportRunner
|
||||
$page = $this->pageRepo->getNewDraftPage($parent);
|
||||
|
||||
// TODO - Import attachments
|
||||
// TODO - Add attachment references
|
||||
// TODO - Import images
|
||||
// TODO - Add image references
|
||||
// TODO - Parse/format HTML
|
||||
|
||||
$this->pageRepo->publishDraft($page, [
|
||||
@ -132,6 +142,8 @@ class ZipImportRunner
|
||||
'tags' => $this->exportTagsToInputArray($exportPage->tags ?? []),
|
||||
]);
|
||||
|
||||
$this->references->addPage($page, $exportPage);
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
|
@ -15,27 +15,23 @@ use BookStack\References\ModelResolvers\PagePermalinkModelResolver;
|
||||
class ZipReferenceParser
|
||||
{
|
||||
/**
|
||||
* @var CrossLinkModelResolver[]
|
||||
* @var CrossLinkModelResolver[]|null
|
||||
*/
|
||||
protected array $modelResolvers;
|
||||
protected ?array $modelResolvers = null;
|
||||
|
||||
public function __construct(EntityQueries $queries)
|
||||
{
|
||||
$this->modelResolvers = [
|
||||
new PagePermalinkModelResolver($queries->pages),
|
||||
new PageLinkModelResolver($queries->pages),
|
||||
new ChapterLinkModelResolver($queries->chapters),
|
||||
new BookLinkModelResolver($queries->books),
|
||||
new ImageModelResolver(),
|
||||
new AttachmentModelResolver(),
|
||||
];
|
||||
public function __construct(
|
||||
protected EntityQueries $queries
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and replace references in the given content.
|
||||
* Calls the handler for each model link detected and replaces the link
|
||||
* with the handler return value if provided.
|
||||
* Returns the resulting content with links replaced.
|
||||
* @param callable(Model):(string|null) $handler
|
||||
*/
|
||||
public function parse(string $content, callable $handler): string
|
||||
public function parseLinks(string $content, callable $handler): string
|
||||
{
|
||||
$escapedBase = preg_quote(url('/'), '/');
|
||||
$linkRegex = "/({$escapedBase}.*?)[\\t\\n\\f>\"'=?#()]/";
|
||||
@ -59,13 +55,43 @@ class ZipReferenceParser
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and replace references in the given content.
|
||||
* Calls the handler for each reference detected and replaces the link
|
||||
* with the handler return value if provided.
|
||||
* Returns the resulting content string with references replaced.
|
||||
* @param callable(string $type, int $id):(string|null) $handler
|
||||
*/
|
||||
public function parseReferences(string $content, callable $handler): string
|
||||
{
|
||||
$referenceRegex = '/\[\[bsexport:([a-z]+):(\d+)]]/';
|
||||
$matches = [];
|
||||
preg_match_all($referenceRegex, $content, $matches);
|
||||
|
||||
if (count($matches) < 3) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < count($matches[0]); $i++) {
|
||||
$referenceText = $matches[0][$i];
|
||||
$type = strtolower($matches[1][$i]);
|
||||
$id = intval($matches[2][$i]);
|
||||
$result = $handler($type, $id);
|
||||
if ($result !== null) {
|
||||
$content = str_replace($referenceText, $result, $content);
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to resolve the given link to a model using the instance model resolvers.
|
||||
*/
|
||||
protected function linkToModel(string $link): ?Model
|
||||
{
|
||||
foreach ($this->modelResolvers as $resolver) {
|
||||
foreach ($this->getModelResolvers() as $resolver) {
|
||||
$model = $resolver->resolve($link);
|
||||
if (!is_null($model)) {
|
||||
return $model;
|
||||
@ -74,4 +100,22 @@ class ZipReferenceParser
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getModelResolvers(): array
|
||||
{
|
||||
if (isset($this->modelResolvers)) {
|
||||
return $this->modelResolvers;
|
||||
}
|
||||
|
||||
$this->modelResolvers = [
|
||||
new PagePermalinkModelResolver($this->queries->pages),
|
||||
new PageLinkModelResolver($this->queries->pages),
|
||||
new ChapterLinkModelResolver($this->queries->chapters),
|
||||
new BookLinkModelResolver($this->queries->books),
|
||||
new ImageModelResolver(),
|
||||
new AttachmentModelResolver(),
|
||||
];
|
||||
|
||||
return $this->modelResolvers;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user