mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-22 12:30:27 +08:00
ZIP Exports: Added working image handling/inclusion
Some checks failed
analyse-php / build (push) Has been cancelled
lint-php / build (push) Has been cancelled
test-migrations / build (8.1) (push) Has been cancelled
test-migrations / build (8.2) (push) Has been cancelled
test-migrations / build (8.3) (push) Has been cancelled
test-php / build (8.1) (push) Has been cancelled
test-php / build (8.2) (push) Has been cancelled
test-php / build (8.3) (push) Has been cancelled
Some checks failed
analyse-php / build (push) Has been cancelled
lint-php / build (push) Has been cancelled
test-migrations / build (8.1) (push) Has been cancelled
test-migrations / build (8.2) (push) Has been cancelled
test-migrations / build (8.3) (push) Has been cancelled
test-php / build (8.1) (push) Has been cancelled
test-php / build (8.2) (push) Has been cancelled
test-php / build (8.3) (push) Has been cancelled
This commit is contained in:
parent
06ffd8ee72
commit
4fb4fe0931
|
@ -35,7 +35,7 @@ class ZipExportBuilder
|
||||||
*/
|
*/
|
||||||
protected function build(): string
|
protected function build(): string
|
||||||
{
|
{
|
||||||
$this->references->buildReferences();
|
$this->references->buildReferences($this->files);
|
||||||
|
|
||||||
$this->data['exported_at'] = date(DATE_ATOM);
|
$this->data['exported_at'] = date(DATE_ATOM);
|
||||||
$this->data['instance'] = [
|
$this->data['instance'] = [
|
||||||
|
|
|
@ -4,6 +4,8 @@ namespace BookStack\Exports;
|
||||||
|
|
||||||
use BookStack\Uploads\Attachment;
|
use BookStack\Uploads\Attachment;
|
||||||
use BookStack\Uploads\AttachmentService;
|
use BookStack\Uploads\AttachmentService;
|
||||||
|
use BookStack\Uploads\Image;
|
||||||
|
use BookStack\Uploads\ImageService;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ZipExportFiles
|
class ZipExportFiles
|
||||||
|
@ -14,8 +16,15 @@ class ZipExportFiles
|
||||||
*/
|
*/
|
||||||
protected array $attachmentRefsById = [];
|
protected array $attachmentRefsById = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* References for images by image ID.
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected array $imageRefsById = [];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected AttachmentService $attachmentService,
|
protected AttachmentService $attachmentService,
|
||||||
|
protected ImageService $imageService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,15 +39,46 @@ class ZipExportFiles
|
||||||
return $this->attachmentRefsById[$attachment->id];
|
return $this->attachmentRefsById[$attachment->id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$existingFiles = $this->getAllFileNames();
|
||||||
do {
|
do {
|
||||||
$fileName = Str::random(20) . '.' . $attachment->extension;
|
$fileName = Str::random(20) . '.' . $attachment->extension;
|
||||||
} while (in_array($fileName, $this->attachmentRefsById));
|
} while (in_array($fileName, $existingFiles));
|
||||||
|
|
||||||
$this->attachmentRefsById[$attachment->id] = $fileName;
|
$this->attachmentRefsById[$attachment->id] = $fileName;
|
||||||
|
|
||||||
return $fileName;
|
return $fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gain a reference to the given image instance.
|
||||||
|
* This is expected to be an image that the user has visibility of,
|
||||||
|
* no permission/access checks are performed here.
|
||||||
|
*/
|
||||||
|
public function referenceForImage(Image $image): string
|
||||||
|
{
|
||||||
|
if (isset($this->imageRefsById[$image->id])) {
|
||||||
|
return $this->imageRefsById[$image->id];
|
||||||
|
}
|
||||||
|
|
||||||
|
$existingFiles = $this->getAllFileNames();
|
||||||
|
$extension = pathinfo($image->path, PATHINFO_EXTENSION);
|
||||||
|
do {
|
||||||
|
$fileName = Str::random(20) . '.' . $extension;
|
||||||
|
} while (in_array($fileName, $existingFiles));
|
||||||
|
|
||||||
|
$this->imageRefsById[$image->id] = $fileName;
|
||||||
|
|
||||||
|
return $fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAllFileNames(): array
|
||||||
|
{
|
||||||
|
return array_merge(
|
||||||
|
array_values($this->attachmentRefsById),
|
||||||
|
array_values($this->imageRefsById),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract each of the ZIP export tracked files.
|
* Extract each of the ZIP export tracked files.
|
||||||
* Calls the given callback for each tracked file, passing a temporary
|
* Calls the given callback for each tracked file, passing a temporary
|
||||||
|
@ -54,5 +94,14 @@ class ZipExportFiles
|
||||||
stream_copy_to_stream($stream, $tmpFileStream);
|
stream_copy_to_stream($stream, $tmpFileStream);
|
||||||
$callback($tmpFile, $ref);
|
$callback($tmpFile, $ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->imageRefsById as $imageId => $ref) {
|
||||||
|
$image = Image::query()->find($imageId);
|
||||||
|
$stream = $this->imageService->getImageStream($image);
|
||||||
|
$tmpFile = tempnam(sys_get_temp_dir(), 'bszipimage-');
|
||||||
|
$tmpFileStream = fopen($tmpFile, 'w');
|
||||||
|
stream_copy_to_stream($stream, $tmpFileStream);
|
||||||
|
$callback($tmpFile, $ref);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,24 @@
|
||||||
|
|
||||||
namespace BookStack\Exports\ZipExportModels;
|
namespace BookStack\Exports\ZipExportModels;
|
||||||
|
|
||||||
use BookStack\Activity\Models\Tag;
|
use BookStack\Exports\ZipExportFiles;
|
||||||
|
use BookStack\Uploads\Image;
|
||||||
|
|
||||||
class ZipExportImage extends ZipExportModel
|
class ZipExportImage extends ZipExportModel
|
||||||
{
|
{
|
||||||
|
public ?int $id = null;
|
||||||
public string $name;
|
public string $name;
|
||||||
public string $file;
|
public string $file;
|
||||||
|
public string $type;
|
||||||
|
|
||||||
|
public static function fromModel(Image $model, ZipExportFiles $files): self
|
||||||
|
{
|
||||||
|
$instance = new self();
|
||||||
|
$instance->id = $model->id;
|
||||||
|
$instance->name = $model->name;
|
||||||
|
$instance->type = $model->type;
|
||||||
|
$instance->file = $files->referenceForImage($model);
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,13 @@
|
||||||
namespace BookStack\Exports;
|
namespace BookStack\Exports;
|
||||||
|
|
||||||
use BookStack\App\Model;
|
use BookStack\App\Model;
|
||||||
|
use BookStack\Entities\Models\Page;
|
||||||
use BookStack\Exports\ZipExportModels\ZipExportAttachment;
|
use BookStack\Exports\ZipExportModels\ZipExportAttachment;
|
||||||
|
use BookStack\Exports\ZipExportModels\ZipExportImage;
|
||||||
|
use BookStack\Exports\ZipExportModels\ZipExportModel;
|
||||||
use BookStack\Exports\ZipExportModels\ZipExportPage;
|
use BookStack\Exports\ZipExportModels\ZipExportPage;
|
||||||
|
use BookStack\Uploads\Attachment;
|
||||||
|
use BookStack\Uploads\Image;
|
||||||
|
|
||||||
class ZipExportReferences
|
class ZipExportReferences
|
||||||
{
|
{
|
||||||
|
@ -16,6 +21,9 @@ class ZipExportReferences
|
||||||
/** @var ZipExportAttachment[] */
|
/** @var ZipExportAttachment[] */
|
||||||
protected array $attachments = [];
|
protected array $attachments = [];
|
||||||
|
|
||||||
|
/** @var ZipExportImage[] */
|
||||||
|
protected array $images = [];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected ZipReferenceParser $parser,
|
protected ZipReferenceParser $parser,
|
||||||
) {
|
) {
|
||||||
|
@ -34,19 +42,12 @@ class ZipExportReferences
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildReferences(): void
|
public function buildReferences(ZipExportFiles $files): void
|
||||||
{
|
{
|
||||||
// TODO - References to images, attachments, other entities
|
|
||||||
|
|
||||||
// TODO - Parse page MD & HTML
|
// TODO - Parse page MD & HTML
|
||||||
foreach ($this->pages as $page) {
|
foreach ($this->pages as $page) {
|
||||||
$page->html = $this->parser->parse($page->html ?? '', function (Model $model): ?string {
|
$page->html = $this->parser->parse($page->html ?? '', function (Model $model) use ($files, $page) {
|
||||||
// TODO - Handle found link to $model
|
return $this->handleModelReference($model, $page, $files);
|
||||||
// - Validate we can see/access $model, or/and that it's
|
|
||||||
// part of the export in progress.
|
|
||||||
|
|
||||||
// TODO - Add images after the above to files
|
|
||||||
return '[CAT]';
|
|
||||||
});
|
});
|
||||||
// TODO - markdown
|
// TODO - markdown
|
||||||
}
|
}
|
||||||
|
@ -55,4 +56,45 @@ class ZipExportReferences
|
||||||
// TODO - Parse chapter desc html
|
// TODO - Parse chapter desc html
|
||||||
// TODO - Parse book desc html
|
// TODO - Parse book desc html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function handleModelReference(Model $model, ZipExportModel $exportModel, ZipExportFiles $files): ?string
|
||||||
|
{
|
||||||
|
// TODO - References to other entities
|
||||||
|
|
||||||
|
// Handle attachment references
|
||||||
|
// No permission check needed here since they would only already exist in this
|
||||||
|
// reference context if already allowed via their entity access.
|
||||||
|
if ($model instanceof Attachment) {
|
||||||
|
if (isset($this->attachments[$model->id])) {
|
||||||
|
return "[[bsexport:attachment:{$model->id}]]";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle image references
|
||||||
|
if ($model instanceof Image) {
|
||||||
|
// Only handle gallery and drawio images
|
||||||
|
if ($model->type !== 'gallery' && $model->type !== 'drawio') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't expect images to be part of book/chapter content
|
||||||
|
if (!($exportModel instanceof ZipExportPage)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = $model->getPage();
|
||||||
|
if ($page && userCan('view', $page)) {
|
||||||
|
if (!isset($this->images[$model->id])) {
|
||||||
|
$exportImage = ZipExportImage::fromModel($model, $files);
|
||||||
|
$this->images[$model->id] = $exportImage;
|
||||||
|
$exportModel->images[] = $exportImage;
|
||||||
|
}
|
||||||
|
return "[[bsexport:image:{$model->id}]]";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,6 +133,19 @@ class ImageService
|
||||||
return $disk->get($image->path);
|
return $disk->get($image->path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the raw data content from an image.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
* @returns ?resource
|
||||||
|
*/
|
||||||
|
public function getImageStream(Image $image): mixed
|
||||||
|
{
|
||||||
|
$disk = $this->storage->getDisk();
|
||||||
|
|
||||||
|
return $disk->stream($image->path);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy an image along with its revisions, thumbnails and remaining folders.
|
* Destroy an image along with its revisions, thumbnails and remaining folders.
|
||||||
*
|
*
|
||||||
|
|
|
@ -55,6 +55,15 @@ class ImageStorageDisk
|
||||||
return $this->filesystem->get($this->adjustPathForDisk($path));
|
return $this->filesystem->get($this->adjustPathForDisk($path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a stream to the file at the given path.
|
||||||
|
* @returns ?resource
|
||||||
|
*/
|
||||||
|
public function stream(string $path): mixed
|
||||||
|
{
|
||||||
|
return $this->filesystem->readStream($this->adjustPathForDisk($path));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the given image data at the given path. Can choose to set
|
* Save the given image data at the given path. Can choose to set
|
||||||
* the image as public which will update its visibility after saving.
|
* the image as public which will update its visibility after saving.
|
||||||
|
|
|
@ -46,13 +46,12 @@ This can be done using the following format:
|
||||||
[[bsexport:<object>:<reference>]]
|
[[bsexport:<object>:<reference>]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Images and attachments are referenced via their file name within the `files/` directory.
|
References are to the `id` for data objects.
|
||||||
Otherwise, other content types are referenced by `id`.
|
|
||||||
Here's an example of each type of such reference that could be used:
|
Here's an example of each type of such reference that could be used:
|
||||||
|
|
||||||
```
|
```
|
||||||
[[bsexport:image:an-image-path.png]]
|
[[bsexport:image:22]]
|
||||||
[[bsexport:attachment:an-image-path.png]]
|
[[bsexport:attachment:55]]
|
||||||
[[bsexport:page:40]]
|
[[bsexport:page:40]]
|
||||||
[[bsexport:chapter:2]]
|
[[bsexport:chapter:2]]
|
||||||
[[bsexport:book:8]]
|
[[bsexport:book:8]]
|
||||||
|
@ -121,10 +120,14 @@ The page editor type, and edit content will be determined by what content is pro
|
||||||
|
|
||||||
#### Image
|
#### Image
|
||||||
|
|
||||||
|
- `id` - Number, optional, original ID for the page from exported system.
|
||||||
- `name` - String, required, name of image.
|
- `name` - String, required, name of image.
|
||||||
- `file` - String reference, required, reference to image file.
|
- `file` - String reference, required, reference to image file.
|
||||||
|
- `type` - String, required, must be 'gallery' or 'drawio'
|
||||||
|
|
||||||
File must be an image type accepted by BookStack (png, jpg, gif, webp)
|
File must be an image type accepted by BookStack (png, jpg, gif, webp).
|
||||||
|
Images of type 'drawio' are expected to be png with draw.io drawing data
|
||||||
|
embedded within it.
|
||||||
|
|
||||||
#### Attachment
|
#### Attachment
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user