Skip to content

Commit 484342f

Browse files
committed
ZIP Exports: Added entity cross refs, Started export tests
1 parent 42ada66 commit 484342f

File tree

6 files changed

+136
-4
lines changed

6 files changed

+136
-4
lines changed

app/Exports/Controllers/BookExportController.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace BookStack\Exports\Controllers;
44

55
use BookStack\Entities\Queries\BookQueries;
6+
use BookStack\Exceptions\NotFoundException;
67
use BookStack\Exports\ExportFormatter;
8+
use BookStack\Exports\ZipExports\ZipExportBuilder;
79
use BookStack\Http\Controller;
810
use Throwable;
911

@@ -63,4 +65,16 @@ public function markdown(string $bookSlug)
6365

6466
return $this->download()->directly($textContent, $bookSlug . '.md');
6567
}
68+
69+
/**
70+
* Export a book to a contained ZIP export file.
71+
* @throws NotFoundException
72+
*/
73+
public function zip(string $bookSlug, ZipExportBuilder $builder)
74+
{
75+
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
76+
$zip = $builder->buildForBook($book);
77+
78+
return $this->download()->streamedDirectly(fopen($zip, 'r'), $bookSlug . '.zip', filesize($zip));
79+
}
6680
}

app/Exports/Controllers/ChapterExportController.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BookStack\Entities\Queries\ChapterQueries;
66
use BookStack\Exceptions\NotFoundException;
77
use BookStack\Exports\ExportFormatter;
8+
use BookStack\Exports\ZipExports\ZipExportBuilder;
89
use BookStack\Http\Controller;
910
use Throwable;
1011

@@ -70,4 +71,16 @@ public function markdown(string $bookSlug, string $chapterSlug)
7071

7172
return $this->download()->directly($chapterText, $chapterSlug . '.md');
7273
}
74+
75+
/**
76+
* Export a book to a contained ZIP export file.
77+
* @throws NotFoundException
78+
*/
79+
public function zip(string $bookSlug, string $chapterSlug, ZipExportBuilder $builder)
80+
{
81+
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
82+
$zip = $builder->buildForChapter($chapter);
83+
84+
return $this->download()->streamedDirectly(fopen($zip, 'r'), $chapterSlug . '.zip', filesize($zip));
85+
}
7386
}

app/Exports/ZipExports/ZipExportReferences.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace BookStack\Exports\ZipExports;
44

55
use BookStack\App\Model;
6+
use BookStack\Entities\Models\Book;
7+
use BookStack\Entities\Models\Chapter;
8+
use BookStack\Entities\Models\Page;
69
use BookStack\Exports\ZipExports\Models\ZipExportAttachment;
710
use BookStack\Exports\ZipExports\Models\ZipExportBook;
811
use BookStack\Exports\ZipExports\Models\ZipExportChapter;
@@ -107,8 +110,6 @@ public function buildReferences(ZipExportFiles $files): void
107110

108111
protected function handleModelReference(Model $model, ZipExportModel $exportModel, ZipExportFiles $files): ?string
109112
{
110-
// TODO - References to other entities
111-
112113
// Handle attachment references
113114
// No permission check needed here since they would only already exist in this
114115
// reference context if already allowed via their entity access.
@@ -143,6 +144,15 @@ protected function handleModelReference(Model $model, ZipExportModel $exportMode
143144
return null;
144145
}
145146

147+
// Handle entity references
148+
if ($model instanceof Book && isset($this->books[$model->id])) {
149+
return "[[bsexport:book:{$model->id}]]";
150+
} else if ($model instanceof Chapter && isset($this->chapters[$model->id])) {
151+
return "[[bsexport:chapter:{$model->id}]]";
152+
} else if ($model instanceof Page && isset($this->pages[$model->id])) {
153+
return "[[bsexport:page:{$model->id}]]";
154+
}
155+
146156
return null;
147157
}
148158
}

routes/web.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/html', [ExportControllers\ChapterExportController::class, 'html']);
133133
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/markdown', [ExportControllers\ChapterExportController::class, 'markdown']);
134134
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/plaintext', [ExportControllers\ChapterExportController::class, 'plainText']);
135+
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/zip', [ExportControllers\ChapterExportController::class, 'zip']);
135136
Route::put('/books/{bookSlug}/chapter/{chapterSlug}/permissions', [PermissionsController::class, 'updateForChapter']);
136137
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/references', [ReferenceController::class, 'chapter']);
137138
Route::get('/books/{bookSlug}/chapter/{chapterSlug}/delete', [EntityControllers\ChapterController::class, 'showDelete']);

tests/Exports/ZipExportTest.php

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,95 @@
22

33
namespace Tests\Exports;
44

5-
use BookStack\Entities\Models\Book;
5+
use Illuminate\Support\Carbon;
6+
use Illuminate\Testing\TestResponse;
67
use Tests\TestCase;
8+
use ZipArchive;
79

810
class ZipExportTest extends TestCase
911
{
10-
public function test_page_export()
12+
public function test_export_results_in_zip_format()
13+
{
14+
$page = $this->entities->page();
15+
$response = $this->asEditor()->get($page->getUrl("/export/zip"));
16+
17+
$zipData = $response->streamedContent();
18+
$zipFile = tempnam(sys_get_temp_dir(), 'bstesta-');
19+
file_put_contents($zipFile, $zipData);
20+
$zip = new ZipArchive();
21+
$zip->open($zipFile, ZipArchive::RDONLY);
22+
23+
$this->assertNotFalse($zip->locateName('data.json'));
24+
$this->assertNotFalse($zip->locateName('files/'));
25+
26+
$data = json_decode($zip->getFromName('data.json'), true);
27+
$this->assertIsArray($data);
28+
$this->assertGreaterThan(0, count($data));
29+
30+
$zip->close();
31+
unlink($zipFile);
32+
}
33+
34+
public function test_export_metadata()
1135
{
1236
$page = $this->entities->page();
37+
$zipResp = $this->asEditor()->get($page->getUrl("/export/zip"));
38+
$zip = $this->extractZipResponse($zipResp);
39+
40+
$this->assertEquals($page->id, $zip->data['page']['id'] ?? null);
41+
$this->assertArrayNotHasKey('book', $zip->data);
42+
$this->assertArrayNotHasKey('chapter', $zip->data);
43+
44+
$now = time();
45+
$date = Carbon::parse($zip->data['exported_at'])->unix();
46+
$this->assertLessThan($now + 2, $date);
47+
$this->assertGreaterThan($now - 2, $date);
48+
49+
$version = trim(file_get_contents(base_path('version')));
50+
$this->assertEquals($version, $zip->data['instance']['version']);
51+
52+
$instanceId = decrypt($zip->data['instance']['id_ciphertext']);
53+
$this->assertEquals('bookstack', $instanceId);
54+
}
55+
56+
public function test_page_export()
57+
{
58+
// TODO
59+
}
60+
61+
public function test_book_export()
62+
{
63+
// TODO
64+
}
65+
66+
public function test_chapter_export()
67+
{
1368
// TODO
1469
}
70+
71+
protected function extractZipResponse(TestResponse $response): ZipResultData
72+
{
73+
$zipData = $response->streamedContent();
74+
$zipFile = tempnam(sys_get_temp_dir(), 'bstest-');
75+
76+
file_put_contents($zipFile, $zipData);
77+
$extractDir = tempnam(sys_get_temp_dir(), 'bstestextracted-');
78+
if (file_exists($extractDir)) {
79+
unlink($extractDir);
80+
}
81+
mkdir($extractDir);
82+
83+
$zip = new ZipArchive();
84+
$zip->open($zipFile, ZipArchive::RDONLY);
85+
$zip->extractTo($extractDir);
86+
87+
$dataJson = file_get_contents($extractDir . DIRECTORY_SEPARATOR . "data.json");
88+
$data = json_decode($dataJson, true);
89+
90+
return new ZipResultData(
91+
$zipFile,
92+
$extractDir,
93+
$data,
94+
);
95+
}
1596
}

tests/Exports/ZipResultData.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Tests\Exports;
4+
5+
class ZipResultData
6+
{
7+
public function __construct(
8+
public string $zipPath,
9+
public string $extractedDirPath,
10+
public array $data,
11+
) {
12+
}
13+
}

0 commit comments

Comments
 (0)