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
protected array $data = [];
public function __construct(
- protected ZipExportFiles $files
+ protected ZipExportFiles $files,
+ protected ZipExportReferences $references,
) {
}
*/
public function buildForPage(Page $page): string
{
- $this->data['page'] = $this->convertPage($page);
- return $this->build();
- }
+ $exportPage = ZipExportPage::fromModel($page, $this->files);
+ $this->data['page'] = $exportPage;
- 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,
- ];
+ $this->references->addPage($exportPage);
- 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,
- ];
+ return $this->build();
}
/**
*/
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'))),
--- /dev/null
+<?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));
+ }
+}
--- /dev/null
+<?php
+
+namespace BookStack\Exports\ZipExportModels;
+
+use BookStack\Activity\Models\Tag;
+
+class ZipExportImage implements ZipExportModel
+{
+ public string $name;
+ public string $file;
+}
--- /dev/null
+<?php
+
+namespace BookStack\Exports\ZipExportModels;
+
+use BookStack\App\Model;
+use BookStack\Exports\ZipExportFiles;
+
+interface ZipExportModel
+{
+// public static function fromModel(Model $model, ZipExportFiles $files): self;
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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));
+ }
+}
--- /dev/null
+<?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
+ }
+}
--- /dev/null
+<?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;
+ }
+}
#### 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.