1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2024-10-29 23:22:34 +01:00

ZIP Exports: Finished up format doc, move files, started builder

Moved all existing export related app files into their new own dir.
This commit is contained in:
Dan Brown 2024-10-15 16:14:11 +01:00
parent 42bd07d733
commit 42b9700673
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
15 changed files with 118 additions and 53 deletions

View File

@ -0,0 +1,7 @@
<?php
namespace BookStack\Exceptions;
class ZipExportException extends \Exception
{
}

View File

@ -1,9 +1,9 @@
<?php
namespace BookStack\Entities\Controllers;
namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Exports\ExportFormatter;
use BookStack\Http\ApiController;
use Throwable;

View File

@ -1,9 +1,9 @@
<?php
namespace BookStack\Entities\Controllers;
namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Exports\ExportFormatter;
use BookStack\Http\Controller;
use Throwable;

View File

@ -1,9 +1,9 @@
<?php
namespace BookStack\Entities\Controllers;
namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\ChapterQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Exports\ExportFormatter;
use BookStack\Http\ApiController;
use Throwable;

View File

@ -1,10 +1,10 @@
<?php
namespace BookStack\Entities\Controllers;
namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\ChapterQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exports\ExportFormatter;
use BookStack\Http\Controller;
use Throwable;

View File

@ -1,9 +1,9 @@
<?php
namespace BookStack\Entities\Controllers;
namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Exports\ExportFormatter;
use BookStack\Http\ApiController;
use Throwable;

View File

@ -1,11 +1,11 @@
<?php
namespace BookStack\Entities\Controllers;
namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Entities\Tools\PageContent;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exports\ExportFormatter;
use BookStack\Http\Controller;
use Throwable;

View File

@ -1,11 +1,13 @@
<?php
namespace BookStack\Entities\Tools;
namespace BookStack\Exports;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\BookContents;
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
use BookStack\Entities\Tools\PageContent;
use BookStack\Uploads\ImageService;
use BookStack\Util\CspService;
use BookStack\Util\HtmlDocument;

View File

@ -1,10 +1,10 @@
<?php
namespace BookStack\Entities\Tools;
namespace BookStack\Exports;
use BookStack\Exceptions\PdfExportException;
use Knp\Snappy\Pdf as SnappyPdf;
use Dompdf\Dompdf;
use Knp\Snappy\Pdf as SnappyPdf;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;

View File

@ -0,0 +1,48 @@
<?php
namespace BookStack\Exports;
use BookStack\Entities\Models\Page;
use BookStack\Exceptions\ZipExportException;
use ZipArchive;
class ZipExportBuilder
{
protected array $data = [];
/**
* @throws ZipExportException
*/
public function buildForPage(Page $page): string
{
$this->data['page'] = [
'id' => $page->id,
];
return $this->build();
}
/**
* @throws ZipExportException
*/
protected function build(): string
{
$this->data['exported_at'] = date(DATE_ATOM);
$this->data['instance'] = [
'version' => trim(file_get_contents(base_path('version'))),
'id_ciphertext' => encrypt('bookstack'),
];
$zipFile = tempnam(sys_get_temp_dir(), 'bszip-');
$zip = new ZipArchive();
$opened = $zip->open($zipFile, ZipArchive::CREATE);
if ($opened !== true) {
throw new ZipExportException('Failed to create zip file for export.');
}
$zip->addFromString('data.json', json_encode($this->data));
$zip->addEmptyDir('files');
return $zipFile;
}
}

View File

@ -16,6 +16,7 @@
"ext-json": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"ext-zip": "*",
"bacon/bacon-qr-code": "^3.0",
"doctrine/dbal": "^3.5",
"dompdf/dompdf": "^3.0",

View File

@ -39,18 +39,24 @@ Some properties in the export data JSON are indicated as `String reference`, and
}
```
TODO - Jotting out idea below.
Would need to validate image/attachment paths against image/attachments listed across all pages in export.
Probably good to ensure filenames are ascii-alpha-num.
`[[bsexport:image:an-image-path.png]]`
`[[bsexport:attachment:an-image-path.png]]`
`[[bsexport:page:1]]`
`[[bsexport:chapter:2]]`
`[[bsexport:book:3]]`
Within HTML and markdown content, you may require references across to other items within the export content.
This can be done using the following format:
TODO - Define how we reference across content:
TODO - References from in-content to file URLs
TODO - References from in-content to in-export content (page cross links within same export).
```
[[bsexport:<object>:<reference>]]
```
Images and attachments are referenced via their file name within the `files/` directory.
Otherwise, other content types are referenced by `id`.
Here's an example of each type of such reference that could be used:
```
[[bsexport:image:an-image-path.png]]
[[bsexport:attachment:an-image-path.png]]
[[bsexport:page:40]]
[[bsexport:chapter:2]]
[[bsexport:book:8]]
```
## Export Data - `data.json`

View File

@ -9,6 +9,7 @@
use BookStack\Activity\Controllers\AuditLogApiController;
use BookStack\Api\ApiDocsController;
use BookStack\Entities\Controllers as EntityControllers;
use BookStack\Exports\Controllers as ExportControllers;
use BookStack\Permissions\ContentPermissionApiController;
use BookStack\Search\SearchApiController;
use BookStack\Uploads\Controllers\AttachmentApiController;
@ -31,21 +32,20 @@ Route::get('books/{id}', [EntityControllers\BookApiController::class, 'read']);
Route::put('books/{id}', [EntityControllers\BookApiController::class, 'update']);
Route::delete('books/{id}', [EntityControllers\BookApiController::class, 'delete']);
Route::get('books/{id}/export/html', [EntityControllers\BookExportApiController::class, 'exportHtml']);
Route::get('books/{id}/export/pdf', [EntityControllers\BookExportApiController::class, 'exportPdf']);
Route::get('books/{id}/export/plaintext', [EntityControllers\BookExportApiController::class, 'exportPlainText']);
Route::get('books/{id}/export/markdown', [EntityControllers\BookExportApiController::class, 'exportMarkdown']);
Route::get('books/{id}/export/html', [ExportControllers\BookExportApiController::class, 'exportHtml']);
Route::get('books/{id}/export/pdf', [ExportControllers\BookExportApiController::class, 'exportPdf']);
Route::get('books/{id}/export/plaintext', [ExportControllers\BookExportApiController::class, 'exportPlainText']);
Route::get('books/{id}/export/markdown', [ExportControllers\BookExportApiController::class, 'exportMarkdown']);
Route::get('chapters', [EntityControllers\ChapterApiController::class, 'list']);
Route::post('chapters', [EntityControllers\ChapterApiController::class, 'create']);
Route::get('chapters/{id}', [EntityControllers\ChapterApiController::class, 'read']);
Route::put('chapters/{id}', [EntityControllers\ChapterApiController::class, 'update']);
Route::delete('chapters/{id}', [EntityControllers\ChapterApiController::class, 'delete']);
Route::get('chapters/{id}/export/html', [EntityControllers\ChapterExportApiController::class, 'exportHtml']);
Route::get('chapters/{id}/export/pdf', [EntityControllers\ChapterExportApiController::class, 'exportPdf']);
Route::get('chapters/{id}/export/plaintext', [EntityControllers\ChapterExportApiController::class, 'exportPlainText']);
Route::get('chapters/{id}/export/markdown', [EntityControllers\ChapterExportApiController::class, 'exportMarkdown']);
Route::get('chapters/{id}/export/html', [ExportControllers\ChapterExportApiController::class, 'exportHtml']);
Route::get('chapters/{id}/export/pdf', [ExportControllers\ChapterExportApiController::class, 'exportPdf']);
Route::get('chapters/{id}/export/plaintext', [ExportControllers\ChapterExportApiController::class, 'exportPlainText']);
Route::get('chapters/{id}/export/markdown', [ExportControllers\ChapterExportApiController::class, 'exportMarkdown']);
Route::get('pages', [EntityControllers\PageApiController::class, 'list']);
Route::post('pages', [EntityControllers\PageApiController::class, 'create']);
@ -53,10 +53,10 @@ Route::get('pages/{id}', [EntityControllers\PageApiController::class, 'read']);
Route::put('pages/{id}', [EntityControllers\PageApiController::class, 'update']);
Route::delete('pages/{id}', [EntityControllers\PageApiController::class, 'delete']);
Route::get('pages/{id}/export/html', [EntityControllers\PageExportApiController::class, 'exportHtml']);
Route::get('pages/{id}/export/pdf', [EntityControllers\PageExportApiController::class, 'exportPdf']);
Route::get('pages/{id}/export/plaintext', [EntityControllers\PageExportApiController::class, 'exportPlainText']);
Route::get('pages/{id}/export/markdown', [EntityControllers\PageExportApiController::class, 'exportMarkdown']);
Route::get('pages/{id}/export/html', [ExportControllers\PageExportApiController::class, 'exportHtml']);
Route::get('pages/{id}/export/pdf', [ExportControllers\PageExportApiController::class, 'exportPdf']);
Route::get('pages/{id}/export/plaintext', [ExportControllers\PageExportApiController::class, 'exportPlainText']);
Route::get('pages/{id}/export/markdown', [ExportControllers\PageExportApiController::class, 'exportMarkdown']);
Route::get('image-gallery', [ImageGalleryApiController::class, 'list']);
Route::post('image-gallery', [ImageGalleryApiController::class, 'create']);

View File

@ -7,6 +7,7 @@ use BookStack\Api\UserApiTokenController;
use BookStack\App\HomeController;
use BookStack\App\MetaController;
use BookStack\Entities\Controllers as EntityControllers;
use BookStack\Exports\Controllers as ExportControllers;
use BookStack\Http\Middleware\VerifyCsrfToken;
use BookStack\Permissions\PermissionsController;
use BookStack\References\ReferenceController;
@ -74,11 +75,11 @@ Route::middleware('auth')->group(function () {
Route::get('/books/{bookSlug}/sort', [EntityControllers\BookSortController::class, 'show']);
Route::put('/books/{bookSlug}/sort', [EntityControllers\BookSortController::class, 'update']);
Route::get('/books/{slug}/references', [ReferenceController::class, 'book']);
Route::get('/books/{bookSlug}/export/html', [EntityControllers\BookExportController::class, 'html']);
Route::get('/books/{bookSlug}/export/pdf', [EntityControllers\BookExportController::class, 'pdf']);
Route::get('/books/{bookSlug}/export/markdown', [EntityControllers\BookExportController::class, 'markdown']);
Route::get('/books/{bookSlug}/export/zip', [EntityControllers\BookExportController::class, 'zip']);
Route::get('/books/{bookSlug}/export/plaintext', [EntityControllers\BookExportController::class, 'plainText']);
Route::get('/books/{bookSlug}/export/html', [ExportControllers\BookExportController::class, 'html']);
Route::get('/books/{bookSlug}/export/pdf', [ExportControllers\BookExportController::class, 'pdf']);
Route::get('/books/{bookSlug}/export/markdown', [ExportControllers\BookExportController::class, 'markdown']);
Route::get('/books/{bookSlug}/export/zip', [ExportControllers\BookExportController::class, 'zip']);
Route::get('/books/{bookSlug}/export/plaintext', [ExportControllers\BookExportController::class, 'plainText']);
// Pages
Route::get('/books/{bookSlug}/create-page', [EntityControllers\PageController::class, 'create']);
@ -86,10 +87,10 @@ Route::middleware('auth')->group(function () {
Route::get('/books/{bookSlug}/draft/{pageId}', [EntityControllers\PageController::class, 'editDraft']);
Route::post('/books/{bookSlug}/draft/{pageId}', [EntityControllers\PageController::class, 'store']);
Route::get('/books/{bookSlug}/page/{pageSlug}', [EntityControllers\PageController::class, 'show']);
Route::get('/books/{bookSlug}/page/{pageSlug}/export/pdf', [EntityControllers\PageExportController::class, 'pdf']);
Route::get('/books/{bookSlug}/page/{pageSlug}/export/html', [EntityControllers\PageExportController::class, 'html']);
Route::get('/books/{bookSlug}/page/{pageSlug}/export/markdown', [EntityControllers\PageExportController::class, 'markdown']);
Route::get('/books/{bookSlug}/page/{pageSlug}/export/plaintext', [EntityControllers\PageExportController::class, 'plainText']);
Route::get('/books/{bookSlug}/page/{pageSlug}/export/pdf', [ExportControllers\PageExportController::class, 'pdf']);
Route::get('/books/{bookSlug}/page/{pageSlug}/export/html', [ExportControllers\PageExportController::class, 'html']);
Route::get('/books/{bookSlug}/page/{pageSlug}/export/markdown', [ExportControllers\PageExportController::class, 'markdown']);
Route::get('/books/{bookSlug}/page/{pageSlug}/export/plaintext', [ExportControllers\PageExportController::class, 'plainText']);
Route::get('/books/{bookSlug}/page/{pageSlug}/edit', [EntityControllers\PageController::class, 'edit']);
Route::get('/books/{bookSlug}/page/{pageSlug}/move', [EntityControllers\PageController::class, 'showMove']);
Route::put('/books/{bookSlug}/page/{pageSlug}/move', [EntityControllers\PageController::class, 'move']);
@ -126,10 +127,10 @@ Route::middleware('auth')->group(function () {
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/edit', [EntityControllers\ChapterController::class, 'edit']);
Route::post('/books/{bookSlug}/chapter/{chapterSlug}/convert-to-book', [EntityControllers\ChapterController::class, 'convertToBook']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/permissions', [PermissionsController::class, 'showForChapter']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/pdf', [EntityControllers\ChapterExportController::class, 'pdf']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/html', [EntityControllers\ChapterExportController::class, 'html']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/markdown', [EntityControllers\ChapterExportController::class, 'markdown']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/plaintext', [EntityControllers\ChapterExportController::class, 'plainText']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/pdf', [ExportControllers\ChapterExportController::class, 'pdf']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/html', [ExportControllers\ChapterExportController::class, 'html']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/markdown', [ExportControllers\ChapterExportController::class, 'markdown']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/plaintext', [ExportControllers\ChapterExportController::class, 'plainText']);
Route::put('/books/{bookSlug}/chapter/{chapterSlug}/permissions', [PermissionsController::class, 'updateForChapter']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/references', [ReferenceController::class, 'chapter']);
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/delete', [EntityControllers\ChapterController::class, 'showDelete']);

View File

@ -5,8 +5,8 @@ namespace Tests\Entity;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\PdfGenerator;
use BookStack\Exceptions\PdfExportException;
use BookStack\Exports\PdfGenerator;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;