Skip to content

Commit 378f0d5

Browse files
committed
ZIP Imports: Built out reference parsing/updating logic
1 parent d13e4d2 commit 378f0d5

File tree

5 files changed

+232
-23
lines changed

5 files changed

+232
-23
lines changed

app/Entities/Repos/PageRepo.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ public function publishDraft(Page $draft, array $input): Page
8787
return $draft;
8888
}
8989

90+
/**
91+
* Directly update the content for the given page from the provided input.
92+
* Used for direct content access in a way that performs required changes
93+
* (Search index & reference regen) without performing an official update.
94+
*/
95+
public function setContentFromInput(Page $page, array $input): void
96+
{
97+
$this->updateTemplateStatusAndContentFromInput($page, $input);
98+
$this->baseRepo->update($page, []);
99+
}
100+
90101
/**
91102
* Update a page in the system.
92103
*/
@@ -121,7 +132,7 @@ public function update(Page $page, array $input): Page
121132
return $page;
122133
}
123134

124-
protected function updateTemplateStatusAndContentFromInput(Page $page, array $input)
135+
protected function updateTemplateStatusAndContentFromInput(Page $page, array $input): void
125136
{
126137
if (isset($input['template']) && userCan('templates-manage')) {
127138
$page->template = ($input['template'] === 'true');

app/Exports/ZipExports/ZipExportReferences.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,25 +85,25 @@ public function buildReferences(ZipExportFiles $files): void
8585
// Parse page content first
8686
foreach ($this->pages as $page) {
8787
$handler = $createHandler($page);
88-
$page->html = $this->parser->parse($page->html ?? '', $handler);
88+
$page->html = $this->parser->parseLinks($page->html ?? '', $handler);
8989
if ($page->markdown) {
90-
$page->markdown = $this->parser->parse($page->markdown, $handler);
90+
$page->markdown = $this->parser->parseLinks($page->markdown, $handler);
9191
}
9292
}
9393

9494
// Parse chapter description HTML
9595
foreach ($this->chapters as $chapter) {
9696
if ($chapter->description_html) {
9797
$handler = $createHandler($chapter);
98-
$chapter->description_html = $this->parser->parse($chapter->description_html, $handler);
98+
$chapter->description_html = $this->parser->parseLinks($chapter->description_html, $handler);
9999
}
100100
}
101101

102102
// Parse book description HTML
103103
foreach ($this->books as $book) {
104104
if ($book->description_html) {
105105
$handler = $createHandler($book);
106-
$book->description_html = $this->parser->parse($book->description_html, $handler);
106+
$book->description_html = $this->parser->parseLinks($book->description_html, $handler);
107107
}
108108
}
109109
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
namespace BookStack\Exports\ZipExports;
4+
5+
use BookStack\App\Model;
6+
use BookStack\Entities\Models\Book;
7+
use BookStack\Entities\Models\Chapter;
8+
use BookStack\Entities\Models\Entity;
9+
use BookStack\Entities\Models\Page;
10+
use BookStack\Entities\Repos\BaseRepo;
11+
use BookStack\Entities\Repos\PageRepo;
12+
use BookStack\Exports\ZipExports\Models\ZipExportBook;
13+
use BookStack\Exports\ZipExports\Models\ZipExportChapter;
14+
use BookStack\Exports\ZipExports\Models\ZipExportPage;
15+
use BookStack\Uploads\Attachment;
16+
use BookStack\Uploads\Image;
17+
use BookStack\Uploads\ImageResizer;
18+
19+
class ZipImportReferences
20+
{
21+
/** @var Page[] */
22+
protected array $pages = [];
23+
/** @var Chapter[] */
24+
protected array $chapters = [];
25+
/** @var Book[] */
26+
protected array $books = [];
27+
/** @var Attachment[] */
28+
protected array $attachments = [];
29+
/** @var Image[] */
30+
protected array $images = [];
31+
32+
/** @var array<string, Model> */
33+
protected array $referenceMap = [];
34+
35+
/** @var array<int, ZipExportPage> */
36+
protected array $zipExportPageMap = [];
37+
/** @var array<int, ZipExportChapter> */
38+
protected array $zipExportChapterMap = [];
39+
/** @var array<int, ZipExportBook> */
40+
protected array $zipExportBookMap = [];
41+
42+
public function __construct(
43+
protected ZipReferenceParser $parser,
44+
protected BaseRepo $baseRepo,
45+
protected PageRepo $pageRepo,
46+
protected ImageResizer $imageResizer,
47+
) {
48+
}
49+
50+
protected function addReference(string $type, Model $model, ?int $importId): void
51+
{
52+
if ($importId) {
53+
$key = $type . ':' . $importId;
54+
$this->referenceMap[$key] = $model;
55+
}
56+
}
57+
58+
public function addPage(Page $page, ZipExportPage $exportPage): void
59+
{
60+
$this->pages[] = $page;
61+
$this->zipExportPageMap[$page->id] = $exportPage;
62+
$this->addReference('page', $page, $exportPage->id);
63+
}
64+
65+
public function addChapter(Chapter $chapter, ZipExportChapter $exportChapter): void
66+
{
67+
$this->chapters[] = $chapter;
68+
$this->zipExportChapterMap[$chapter->id] = $exportChapter;
69+
$this->addReference('chapter', $chapter, $exportChapter->id);
70+
}
71+
72+
public function addBook(Book $book, ZipExportBook $exportBook): void
73+
{
74+
$this->books[] = $book;
75+
$this->zipExportBookMap[$book->id] = $exportBook;
76+
$this->addReference('book', $book, $exportBook->id);
77+
}
78+
79+
public function addAttachment(Attachment $attachment, ?int $importId): void
80+
{
81+
$this->attachments[] = $attachment;
82+
$this->addReference('attachment', $attachment, $importId);
83+
}
84+
85+
public function addImage(Image $image, ?int $importId): void
86+
{
87+
$this->images[] = $image;
88+
$this->addReference('image', $image, $importId);
89+
}
90+
91+
protected function handleReference(string $type, int $id): ?string
92+
{
93+
$key = $type . ':' . $id;
94+
$model = $this->referenceMap[$key] ?? null;
95+
if ($model instanceof Entity) {
96+
return $model->getUrl();
97+
} else if ($model instanceof Image) {
98+
if ($model->type === 'gallery') {
99+
$this->imageResizer->loadGalleryThumbnailsForImage($model, false);
100+
return $model->thumbs['gallery'] ?? $model->url;
101+
}
102+
103+
return $model->url;
104+
}
105+
106+
return null;
107+
}
108+
109+
public function replaceReferences(): void
110+
{
111+
foreach ($this->books as $book) {
112+
$exportBook = $this->zipExportBookMap[$book->id];
113+
$content = $exportBook->description_html || '';
114+
$parsed = $this->parser->parseReferences($content, $this->handleReference(...));
115+
116+
$this->baseRepo->update($book, [
117+
'description_html' => $parsed,
118+
]);
119+
}
120+
121+
foreach ($this->chapters as $chapter) {
122+
$exportChapter = $this->zipExportChapterMap[$chapter->id];
123+
$content = $exportChapter->description_html || '';
124+
$parsed = $this->parser->parseReferences($content, $this->handleReference(...));
125+
126+
$this->baseRepo->update($chapter, [
127+
'description_html' => $parsed,
128+
]);
129+
}
130+
131+
foreach ($this->pages as $page) {
132+
$exportPage = $this->zipExportPageMap[$page->id];
133+
$contentType = $exportPage->markdown ? 'markdown' : 'html';
134+
$content = $exportPage->markdown ?: ($exportPage->html ?: '');
135+
$parsed = $this->parser->parseReferences($content, $this->handleReference(...));
136+
137+
$this->pageRepo->setContentFromInput($page, [
138+
$contentType => $parsed,
139+
]);
140+
}
141+
}
142+
}

app/Exports/ZipExports/ZipImportRunner.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,14 @@
2323
class ZipImportRunner
2424
{
2525
protected array $tempFilesToCleanup = []; // TODO
26-
protected array $createdImages = []; // TODO
27-
protected array $createdAttachments = []; // TODO
2826

2927
public function __construct(
3028
protected FileStorage $storage,
3129
protected PageRepo $pageRepo,
3230
protected ChapterRepo $chapterRepo,
3331
protected BookRepo $bookRepo,
3432
protected ImageService $imageService,
33+
protected ZipImportReferences $references,
3534
) {
3635
}
3736

@@ -68,6 +67,11 @@ public function run(Import $import, ?Entity $parent = null): void
6867
// TODO - Run import
6968
// TODO - In transaction?
7069
// TODO - Revert uploaded files if goes wrong
70+
// TODO - Attachments
71+
// TODO - Images
72+
// (Both listed/stored in references)
73+
74+
$this->references->replaceReferences();
7175
}
7276

7377
protected function importBook(ZipExportBook $exportBook, ZipExportReader $reader): Book
@@ -82,15 +86,17 @@ protected function importBook(ZipExportBook $exportBook, ZipExportReader $reader
8286
// TODO - Parse/format description_html references
8387

8488
if ($book->cover) {
85-
$this->createdImages[] = $book->cover;
89+
$this->references->addImage($book->cover, null);
8690
}
8791

8892
// TODO - Pages
8993
foreach ($exportBook->chapters as $exportChapter) {
90-
$this->importChapter($exportChapter, $book);
94+
$this->importChapter($exportChapter, $book, $reader);
9195
}
9296
// TODO - Sort chapters/pages by order
9397

98+
$this->references->addBook($book, $exportBook);
99+
94100
return $book;
95101
}
96102

@@ -114,6 +120,8 @@ protected function importChapter(ZipExportChapter $exportChapter, Book $parent,
114120
}
115121
// TODO - Pages
116122

123+
$this->references->addChapter($chapter, $exportChapter);
124+
117125
return $chapter;
118126
}
119127

@@ -122,7 +130,9 @@ protected function importPage(ZipExportPage $exportPage, Book|Chapter $parent, Z
122130
$page = $this->pageRepo->getNewDraftPage($parent);
123131

124132
// TODO - Import attachments
133+
// TODO - Add attachment references
125134
// TODO - Import images
135+
// TODO - Add image references
126136
// TODO - Parse/format HTML
127137

128138
$this->pageRepo->publishDraft($page, [
@@ -132,6 +142,8 @@ protected function importPage(ZipExportPage $exportPage, Book|Chapter $parent, Z
132142
'tags' => $this->exportTagsToInputArray($exportPage->tags ?? []),
133143
]);
134144

145+
$this->references->addPage($page, $exportPage);
146+
135147
return $page;
136148
}
137149

app/Exports/ZipExports/ZipReferenceParser.php

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,23 @@
1515
class ZipReferenceParser
1616
{
1717
/**
18-
* @var CrossLinkModelResolver[]
18+
* @var CrossLinkModelResolver[]|null
1919
*/
20-
protected array $modelResolvers;
20+
protected ?array $modelResolvers = null;
2121

22-
public function __construct(EntityQueries $queries)
23-
{
24-
$this->modelResolvers = [
25-
new PagePermalinkModelResolver($queries->pages),
26-
new PageLinkModelResolver($queries->pages),
27-
new ChapterLinkModelResolver($queries->chapters),
28-
new BookLinkModelResolver($queries->books),
29-
new ImageModelResolver(),
30-
new AttachmentModelResolver(),
31-
];
22+
public function __construct(
23+
protected EntityQueries $queries
24+
) {
3225
}
3326

3427
/**
3528
* Parse and replace references in the given content.
29+
* Calls the handler for each model link detected and replaces the link
30+
* with the handler return value if provided.
31+
* Returns the resulting content with links replaced.
3632
* @param callable(Model):(string|null) $handler
3733
*/
38-
public function parse(string $content, callable $handler): string
34+
public function parseLinks(string $content, callable $handler): string
3935
{
4036
$escapedBase = preg_quote(url('/'), '/');
4137
$linkRegex = "/({$escapedBase}.*?)[\\t\\n\\f>\"'=?#()]/";
@@ -59,13 +55,43 @@ public function parse(string $content, callable $handler): string
5955
return $content;
6056
}
6157

58+
/**
59+
* Parse and replace references in the given content.
60+
* Calls the handler for each reference detected and replaces the link
61+
* with the handler return value if provided.
62+
* Returns the resulting content string with references replaced.
63+
* @param callable(string $type, int $id):(string|null) $handler
64+
*/
65+
public function parseReferences(string $content, callable $handler): string
66+
{
67+
$referenceRegex = '/\[\[bsexport:([a-z]+):(\d+)]]/';
68+
$matches = [];
69+
preg_match_all($referenceRegex, $content, $matches);
70+
71+
if (count($matches) < 3) {
72+
return $content;
73+
}
74+
75+
for ($i = 0; $i < count($matches[0]); $i++) {
76+
$referenceText = $matches[0][$i];
77+
$type = strtolower($matches[1][$i]);
78+
$id = intval($matches[2][$i]);
79+
$result = $handler($type, $id);
80+
if ($result !== null) {
81+
$content = str_replace($referenceText, $result, $content);
82+
}
83+
}
84+
85+
return $content;
86+
}
87+
6288

6389
/**
6490
* Attempt to resolve the given link to a model using the instance model resolvers.
6591
*/
6692
protected function linkToModel(string $link): ?Model
6793
{
68-
foreach ($this->modelResolvers as $resolver) {
94+
foreach ($this->getModelResolvers() as $resolver) {
6995
$model = $resolver->resolve($link);
7096
if (!is_null($model)) {
7197
return $model;
@@ -74,4 +100,22 @@ protected function linkToModel(string $link): ?Model
74100

75101
return null;
76102
}
103+
104+
protected function getModelResolvers(): array
105+
{
106+
if (isset($this->modelResolvers)) {
107+
return $this->modelResolvers;
108+
}
109+
110+
$this->modelResolvers = [
111+
new PagePermalinkModelResolver($this->queries->pages),
112+
new PageLinkModelResolver($this->queries->pages),
113+
new ChapterLinkModelResolver($this->queries->chapters),
114+
new BookLinkModelResolver($this->queries->books),
115+
new ImageModelResolver(),
116+
new AttachmentModelResolver(),
117+
];
118+
119+
return $this->modelResolvers;
120+
}
77121
}

0 commit comments

Comments
 (0)