Showing
7 changed files
with
207 additions
and
7 deletions
| ... | @@ -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 | } | ... | ... |
resources/views/chapters/export.blade.php
0 → 100644
| 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 | ... | ... |
-
Please register or sign in to post a comment