mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-29 23:22:34 +01:00
ZIP Exports: Added entity cross refs, Started export tests
This commit is contained in:
parent
42ada66fdd
commit
484342f26a
@ -3,7 +3,9 @@
|
||||
namespace BookStack\Exports\Controllers;
|
||||
|
||||
use BookStack\Entities\Queries\BookQueries;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Exports\ExportFormatter;
|
||||
use BookStack\Exports\ZipExports\ZipExportBuilder;
|
||||
use BookStack\Http\Controller;
|
||||
use Throwable;
|
||||
|
||||
@ -63,4 +65,16 @@ class BookExportController extends Controller
|
||||
|
||||
return $this->download()->directly($textContent, $bookSlug . '.md');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a book to a contained ZIP export file.
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function zip(string $bookSlug, ZipExportBuilder $builder)
|
||||
{
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$zip = $builder->buildForBook($book);
|
||||
|
||||
return $this->download()->streamedDirectly(fopen($zip, 'r'), $bookSlug . '.zip', filesize($zip));
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace BookStack\Exports\Controllers;
|
||||
use BookStack\Entities\Queries\ChapterQueries;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Exports\ExportFormatter;
|
||||
use BookStack\Exports\ZipExports\ZipExportBuilder;
|
||||
use BookStack\Http\Controller;
|
||||
use Throwable;
|
||||
|
||||
@ -70,4 +71,16 @@ class ChapterExportController extends Controller
|
||||
|
||||
return $this->download()->directly($chapterText, $chapterSlug . '.md');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a book to a contained ZIP export file.
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function zip(string $bookSlug, string $chapterSlug, ZipExportBuilder $builder)
|
||||
{
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$zip = $builder->buildForChapter($chapter);
|
||||
|
||||
return $this->download()->streamedDirectly(fopen($zip, 'r'), $chapterSlug . '.zip', filesize($zip));
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
namespace BookStack\Exports\ZipExports;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportAttachment;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportBook;
|
||||
use BookStack\Exports\ZipExports\Models\ZipExportChapter;
|
||||
@ -107,8 +110,6 @@ class ZipExportReferences
|
||||
|
||||
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.
|
||||
@ -143,6 +144,15 @@ class ZipExportReferences
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle entity references
|
||||
if ($model instanceof Book && isset($this->books[$model->id])) {
|
||||
return "[[bsexport:book:{$model->id}]]";
|
||||
} else if ($model instanceof Chapter && isset($this->chapters[$model->id])) {
|
||||
return "[[bsexport:chapter:{$model->id}]]";
|
||||
} else if ($model instanceof Page && isset($this->pages[$model->id])) {
|
||||
return "[[bsexport:page:{$model->id}]]";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +132,7 @@ Route::middleware('auth')->group(function () {
|
||||
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::get('/books/{bookSlug}/chapter/{chapterSlug}/export/zip', [ExportControllers\ChapterExportController::class, 'zip']);
|
||||
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']);
|
||||
|
@ -2,14 +2,95 @@
|
||||
|
||||
namespace Tests\Exports;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Testing\TestResponse;
|
||||
use Tests\TestCase;
|
||||
use ZipArchive;
|
||||
|
||||
class ZipExportTest extends TestCase
|
||||
{
|
||||
public function test_page_export()
|
||||
public function test_export_results_in_zip_format()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
$response = $this->asEditor()->get($page->getUrl("/export/zip"));
|
||||
|
||||
$zipData = $response->streamedContent();
|
||||
$zipFile = tempnam(sys_get_temp_dir(), 'bstesta-');
|
||||
file_put_contents($zipFile, $zipData);
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($zipFile, ZipArchive::RDONLY);
|
||||
|
||||
$this->assertNotFalse($zip->locateName('data.json'));
|
||||
$this->assertNotFalse($zip->locateName('files/'));
|
||||
|
||||
$data = json_decode($zip->getFromName('data.json'), true);
|
||||
$this->assertIsArray($data);
|
||||
$this->assertGreaterThan(0, count($data));
|
||||
|
||||
$zip->close();
|
||||
unlink($zipFile);
|
||||
}
|
||||
|
||||
public function test_export_metadata()
|
||||
{
|
||||
$page = $this->entities->page();
|
||||
$zipResp = $this->asEditor()->get($page->getUrl("/export/zip"));
|
||||
$zip = $this->extractZipResponse($zipResp);
|
||||
|
||||
$this->assertEquals($page->id, $zip->data['page']['id'] ?? null);
|
||||
$this->assertArrayNotHasKey('book', $zip->data);
|
||||
$this->assertArrayNotHasKey('chapter', $zip->data);
|
||||
|
||||
$now = time();
|
||||
$date = Carbon::parse($zip->data['exported_at'])->unix();
|
||||
$this->assertLessThan($now + 2, $date);
|
||||
$this->assertGreaterThan($now - 2, $date);
|
||||
|
||||
$version = trim(file_get_contents(base_path('version')));
|
||||
$this->assertEquals($version, $zip->data['instance']['version']);
|
||||
|
||||
$instanceId = decrypt($zip->data['instance']['id_ciphertext']);
|
||||
$this->assertEquals('bookstack', $instanceId);
|
||||
}
|
||||
|
||||
public function test_page_export()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public function test_book_export()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public function test_chapter_export()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
protected function extractZipResponse(TestResponse $response): ZipResultData
|
||||
{
|
||||
$zipData = $response->streamedContent();
|
||||
$zipFile = tempnam(sys_get_temp_dir(), 'bstest-');
|
||||
|
||||
file_put_contents($zipFile, $zipData);
|
||||
$extractDir = tempnam(sys_get_temp_dir(), 'bstestextracted-');
|
||||
if (file_exists($extractDir)) {
|
||||
unlink($extractDir);
|
||||
}
|
||||
mkdir($extractDir);
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($zipFile, ZipArchive::RDONLY);
|
||||
$zip->extractTo($extractDir);
|
||||
|
||||
$dataJson = file_get_contents($extractDir . DIRECTORY_SEPARATOR . "data.json");
|
||||
$data = json_decode($dataJson, true);
|
||||
|
||||
return new ZipResultData(
|
||||
$zipFile,
|
||||
$extractDir,
|
||||
$data,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
13
tests/Exports/ZipResultData.php
Normal file
13
tests/Exports/ZipResultData.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Exports;
|
||||
|
||||
class ZipResultData
|
||||
{
|
||||
public function __construct(
|
||||
public string $zipPath,
|
||||
public string $extractedDirPath,
|
||||
public array $data,
|
||||
) {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user