]> BookStack Code Mirror - bookstack/blob - app/Entities/Tools/ExportFormatter.php
Apply fixes from StyleCI
[bookstack] / app / Entities / Tools / ExportFormatter.php
1 <?php
2
3 namespace BookStack\Entities\Tools;
4
5 use BookStack\Entities\Models\Book;
6 use BookStack\Entities\Models\Chapter;
7 use BookStack\Entities\Models\Page;
8 use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
9 use BookStack\Uploads\ImageService;
10 use DomPDF;
11 use Exception;
12 use SnappyPDF;
13 use Throwable;
14
15 class ExportFormatter
16 {
17     protected $imageService;
18
19     /**
20      * ExportService constructor.
21      */
22     public function __construct(ImageService $imageService)
23     {
24         $this->imageService = $imageService;
25     }
26
27     /**
28      * Convert a page to a self-contained HTML file.
29      * Includes required CSS & image content. Images are base64 encoded into the HTML.
30      *
31      * @throws Throwable
32      */
33     public function pageToContainedHtml(Page $page)
34     {
35         $page->html = (new PageContent($page))->render();
36         $pageHtml = view('pages.export', [
37             'page'   => $page,
38             'format' => 'html',
39         ])->render();
40
41         return $this->containHtml($pageHtml);
42     }
43
44     /**
45      * Convert a chapter to a self-contained HTML file.
46      *
47      * @throws Throwable
48      */
49     public function chapterToContainedHtml(Chapter $chapter)
50     {
51         $pages = $chapter->getVisiblePages();
52         $pages->each(function ($page) {
53             $page->html = (new PageContent($page))->render();
54         });
55         $html = view('chapters.export', [
56             'chapter' => $chapter,
57             'pages'   => $pages,
58             'format'  => 'html',
59         ])->render();
60
61         return $this->containHtml($html);
62     }
63
64     /**
65      * Convert a book to a self-contained HTML file.
66      *
67      * @throws Throwable
68      */
69     public function bookToContainedHtml(Book $book)
70     {
71         $bookTree = (new BookContents($book))->getTree(false, true);
72         $html = view('books.export', [
73             'book'         => $book,
74             'bookChildren' => $bookTree,
75             'format'       => 'html',
76         ])->render();
77
78         return $this->containHtml($html);
79     }
80
81     /**
82      * Convert a page to a PDF file.
83      *
84      * @throws Throwable
85      */
86     public function pageToPdf(Page $page)
87     {
88         $page->html = (new PageContent($page))->render();
89         $html = view('pages.export', [
90             'page'   => $page,
91             'format' => 'pdf',
92         ])->render();
93
94         return $this->htmlToPdf($html);
95     }
96
97     /**
98      * Convert a chapter to a PDF file.
99      *
100      * @throws Throwable
101      */
102     public function chapterToPdf(Chapter $chapter)
103     {
104         $pages = $chapter->getVisiblePages();
105         $pages->each(function ($page) {
106             $page->html = (new PageContent($page))->render();
107         });
108
109         $html = view('chapters.export', [
110             'chapter' => $chapter,
111             'pages'   => $pages,
112             'format'  => 'pdf',
113         ])->render();
114
115         return $this->htmlToPdf($html);
116     }
117
118     /**
119      * Convert a book to a PDF file.
120      *
121      * @throws Throwable
122      */
123     public function bookToPdf(Book $book)
124     {
125         $bookTree = (new BookContents($book))->getTree(false, true);
126         $html = view('books.export', [
127             'book'         => $book,
128             'bookChildren' => $bookTree,
129             'format'       => 'pdf',
130         ])->render();
131
132         return $this->htmlToPdf($html);
133     }
134
135     /**
136      * Convert normal web-page HTML to a PDF.
137      *
138      * @throws Exception
139      */
140     protected function htmlToPdf(string $html): string
141     {
142         $containedHtml = $this->containHtml($html);
143         $useWKHTML = config('snappy.pdf.binary') !== false;
144         if ($useWKHTML) {
145             $pdf = SnappyPDF::loadHTML($containedHtml);
146             $pdf->setOption('print-media-type', true);
147         } else {
148             $pdf = DomPDF::loadHTML($containedHtml);
149         }
150
151         return $pdf->output();
152     }
153
154     /**
155      * Bundle of the contents of a html file to be self-contained.
156      *
157      * @throws Exception
158      */
159     protected function containHtml(string $htmlContent): string
160     {
161         $imageTagsOutput = [];
162         preg_match_all("/\<img.*?src\=(\'|\")(.*?)(\'|\").*?\>/i", $htmlContent, $imageTagsOutput);
163
164         // Replace image src with base64 encoded image strings
165         if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) {
166             foreach ($imageTagsOutput[0] as $index => $imgMatch) {
167                 $oldImgTagString = $imgMatch;
168                 $srcString = $imageTagsOutput[2][$index];
169                 $imageEncoded = $this->imageService->imageUriToBase64($srcString);
170                 if ($imageEncoded === null) {
171                     $imageEncoded = $srcString;
172                 }
173                 $newImgTagString = str_replace($srcString, $imageEncoded, $oldImgTagString);
174                 $htmlContent = str_replace($oldImgTagString, $newImgTagString, $htmlContent);
175             }
176         }
177
178         $linksOutput = [];
179         preg_match_all("/\<a.*href\=(\'|\")(.*?)(\'|\").*?\>/i", $htmlContent, $linksOutput);
180
181         // Replace image src with base64 encoded image strings
182         if (isset($linksOutput[0]) && count($linksOutput[0]) > 0) {
183             foreach ($linksOutput[0] as $index => $linkMatch) {
184                 $oldLinkString = $linkMatch;
185                 $srcString = $linksOutput[2][$index];
186                 if (strpos(trim($srcString), 'http') !== 0) {
187                     $newSrcString = url($srcString);
188                     $newLinkString = str_replace($srcString, $newSrcString, $oldLinkString);
189                     $htmlContent = str_replace($oldLinkString, $newLinkString, $htmlContent);
190                 }
191             }
192         }
193
194         // Replace any relative links with system domain
195         return $htmlContent;
196     }
197
198     /**
199      * Converts the page contents into simple plain text.
200      * This method filters any bad looking content to provide a nice final output.
201      */
202     public function pageToPlainText(Page $page): string
203     {
204         $html = (new PageContent($page))->render();
205         $text = strip_tags($html);
206         // Replace multiple spaces with single spaces
207         $text = preg_replace('/\ {2,}/', ' ', $text);
208         // Reduce multiple horrid whitespace characters.
209         $text = preg_replace('/(\x0A|\xA0|\x0A|\r|\n){2,}/su', "\n\n", $text);
210         $text = html_entity_decode($text);
211         // Add title
212         $text = $page->name . "\n\n" . $text;
213
214         return $text;
215     }
216
217     /**
218      * Convert a chapter into a plain text string.
219      */
220     public function chapterToPlainText(Chapter $chapter): string
221     {
222         $text = $chapter->name . "\n\n";
223         $text .= $chapter->description . "\n\n";
224         foreach ($chapter->getVisiblePages() as $page) {
225             $text .= $this->pageToPlainText($page);
226         }
227
228         return $text;
229     }
230
231     /**
232      * Convert a book into a plain text string.
233      */
234     public function bookToPlainText(Book $book): string
235     {
236         $bookTree = (new BookContents($book))->getTree(false, false);
237         $text = $book->name . "\n\n";
238         foreach ($bookTree as $bookChild) {
239             if ($bookChild->isA('chapter')) {
240                 $text .= $this->chapterToPlainText($bookChild);
241             } else {
242                 $text .= $this->pageToPlainText($bookChild);
243             }
244         }
245
246         return $text;
247     }
248
249     /**
250      * Convert a page to a Markdown file.
251      */
252     public function pageToMarkdown(Page $page): string
253     {
254         if ($page->markdown) {
255             return '# ' . $page->name . "\n\n" . $page->markdown;
256         }
257
258         return '# ' . $page->name . "\n\n" . (new HtmlToMarkdown($page->html))->convert();
259     }
260
261     /**
262      * Convert a chapter to a Markdown file.
263      */
264     public function chapterToMarkdown(Chapter $chapter): string
265     {
266         $text = '# ' . $chapter->name . "\n\n";
267         $text .= $chapter->description . "\n\n";
268         foreach ($chapter->pages as $page) {
269             $text .= $this->pageToMarkdown($page) . "\n\n";
270         }
271
272         return $text;
273     }
274
275     /**
276      * Convert a book into a plain text string.
277      */
278     public function bookToMarkdown(Book $book): string
279     {
280         $bookTree = (new BookContents($book))->getTree(false, true);
281         $text = '# ' . $book->name . "\n\n";
282         foreach ($bookTree as $bookChild) {
283             if ($bookChild instanceof Chapter) {
284                 $text .= $this->chapterToMarkdown($bookChild);
285             } else {
286                 $text .= $this->pageToMarkdown($bookChild);
287             }
288         }
289
290         return $text;
291     }
292 }