]> BookStack Code Mirror - bookstack/commitdiff
ZIP Exports: Finished up format doc, move files, started builder
authorDan Brown <redacted>
Tue, 15 Oct 2024 15:14:11 +0000 (16:14 +0100)
committerDan Brown <redacted>
Tue, 15 Oct 2024 15:14:11 +0000 (16:14 +0100)
Moved all existing export related app files into their new own dir.

15 files changed:
app/Exceptions/ZipExportException.php [new file with mode: 0644]
app/Exports/Controllers/BookExportApiController.php [moved from app/Entities/Controllers/BookExportApiController.php with 95% similarity]
app/Exports/Controllers/BookExportController.php [moved from app/Entities/Controllers/BookExportController.php with 95% similarity]
app/Exports/Controllers/ChapterExportApiController.php [moved from app/Entities/Controllers/ChapterExportApiController.php with 95% similarity]
app/Exports/Controllers/ChapterExportController.php [moved from app/Entities/Controllers/ChapterExportController.php with 96% similarity]
app/Exports/Controllers/PageExportApiController.php [moved from app/Entities/Controllers/PageExportApiController.php with 95% similarity]
app/Exports/Controllers/PageExportController.php [moved from app/Entities/Controllers/PageExportController.php with 96% similarity]
app/Exports/ExportFormatter.php [moved from app/Entities/Tools/ExportFormatter.php with 98% similarity]
app/Exports/PdfGenerator.php [moved from app/Entities/Tools/PdfGenerator.php with 99% similarity]
app/Exports/ZipExportBuilder.php [new file with mode: 0644]
composer.json
dev/docs/portable-zip-file-format.md
routes/api.php
routes/web.php
tests/Entity/ExportTest.php

diff --git a/app/Exceptions/ZipExportException.php b/app/Exceptions/ZipExportException.php
new file mode 100644 (file)
index 0000000..b2c811e
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+namespace BookStack\Exceptions;
+
+class ZipExportException extends \Exception
+{
+}
similarity index 95%
rename from app/Entities/Controllers/BookExportApiController.php
rename to app/Exports/Controllers/BookExportApiController.php
index 1161ddb8886964423f088630424317225914eb74..164946b0c781d30d472bcda8cfb4066e9b71763d 100644 (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;
 
similarity index 95%
rename from app/Entities/Controllers/BookExportController.php
rename to app/Exports/Controllers/BookExportController.php
index 5c1a964c1e1037c2d60bd789cb5a049acb71b1c2..36906b6ad7bbad35de90b48304709f2dc1416769 100644 (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;
 
similarity index 95%
rename from app/Entities/Controllers/ChapterExportApiController.php
rename to app/Exports/Controllers/ChapterExportApiController.php
index ceb2522b2118212657a9a1bbab666fe42c28581c..9914e2b7fbed242405e2bd41e0555674eb9f5ca0 100644 (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;
 
similarity index 96%
rename from app/Entities/Controllers/ChapterExportController.php
rename to app/Exports/Controllers/ChapterExportController.php
index ead601ab46b432ca541c74df4d77a15f7cd8aeda..d85b90dcb412d82be0d0823163ccf7ce448a4a5f 100644 (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;
 
similarity index 95%
rename from app/Entities/Controllers/PageExportApiController.php
rename to app/Exports/Controllers/PageExportApiController.php
index 693760bc8e727ddb6ce13a8c004f3a808f816670..c6e20b615d2426f92af9afbd03d2cceac492b0d2 100644 (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;
 
similarity index 96%
rename from app/Entities/Controllers/PageExportController.php
rename to app/Exports/Controllers/PageExportController.php
index be97f1930bdd28e61f5f14d98647b336cc1439cf..a4e7aae879dcc6a7e8f894267e64712a1c632591 100644 (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;
 
similarity index 98%
rename from app/Entities/Tools/ExportFormatter.php
rename to app/Exports/ExportFormatter.php
index beddfe8e6e0f08cb6e0f373a6d46d202a9b6056e..4f78830b075cb49f053d88672610ff365684abed 100644 (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;
similarity index 99%
rename from app/Entities/Tools/PdfGenerator.php
rename to app/Exports/PdfGenerator.php
index 79cd1b02f7ff1daac18cbd30c6c1c3679ed0c29f..0524e063fb259ca22e8e9a6e3c9a22352bb6e2ac 100644 (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;
 
diff --git a/app/Exports/ZipExportBuilder.php b/app/Exports/ZipExportBuilder.php
new file mode 100644 (file)
index 0000000..d1a7b6b
--- /dev/null
@@ -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;
+    }
+}
index 5c54774f1e274f355178d41b78b60fae33ec853f..3680a2c6aa292a2ded16ebd1aefe8e5ebfe686b8 100644 (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",
index dc21bf8e58e6859eaf8cdc62701a280df12b1902..d5635bd398d69ae973458d4449584346c0622399 100644 (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]]`
-
-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).
+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:
+
+```
+[[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`
 
index c0919d3247ba12eae80dddc7199f9a3ca020e261..71036485597d20d41e8b500aeded1341496d983b 100644 (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']);
index 81b938f32eccbde217f15ded0f9e572935c122bf..5220684c0b03b5233f07a0cd09620ce4f01402ba 100644 (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']);
index 7aafa3b79277414cdcca202dfd1c17732b81cb2a..11cfddb206ebf9a9276ffffda61e58b3b6b103a2 100644 (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;