]> BookStack Code Mirror - bookstack/blob - tests/Exports/PdfExportTest.php
Sorting: Fixes during testing of sort rules
[bookstack] / tests / Exports / PdfExportTest.php
1 <?php
2
3 namespace Tests\Exports;
4
5 use BookStack\Entities\Models\Page;
6 use BookStack\Exceptions\PdfExportException;
7 use BookStack\Exports\PdfGenerator;
8 use FilesystemIterator;
9 use Tests\TestCase;
10
11 class PdfExportTest extends TestCase
12 {
13     public function test_page_pdf_export()
14     {
15         $page = $this->entities->page();
16         $this->asEditor();
17
18         $resp = $this->get($page->getUrl('/export/pdf'));
19         $resp->assertStatus(200);
20         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"');
21     }
22
23     public function test_book_pdf_export()
24     {
25         $page = $this->entities->page();
26         $book = $page->book;
27         $this->asEditor();
28
29         $resp = $this->get($book->getUrl('/export/pdf'));
30         $resp->assertStatus(200);
31         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf"');
32     }
33
34     public function test_chapter_pdf_export()
35     {
36         $chapter = $this->entities->chapter();
37         $this->asEditor();
38
39         $resp = $this->get($chapter->getUrl('/export/pdf'));
40         $resp->assertStatus(200);
41         $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"');
42     }
43
44
45     public function test_page_pdf_export_converts_iframes_to_links()
46     {
47         $page = Page::query()->first()->forceFill([
48             'html'     => '<iframe width="560" height="315" src="//www.youtube.com/embed/ShqUjt33uOs"></iframe>',
49         ]);
50         $page->save();
51
52         $pdfHtml = '';
53         $mockPdfGenerator = $this->mock(PdfGenerator::class);
54         $mockPdfGenerator->shouldReceive('fromHtml')
55             ->with(\Mockery::capture($pdfHtml))
56             ->andReturn('');
57         $mockPdfGenerator->shouldReceive('getActiveEngine')->andReturn(PdfGenerator::ENGINE_DOMPDF);
58
59         $this->asEditor()->get($page->getUrl('/export/pdf'));
60         $this->assertStringNotContainsString('iframe>', $pdfHtml);
61         $this->assertStringContainsString('<p><a href="https://www.youtube.com/embed/ShqUjt33uOs">https://www.youtube.com/embed/ShqUjt33uOs</a></p>', $pdfHtml);
62     }
63
64     public function test_page_pdf_export_opens_details_blocks()
65     {
66         $page = $this->entities->page()->forceFill([
67             'html'     => '<details><summary>Hello</summary><p>Content!</p></details>',
68         ]);
69         $page->save();
70
71         $pdfHtml = '';
72         $mockPdfGenerator = $this->mock(PdfGenerator::class);
73         $mockPdfGenerator->shouldReceive('fromHtml')
74             ->with(\Mockery::capture($pdfHtml))
75             ->andReturn('');
76         $mockPdfGenerator->shouldReceive('getActiveEngine')->andReturn(PdfGenerator::ENGINE_DOMPDF);
77
78         $this->asEditor()->get($page->getUrl('/export/pdf'));
79         $this->assertStringContainsString('<details open="open"', $pdfHtml);
80     }
81
82     public function test_wkhtmltopdf_only_used_when_allow_untrusted_is_true()
83     {
84         $page = $this->entities->page();
85
86         config()->set('exports.snappy.pdf_binary', '/abc123');
87         config()->set('app.allow_untrusted_server_fetching', false);
88
89         $resp = $this->asEditor()->get($page->getUrl('/export/pdf'));
90         $resp->assertStatus(200); // Sucessful response with invalid snappy binary indicates dompdf usage.
91
92         config()->set('app.allow_untrusted_server_fetching', true);
93         $resp = $this->get($page->getUrl('/export/pdf'));
94         $resp->assertStatus(500); // Bad response indicates wkhtml usage
95     }
96
97     public function test_pdf_command_option_used_if_set()
98     {
99         $page = $this->entities->page();
100         $command = 'cp {input_html_path} {output_pdf_path}';
101         config()->set('exports.pdf_command', $command);
102
103         $resp = $this->asEditor()->get($page->getUrl('/export/pdf'));
104         $download = $resp->getContent();
105
106         $this->assertStringContainsString(e($page->name), $download);
107         $this->assertStringContainsString('<html lang=', $download);
108     }
109
110     public function test_pdf_command_option_errors_if_output_path_not_written_to()
111     {
112         $page = $this->entities->page();
113         $command = 'echo "hi"';
114         config()->set('exports.pdf_command', $command);
115
116         $this->assertThrows(function () use ($page) {
117             $this->withoutExceptionHandling()->asEditor()->get($page->getUrl('/export/pdf'));
118         }, PdfExportException::class);
119     }
120
121     public function test_pdf_command_option_errors_if_command_returns_error_status()
122     {
123         $page = $this->entities->page();
124         $command = 'exit 1';
125         config()->set('exports.pdf_command', $command);
126
127         $this->assertThrows(function () use ($page) {
128             $this->withoutExceptionHandling()->asEditor()->get($page->getUrl('/export/pdf'));
129         }, PdfExportException::class);
130     }
131
132     public function test_pdf_command_timeout_option_limits_export_time()
133     {
134         $page = $this->entities->page();
135         $command = 'php -r \'sleep(4);\'';
136         config()->set('exports.pdf_command', $command);
137         config()->set('exports.pdf_command_timeout', 1);
138
139         $this->assertThrows(function () use ($page) {
140             $start = time();
141             $this->withoutExceptionHandling()->asEditor()->get($page->getUrl('/export/pdf'));
142
143             $this->assertTrue(time() < ($start + 3));
144         }, PdfExportException::class,
145             "PDF Export via command failed due to timeout at 1 second(s)");
146     }
147
148     public function test_pdf_command_option_does_not_leave_temp_files()
149     {
150         $tempDir = sys_get_temp_dir();
151         $startTempFileCount = iterator_count((new FileSystemIterator($tempDir, FilesystemIterator::SKIP_DOTS)));
152
153         $page = $this->entities->page();
154         $command = 'cp {input_html_path} {output_pdf_path}';
155         config()->set('exports.pdf_command', $command);
156
157         $this->asEditor()->get($page->getUrl('/export/pdf'));
158
159         $afterTempFileCount = iterator_count((new FileSystemIterator($tempDir, FilesystemIterator::SKIP_DOTS)));
160         $this->assertEquals($startTempFileCount, $afterTempFileCount);
161     }
162 }