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