Moved all existing export related app files into their new own dir.
--- /dev/null
+<?php
+
+namespace BookStack\Exceptions;
+
+class ZipExportException extends \Exception
+{
+}
<?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;
<?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;
<?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;
<?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;
<?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;
<?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;
<?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;
<?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;
--- /dev/null
+<?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;
+ }
+}
"ext-json": "*",
"ext-mbstring": "*",
"ext-xml": "*",
+ "ext-zip": "*",
"bacon/bacon-qr-code": "^3.0",
"doctrine/dbal": "^3.5",
"dompdf/dompdf": "^3.0",
}
```
-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`
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;
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']);
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']);
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;
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']);
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']);
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']);
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;