0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-04-22 11:52:30 +00:00

ZIP Export: Started building link/ref handling

This commit is contained in:
Dan Brown 2024-10-20 19:56:56 +01:00
parent 21ccfa97dd
commit 7c39dd5cba
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
9 changed files with 266 additions and 46 deletions

View file

@ -2,10 +2,9 @@
namespace BookStack\Exports;
use BookStack\Activity\Models\Tag;
use BookStack\Entities\Models\Page;
use BookStack\Exceptions\ZipExportException;
use BookStack\Uploads\Attachment;
use BookStack\Exports\ZipExportModels\ZipExportPage;
use ZipArchive;
class ZipExportBuilder
@ -13,7 +12,8 @@ class ZipExportBuilder
protected array $data = [];
public function __construct(
protected ZipExportFiles $files
protected ZipExportFiles $files,
protected ZipExportReferences $references,
) {
}
@ -22,57 +22,21 @@ class ZipExportBuilder
*/
public function buildForPage(Page $page): string
{
$this->data['page'] = $this->convertPage($page);
$exportPage = ZipExportPage::fromModel($page, $this->files);
$this->data['page'] = $exportPage;
$this->references->addPage($exportPage);
return $this->build();
}
protected function convertPage(Page $page): array
{
$tags = array_map($this->convertTag(...), $page->tags()->get()->all());
$attachments = array_map($this->convertAttachment(...), $page->attachments()->get()->all());
return [
'id' => $page->id,
'name' => $page->name,
'html' => '', // TODO
'markdown' => '', // TODO
'priority' => $page->priority,
'attachments' => $attachments,
'images' => [], // TODO
'tags' => $tags,
];
}
protected function convertAttachment(Attachment $attachment): array
{
$data = [
'name' => $attachment->name,
'order' => $attachment->order,
];
if ($attachment->external) {
$data['link'] = $attachment->path;
} else {
$data['file'] = $this->files->referenceForAttachment($attachment);
}
return $data;
}
protected function convertTag(Tag $tag): array
{
return [
'name' => $tag->name,
'value' => $tag->value,
'order' => $tag->order,
];
}
/**
* @throws ZipExportException
*/
protected function build(): string
{
$this->references->buildReferences();
$this->data['exported_at'] = date(DATE_ATOM);
$this->data['instance'] = [
'version' => trim(file_get_contents(base_path('version'))),

View file

@ -0,0 +1,37 @@
<?php
namespace BookStack\Exports\ZipExportModels;
use BookStack\Exports\ZipExportFiles;
use BookStack\Uploads\Attachment;
class ZipExportAttachment implements ZipExportModel
{
public ?int $id = null;
public string $name;
public ?int $order = null;
public ?string $link = null;
public ?string $file = null;
public static function fromModel(Attachment $model, ZipExportFiles $files): self
{
$instance = new self();
$instance->id = $model->id;
$instance->name = $model->name;
if ($model->external) {
$instance->link = $model->path;
} else {
$instance->file = $files->referenceForAttachment($model);
}
return $instance;
}
public static function fromModelArray(array $attachmentArray, ZipExportFiles $files): array
{
return array_values(array_map(function (Attachment $attachment) use ($files) {
return self::fromModel($attachment, $files);
}, $attachmentArray));
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace BookStack\Exports\ZipExportModels;
use BookStack\Activity\Models\Tag;
class ZipExportImage implements ZipExportModel
{
public string $name;
public string $file;
}

View file

@ -0,0 +1,11 @@
<?php
namespace BookStack\Exports\ZipExportModels;
use BookStack\App\Model;
use BookStack\Exports\ZipExportFiles;
interface ZipExportModel
{
// public static function fromModel(Model $model, ZipExportFiles $files): self;
}

View file

@ -0,0 +1,39 @@
<?php
namespace BookStack\Exports\ZipExportModels;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\PageContent;
use BookStack\Exports\ZipExportFiles;
class ZipExportPage implements ZipExportModel
{
public ?int $id = null;
public string $name;
public ?string $html = null;
public ?string $markdown = null;
public ?int $priority = null;
/** @var ZipExportAttachment[] */
public array $attachments = [];
/** @var ZipExportImage[] */
public array $images = [];
/** @var ZipExportTag[] */
public array $tags = [];
public static function fromModel(Page $model, ZipExportFiles $files): self
{
$instance = new self();
$instance->id = $model->id;
$instance->name = $model->name;
$instance->html = (new PageContent($model))->render();
if (!empty($model->markdown)) {
$instance->markdown = $model->markdown;
}
$instance->tags = ZipExportTag::fromModelArray($model->tags()->get()->all());
$instance->attachments = ZipExportAttachment::fromModelArray($model->attachments()->get()->all(), $files);
return $instance;
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace BookStack\Exports\ZipExportModels;
use BookStack\Activity\Models\Tag;
class ZipExportTag implements ZipExportModel
{
public string $name;
public ?string $value = null;
public ?int $order = null;
public static function fromModel(Tag $model): self
{
$instance = new self();
$instance->name = $model->name;
$instance->value = $model->value;
$instance->order = $model->order;
return $instance;
}
public static function fromModelArray(array $tagArray): array
{
return array_values(array_map(self::fromModel(...), $tagArray));
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace BookStack\Exports;
use BookStack\App\Model;
use BookStack\Exports\ZipExportModels\ZipExportAttachment;
use BookStack\Exports\ZipExportModels\ZipExportPage;
class ZipExportReferences
{
/** @var ZipExportPage[] */
protected array $pages = [];
protected array $books = [];
protected array $chapters = [];
/** @var ZipExportAttachment[] */
protected array $attachments = [];
public function __construct(
protected ZipReferenceParser $parser,
) {
}
public function addPage(ZipExportPage $page): void
{
if ($page->id) {
$this->pages[$page->id] = $page;
}
foreach ($page->attachments as $attachment) {
if ($attachment->id) {
$this->attachments[$attachment->id] = $attachment;
}
}
}
public function buildReferences(): void
{
// TODO - References to images, attachments, other entities
// TODO - Parse page MD & HTML
foreach ($this->pages as $page) {
$page->html = $this->parser->parse($page->html ?? '', function (Model $model): ?string {
// TODO - Handle found link to $model
// - Validate we can see/access $model, or/and that it's
// part of the export in progress.
return '[CAT]';
});
// TODO - markdown
}
// TODO - Parse chapter desc html
// TODO - Parse book desc html
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace BookStack\Exports;
use BookStack\App\Model;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\References\ModelResolvers\BookLinkModelResolver;
use BookStack\References\ModelResolvers\ChapterLinkModelResolver;
use BookStack\References\ModelResolvers\CrossLinkModelResolver;
use BookStack\References\ModelResolvers\PageLinkModelResolver;
use BookStack\References\ModelResolvers\PagePermalinkModelResolver;
class ZipReferenceParser
{
/**
* @var CrossLinkModelResolver[]
*/
protected array $modelResolvers;
public function __construct(EntityQueries $queries)
{
$this->modelResolvers = [
new PagePermalinkModelResolver($queries->pages),
new PageLinkModelResolver($queries->pages),
new ChapterLinkModelResolver($queries->chapters),
new BookLinkModelResolver($queries->books),
// TODO - Image
// TODO - Attachment
];
}
/**
* Parse and replace references in the given content.
* @param callable(Model):(string|null) $handler
*/
public function parse(string $content, callable $handler): string
{
$escapedBase = preg_quote(url('/'), '/');
$linkRegex = "/({$escapedBase}.*?)[\\t\\n\\f>\"'=?#]/";
$matches = [];
preg_match_all($linkRegex, $content, $matches);
if (count($matches) < 2) {
return $content;
}
foreach ($matches[1] as $link) {
$model = $this->linkToModel($link);
if ($model) {
$result = $handler($model);
if ($result !== null) {
$content = str_replace($link, $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) {
$model = $resolver->resolve($link);
if (!is_null($model)) {
return $model;
}
}
return null;
}
}

View file

@ -128,6 +128,7 @@ File must be an image type accepted by BookStack (png, jpg, gif, webp)
#### Attachment
- `id` - Number, optional, original ID for the attachment from exported system.
- `name` - String, required, name of attachment.
- `link` - String, semi-optional, URL of attachment.
- `file` - String reference, semi-optional, reference to attachment file.