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() { $page = $this->entities->page(); $zipResp = $this->asEditor()->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $this->assertEquals([ 'id' => $page->id, 'name' => $page->name, 'html' => (new PageContent($page))->render(), 'priority' => $page->priority, 'attachments' => [], 'images' => [], 'tags' => [], ], $pageData); } public function test_page_export_with_markdown() { $page = $this->entities->page(); $markdown = "# My page\n\nwritten in markdown for export\n"; $page->markdown = $markdown; $page->save(); $zipResp = $this->asEditor()->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $this->assertEquals($markdown, $pageData['markdown']); $this->assertNotEmpty($pageData['html']); } public function test_page_export_with_tags() { $page = $this->entities->page(); $page->tags()->saveMany([ new Tag(['name' => 'Exporty', 'value' => 'Content', 'order' => 1]), new Tag(['name' => 'Another', 'value' => '', 'order' => 2]), ]); $zipResp = $this->asEditor()->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $this->assertEquals([ [ 'name' => 'Exporty', 'value' => 'Content', ], [ 'name' => 'Another', 'value' => '', ] ], $pageData['tags']); } public function test_page_export_with_images() { $this->asEditor(); $page = $this->entities->page(); $result = $this->files->uploadGalleryImageToPage($this, $page); $displayThumb = $result['response']->thumbs->gallery ?? ''; $page->html = '
'; $page->save(); $image = Image::findOrFail($result['response']->id); $zipResp = $this->asEditor()->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $this->assertCount(1, $pageData['images']); $imageData = $pageData['images'][0]; $this->assertEquals($image->id, $imageData['id']); $this->assertEquals($image->name, $imageData['name']); $this->assertEquals('gallery', $imageData['type']); $this->assertNotEmpty($imageData['file']); $filePath = $zip->extractPath("files/{$imageData['file']}"); $this->assertFileExists($filePath); $this->assertEquals(file_get_contents(public_path($image->path)), file_get_contents($filePath)); $this->assertEquals('', $pageData['html']); } public function test_page_export_file_attachments() { $contents = 'My great attachment content!'; $page = $this->entities->page(); $this->asAdmin(); $attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'PageAttachmentExport.txt', $contents, 'text/plain'); $zipResp = $this->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $this->assertCount(1, $pageData['attachments']); $attachmentData = $pageData['attachments'][0]; $this->assertEquals('PageAttachmentExport.txt', $attachmentData['name']); $this->assertEquals($attachment->id, $attachmentData['id']); $this->assertArrayNotHasKey('link', $attachmentData); $this->assertNotEmpty($attachmentData['file']); $fileRef = $attachmentData['file']; $filePath = $zip->extractPath("/files/$fileRef"); $this->assertFileExists($filePath); $this->assertEquals($contents, file_get_contents($filePath)); } public function test_page_export_link_attachments() { $page = $this->entities->page(); $this->asEditor(); $attachment = Attachment::factory()->create([ 'name' => 'My link attachment for export', 'path' => 'https://example.com/cats', 'external' => true, 'uploaded_to' => $page->id, 'order' => 1, ]); $zipResp = $this->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $this->assertCount(1, $pageData['attachments']); $attachmentData = $pageData['attachments'][0]; $this->assertEquals('My link attachment for export', $attachmentData['name']); $this->assertEquals($attachment->id, $attachmentData['id']); $this->assertEquals('https://example.com/cats', $attachmentData['link']); $this->assertArrayNotHasKey('file', $attachmentData); } public function test_book_export() { $book = $this->entities->book(); $book->tags()->saveMany(Tag::factory()->count(2)->make()); $zipResp = $this->asEditor()->get($book->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $this->assertArrayHasKey('book', $zip->data); $bookData = $zip->data['book']; $this->assertEquals($book->id, $bookData['id']); $this->assertEquals($book->name, $bookData['name']); $this->assertEquals($book->descriptionHtml(), $bookData['description_html']); $this->assertCount(2, $bookData['tags']); $this->assertCount($book->directPages()->count(), $bookData['pages']); $this->assertCount($book->chapters()->count(), $bookData['chapters']); $this->assertArrayNotHasKey('cover', $bookData); } public function test_book_export_with_cover_image() { $book = $this->entities->book(); $bookRepo = $this->app->make(BookRepo::class); $coverImageFile = $this->files->uploadedImage('cover.png'); $bookRepo->updateCoverImage($book, $coverImageFile); $coverImage = $book->cover()->first(); $zipResp = $this->asEditor()->get($book->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $this->assertArrayHasKey('cover', $zip->data['book']); $coverRef = $zip->data['book']['cover']; $coverPath = $zip->extractPath("/files/$coverRef"); $this->assertFileExists($coverPath); $this->assertEquals(file_get_contents(public_path($coverImage->path)), file_get_contents($coverPath)); } public function test_chapter_export() { $chapter = $this->entities->chapter(); $chapter->tags()->saveMany(Tag::factory()->count(2)->make()); $zipResp = $this->asEditor()->get($chapter->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $this->assertArrayHasKey('chapter', $zip->data); $chapterData = $zip->data['chapter']; $this->assertEquals($chapter->id, $chapterData['id']); $this->assertEquals($chapter->name, $chapterData['name']); $this->assertEquals($chapter->descriptionHtml(), $chapterData['description_html']); $this->assertCount(2, $chapterData['tags']); $this->assertEquals($chapter->priority, $chapterData['priority']); $this->assertCount($chapter->pages()->count(), $chapterData['pages']); } public function test_cross_reference_links_are_converted() { $book = $this->entities->bookHasChaptersAndPages(); $chapter = $book->chapters()->first(); $page = $chapter->pages()->first(); $book->description_html = ''; $book->save(); $chapter->description_html = ''; $chapter->save(); $page->html = ''; $page->save(); $zipResp = $this->asEditor()->get($book->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $bookData = $zip->data['book']; $chapterData = $bookData['chapters'][0]; $pageData = $chapterData['pages'][0]; $this->assertStringContainsString('href="[[bsexport:chapter:' . $chapter->id . ']]"', $bookData['description_html']); $this->assertStringContainsString('href="[[bsexport:page:' . $page->id . ']]#section2"', $chapterData['description_html']); $this->assertStringContainsString('href="[[bsexport:book:' . $book->id . ']]?view=true"', $pageData['html']); } public function test_book_and_chapter_description_links_to_images_in_pages_are_converted() { $book = $this->entities->bookHasChaptersAndPages(); $chapter = $book->chapters()->first(); $page = $chapter->pages()->first(); $this->asEditor(); $this->files->uploadGalleryImageToPage($this, $page); /** @var Image $image */ $image = Image::query()->where('type', '=', 'gallery') ->where('uploaded_to', '=', $page->id)->first(); $book->description_html = ''; $book->save(); $chapter->description_html = ''; $chapter->save(); $zipResp = $this->get($book->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $bookData = $zip->data['book']; $chapterData = $bookData['chapters'][0]; $this->assertStringContainsString('href="[[bsexport:image:' . $image->id . ']]"', $bookData['description_html']); $this->assertStringContainsString('href="[[bsexport:image:' . $image->id . ']]"', $chapterData['description_html']); } public function test_image_links_are_handled_when_using_external_storage_url() { $page = $this->entities->page(); $this->asEditor(); $this->files->uploadGalleryImageToPage($this, $page); /** @var Image $image */ $image = Image::query()->where('type', '=', 'gallery') ->where('uploaded_to', '=', $page->id)->first(); config()->set('filesystems.url', 'https://i.example.com/content'); $storageUrl = 'https://i.example.com/content/' . ltrim($image->path, '/'); $page->html = ''; $page->save(); $zipResp = $this->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $ref = '[[bsexport:image:' . $image->id . ']]'; $this->assertStringContainsString("Original URLStorage URL", $pageData['html']); } public function test_cross_reference_links_external_to_export_are_not_converted() { $page = $this->entities->page(); $page->html = ''; $page->save(); $zipResp = $this->asEditor()->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $this->assertStringContainsString('href="' . $page->book->getUrl() . '"', $pageData['html']); } public function test_attachments_links_are_converted() { $page = $this->entities->page(); $attachment = Attachment::factory()->create([ 'name' => 'My link attachment for export reference', 'path' => 'https://example.com/cats/ref', 'external' => true, 'uploaded_to' => $page->id, 'order' => 1, ]); $page->html = ''; $page->save(); $zipResp = $this->asEditor()->get($page->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['page']; $this->assertStringContainsString('href="[[bsexport:attachment:' . $attachment->id . ']]?open=true"', $pageData['html']); } public function test_links_in_markdown_are_parsed() { $chapter = $this->entities->chapterHasPages(); $page = $chapter->pages()->first(); $page->markdown = "[Link to chapter]({$chapter->getUrl()})"; $page->save(); $zipResp = $this->asEditor()->get($chapter->getUrl("/export/zip")); $zip = $this->extractZipResponse($zipResp); $pageData = $zip->data['chapter']['pages'][0]; $this->assertStringContainsString("[Link to chapter]([[bsexport:chapter:{$chapter->id}]])", $pageData['markdown']); } 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, ); } }