Dan Brown

Added chapter export options

Closes #177
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
3 use Activity; 3 use Activity;
4 use BookStack\Repos\EntityRepo; 4 use BookStack\Repos\EntityRepo;
5 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
6 +use BookStack\Services\ExportService;
6 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
7 use Illuminate\Http\Response; 8 use Illuminate\Http\Response;
8 use Views; 9 use Views;
...@@ -12,16 +13,19 @@ class ChapterController extends Controller ...@@ -12,16 +13,19 @@ class ChapterController extends Controller
12 13
13 protected $userRepo; 14 protected $userRepo;
14 protected $entityRepo; 15 protected $entityRepo;
16 + protected $exportService;
15 17
16 /** 18 /**
17 * ChapterController constructor. 19 * ChapterController constructor.
18 * @param EntityRepo $entityRepo 20 * @param EntityRepo $entityRepo
19 * @param UserRepo $userRepo 21 * @param UserRepo $userRepo
22 + * @param ExportService $exportService
20 */ 23 */
21 - public function __construct(EntityRepo $entityRepo, UserRepo $userRepo) 24 + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
22 { 25 {
23 $this->entityRepo = $entityRepo; 26 $this->entityRepo = $entityRepo;
24 $this->userRepo = $userRepo; 27 $this->userRepo = $userRepo;
28 + $this->exportService = $exportService;
25 parent::__construct(); 29 parent::__construct();
26 } 30 }
27 31
...@@ -236,4 +240,52 @@ class ChapterController extends Controller ...@@ -236,4 +240,52 @@ class ChapterController extends Controller
236 session()->flash('success', trans('entities.chapters_permissions_success')); 240 session()->flash('success', trans('entities.chapters_permissions_success'));
237 return redirect($chapter->getUrl()); 241 return redirect($chapter->getUrl());
238 } 242 }
243 +
244 + /**
245 + * Exports a chapter to pdf .
246 + * @param string $bookSlug
247 + * @param string $chapterSlug
248 + * @return \Illuminate\Http\Response
249 + */
250 + public function exportPdf($bookSlug, $chapterSlug)
251 + {
252 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
253 + $pdfContent = $this->exportService->chapterToPdf($chapter);
254 + return response()->make($pdfContent, 200, [
255 + 'Content-Type' => 'application/octet-stream',
256 + 'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.pdf'
257 + ]);
258 + }
259 +
260 + /**
261 + * Export a chapter to a self-contained HTML file.
262 + * @param string $bookSlug
263 + * @param string $chapterSlug
264 + * @return \Illuminate\Http\Response
265 + */
266 + public function exportHtml($bookSlug, $chapterSlug)
267 + {
268 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
269 + $containedHtml = $this->exportService->chapterToContainedHtml($chapter);
270 + return response()->make($containedHtml, 200, [
271 + 'Content-Type' => 'application/octet-stream',
272 + 'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.html'
273 + ]);
274 + }
275 +
276 + /**
277 + * Export a chapter to a simple plaintext .txt file.
278 + * @param string $bookSlug
279 + * @param string $chapterSlug
280 + * @return \Illuminate\Http\Response
281 + */
282 + public function exportPlainText($bookSlug, $chapterSlug)
283 + {
284 + $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
285 + $containedHtml = $this->exportService->chapterToPlainText($chapter);
286 + return response()->make($containedHtml, 200, [
287 + 'Content-Type' => 'application/octet-stream',
288 + 'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.txt'
289 + ]);
290 + }
239 } 291 }
......
...@@ -429,7 +429,7 @@ class PageController extends Controller ...@@ -429,7 +429,7 @@ class PageController extends Controller
429 } 429 }
430 430
431 /** 431 /**
432 - * Exports a page to pdf format using barryvdh/laravel-dompdf wrapper. 432 + * Exports a page to a PDF.
433 * https://github.com/barryvdh/laravel-dompdf 433 * https://github.com/barryvdh/laravel-dompdf
434 * @param string $bookSlug 434 * @param string $bookSlug
435 * @param string $pageSlug 435 * @param string $pageSlug
......
1 <?php namespace BookStack\Services; 1 <?php namespace BookStack\Services;
2 2
3 use BookStack\Book; 3 use BookStack\Book;
4 +use BookStack\Chapter;
4 use BookStack\Page; 5 use BookStack\Page;
5 use BookStack\Repos\EntityRepo; 6 use BookStack\Repos\EntityRepo;
6 7
...@@ -34,6 +35,24 @@ class ExportService ...@@ -34,6 +35,24 @@ class ExportService
34 } 35 }
35 36
36 /** 37 /**
38 + * Convert a chapter to a self-contained HTML file.
39 + * @param Chapter $chapter
40 + * @return mixed|string
41 + */
42 + public function chapterToContainedHtml(Chapter $chapter)
43 + {
44 + $pages = $this->entityRepo->getChapterChildren($chapter);
45 + $pages->each(function($page) {
46 + $page->html = $this->entityRepo->renderPage($page);
47 + });
48 + $html = view('chapters/export', [
49 + 'chapter' => $chapter,
50 + 'pages' => $pages
51 + ])->render();
52 + return $this->containHtml($html);
53 + }
54 +
55 + /**
37 * Convert a book to a self-contained HTML file. 56 * Convert a book to a self-contained HTML file.
38 * @param Book $book 57 * @param Book $book
39 * @return mixed|string 58 * @return mixed|string
...@@ -63,6 +82,24 @@ class ExportService ...@@ -63,6 +82,24 @@ class ExportService
63 } 82 }
64 83
65 /** 84 /**
85 + * Convert a chapter to a PDF file.
86 + * @param Chapter $chapter
87 + * @return mixed|string
88 + */
89 + public function chapterToPdf(Chapter $chapter)
90 + {
91 + $pages = $this->entityRepo->getChapterChildren($chapter);
92 + $pages->each(function($page) {
93 + $page->html = $this->entityRepo->renderPage($page);
94 + });
95 + $html = view('chapters/export', [
96 + 'chapter' => $chapter,
97 + 'pages' => $pages
98 + ])->render();
99 + return $this->htmlToPdf($html);
100 + }
101 +
102 + /**
66 * Convert a book to a PDF file 103 * Convert a book to a PDF file
67 * @param Book $book 104 * @param Book $book
68 * @return string 105 * @return string
...@@ -169,6 +206,21 @@ class ExportService ...@@ -169,6 +206,21 @@ class ExportService
169 } 206 }
170 207
171 /** 208 /**
209 + * Convert a chapter into a plain text string.
210 + * @param Chapter $chapter
211 + * @return string
212 + */
213 + public function chapterToPlainText(Chapter $chapter)
214 + {
215 + $text = $chapter->name . "\n\n";
216 + $text .= $chapter->description . "\n\n";
217 + foreach ($chapter->pages as $page) {
218 + $text .= $this->pageToPlainText($page);
219 + }
220 + return $text;
221 + }
222 +
223 + /**
172 * Convert a book into a plain text string. 224 * Convert a book into a plain text string.
173 * @param Book $book 225 * @param Book $book
174 * @return string 226 * @return string
...@@ -179,11 +231,7 @@ class ExportService ...@@ -179,11 +231,7 @@ class ExportService
179 $text = $book->name . "\n\n"; 231 $text = $book->name . "\n\n";
180 foreach ($bookTree as $bookChild) { 232 foreach ($bookTree as $bookChild) {
181 if ($bookChild->isA('chapter')) { 233 if ($bookChild->isA('chapter')) {
182 - $text .= $bookChild->name . "\n\n"; 234 + $text .= $this->chapterToPlainText($bookChild);
183 - $text .= $bookChild->description . "\n\n";
184 - foreach ($bookChild->pages as $page) {
185 - $text .= $this->pageToPlainText($page);
186 - }
187 } else { 235 } else {
188 $text .= $this->pageToPlainText($bookChild); 236 $text .= $this->pageToPlainText($bookChild);
189 } 237 }
......
1 +<!doctype html>
2 +<html lang="en">
3 +<head>
4 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
5 + <title>{{ $chapter->name }}</title>
6 +
7 + <style>
8 + {!! file_get_contents(public_path('/css/export-styles.css')) !!}
9 + .page-break {
10 + page-break-after: always;
11 + }
12 + ul.contents ul li {
13 + list-style: circle;
14 + }
15 + @media screen {
16 + .page-break {
17 + border-top: 1px solid #DDD;
18 + }
19 + }
20 + </style>
21 + @yield('head')
22 +</head>
23 +<body>
24 +<div class="container">
25 + <div class="row">
26 + <div class="col-md-8 col-md-offset-2">
27 + <div class="page-content">
28 +
29 + <h1 style="font-size: 4.8em">{{$chapter->name}}</h1>
30 +
31 + <p>{{ $chapter->description }}</p>
32 +
33 + @if(count($pages) > 0)
34 + <ul class="contents">
35 + @foreach($pages as $page)
36 + <li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
37 + @endforeach
38 + </ul>
39 + @endif
40 +
41 + @foreach($pages as $page)
42 + <div class="page-break"></div>
43 + <h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
44 + {!! $page->html !!}
45 + @endforeach
46 +
47 + </div>
48 + </div>
49 + </div>
50 +</div>
51 +</body>
52 +</html>
...@@ -10,6 +10,14 @@ ...@@ -10,6 +10,14 @@
10 </div> 10 </div>
11 <div class="col-sm-4 faded"> 11 <div class="col-sm-4 faded">
12 <div class="action-buttons"> 12 <div class="action-buttons">
13 + <span dropdown class="dropdown-container">
14 + <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.pages_export') }}</div>
15 + <ul class="wide">
16 + <li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.pages_export_html') }} <span class="text-muted float right">.html</span></a></li>
17 + <li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.pages_export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
18 + <li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.pages_export_text') }} <span class="text-muted float right">.txt</span></a></li>
19 + </ul>
20 + </span>
13 @if(userCan('page-create', $chapter)) 21 @if(userCan('page-create', $chapter))
14 <a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a> 22 <a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
15 @endif 23 @endif
......
...@@ -67,6 +67,9 @@ Route::group(['middleware' => 'auth'], function () { ...@@ -67,6 +67,9 @@ Route::group(['middleware' => 'auth'], function () {
67 Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move'); 67 Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move');
68 Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit'); 68 Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
69 Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict'); 69 Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict');
70 + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterController@exportPdf');
71 + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterController@exportHtml');
72 + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterController@exportPlainText');
70 Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict'); 73 Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict');
71 Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); 74 Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
72 Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy'); 75 Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');
......
1 <?php namespace Tests; 1 <?php namespace Tests;
2 2
3 3
4 +use BookStack\Chapter;
4 use BookStack\Page; 5 use BookStack\Page;
5 6
6 class ExportTest extends TestCase 7 class ExportTest extends TestCase
...@@ -75,4 +76,40 @@ class ExportTest extends TestCase ...@@ -75,4 +76,40 @@ class ExportTest extends TestCase
75 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html'); 76 $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html');
76 } 77 }
77 78
79 + public function test_chapter_text_export()
80 + {
81 + $chapter = Chapter::first();
82 + $page = $chapter->pages[0];
83 + $this->asEditor();
84 +
85 + $resp = $this->get($chapter->getUrl('/export/plaintext'));
86 + $resp->assertStatus(200);
87 + $resp->assertSee($chapter->name);
88 + $resp->assertSee($page->name);
89 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt');
90 + }
91 +
92 + public function test_chapter_pdf_export()
93 + {
94 + $chapter = Chapter::first();
95 + $this->asEditor();
96 +
97 + $resp = $this->get($chapter->getUrl('/export/pdf'));
98 + $resp->assertStatus(200);
99 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf');
100 + }
101 +
102 + public function test_chapter_html_export()
103 + {
104 + $chapter = Chapter::first();
105 + $page = $chapter->pages[0];
106 + $this->asEditor();
107 +
108 + $resp = $this->get($chapter->getUrl('/export/html'));
109 + $resp->assertStatus(200);
110 + $resp->assertSee($chapter->name);
111 + $resp->assertSee($page->name);
112 + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html');
113 + }
114 +
78 } 115 }
...\ No newline at end of file ...\ No newline at end of file
......