Added chapter export options

Closes #177
This commit is contained in:
Dan Brown
2017-02-26 14:25:02 +00:00
parent eded8abded
commit 253132afdf
7 changed files with 207 additions and 7 deletions

View File

@ -3,6 +3,7 @@
use Activity; use Activity;
use BookStack\Repos\EntityRepo; use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo; use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Views; use Views;
@ -12,16 +13,19 @@ class ChapterController extends Controller
protected $userRepo; protected $userRepo;
protected $entityRepo; protected $entityRepo;
protected $exportService;
/** /**
* ChapterController constructor. * ChapterController constructor.
* @param EntityRepo $entityRepo * @param EntityRepo $entityRepo
* @param UserRepo $userRepo * @param UserRepo $userRepo
* @param ExportService $exportService
*/ */
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo) public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
{ {
$this->entityRepo = $entityRepo; $this->entityRepo = $entityRepo;
$this->userRepo = $userRepo; $this->userRepo = $userRepo;
$this->exportService = $exportService;
parent::__construct(); parent::__construct();
} }
@ -236,4 +240,52 @@ class ChapterController extends Controller
session()->flash('success', trans('entities.chapters_permissions_success')); session()->flash('success', trans('entities.chapters_permissions_success'));
return redirect($chapter->getUrl()); return redirect($chapter->getUrl());
} }
/**
* Exports a chapter to pdf .
* @param string $bookSlug
* @param string $chapterSlug
* @return \Illuminate\Http\Response
*/
public function exportPdf($bookSlug, $chapterSlug)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$pdfContent = $this->exportService->chapterToPdf($chapter);
return response()->make($pdfContent, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.pdf'
]);
}
/**
* Export a chapter to a self-contained HTML file.
* @param string $bookSlug
* @param string $chapterSlug
* @return \Illuminate\Http\Response
*/
public function exportHtml($bookSlug, $chapterSlug)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$containedHtml = $this->exportService->chapterToContainedHtml($chapter);
return response()->make($containedHtml, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.html'
]);
}
/**
* Export a chapter to a simple plaintext .txt file.
* @param string $bookSlug
* @param string $chapterSlug
* @return \Illuminate\Http\Response
*/
public function exportPlainText($bookSlug, $chapterSlug)
{
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
$containedHtml = $this->exportService->chapterToPlainText($chapter);
return response()->make($containedHtml, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $chapterSlug . '.txt'
]);
}
} }

View File

@ -429,7 +429,7 @@ class PageController extends Controller
} }
/** /**
* Exports a page to pdf format using barryvdh/laravel-dompdf wrapper. * Exports a page to a PDF.
* https://github.com/barryvdh/laravel-dompdf * https://github.com/barryvdh/laravel-dompdf
* @param string $bookSlug * @param string $bookSlug
* @param string $pageSlug * @param string $pageSlug

View File

@ -1,6 +1,7 @@
<?php namespace BookStack\Services; <?php namespace BookStack\Services;
use BookStack\Book; use BookStack\Book;
use BookStack\Chapter;
use BookStack\Page; use BookStack\Page;
use BookStack\Repos\EntityRepo; use BookStack\Repos\EntityRepo;
@ -33,6 +34,24 @@ class ExportService
return $this->containHtml($pageHtml); return $this->containHtml($pageHtml);
} }
/**
* Convert a chapter to a self-contained HTML file.
* @param Chapter $chapter
* @return mixed|string
*/
public function chapterToContainedHtml(Chapter $chapter)
{
$pages = $this->entityRepo->getChapterChildren($chapter);
$pages->each(function($page) {
$page->html = $this->entityRepo->renderPage($page);
});
$html = view('chapters/export', [
'chapter' => $chapter,
'pages' => $pages
])->render();
return $this->containHtml($html);
}
/** /**
* Convert a book to a self-contained HTML file. * Convert a book to a self-contained HTML file.
* @param Book $book * @param Book $book
@ -62,6 +81,24 @@ class ExportService
return $this->htmlToPdf($html); return $this->htmlToPdf($html);
} }
/**
* Convert a chapter to a PDF file.
* @param Chapter $chapter
* @return mixed|string
*/
public function chapterToPdf(Chapter $chapter)
{
$pages = $this->entityRepo->getChapterChildren($chapter);
$pages->each(function($page) {
$page->html = $this->entityRepo->renderPage($page);
});
$html = view('chapters/export', [
'chapter' => $chapter,
'pages' => $pages
])->render();
return $this->htmlToPdf($html);
}
/** /**
* Convert a book to a PDF file * Convert a book to a PDF file
* @param Book $book * @param Book $book
@ -168,6 +205,21 @@ class ExportService
return $text; return $text;
} }
/**
* Convert a chapter into a plain text string.
* @param Chapter $chapter
* @return string
*/
public function chapterToPlainText(Chapter $chapter)
{
$text = $chapter->name . "\n\n";
$text .= $chapter->description . "\n\n";
foreach ($chapter->pages as $page) {
$text .= $this->pageToPlainText($page);
}
return $text;
}
/** /**
* Convert a book into a plain text string. * Convert a book into a plain text string.
* @param Book $book * @param Book $book
@ -179,11 +231,7 @@ class ExportService
$text = $book->name . "\n\n"; $text = $book->name . "\n\n";
foreach ($bookTree as $bookChild) { foreach ($bookTree as $bookChild) {
if ($bookChild->isA('chapter')) { if ($bookChild->isA('chapter')) {
$text .= $bookChild->name . "\n\n"; $text .= $this->chapterToPlainText($bookChild);
$text .= $bookChild->description . "\n\n";
foreach ($bookChild->pages as $page) {
$text .= $this->pageToPlainText($page);
}
} else { } else {
$text .= $this->pageToPlainText($bookChild); $text .= $this->pageToPlainText($bookChild);
} }

View File

@ -0,0 +1,52 @@
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>{{ $chapter->name }}</title>
<style>
{!! file_get_contents(public_path('/css/export-styles.css')) !!}
.page-break {
page-break-after: always;
}
ul.contents ul li {
list-style: circle;
}
@media screen {
.page-break {
border-top: 1px solid #DDD;
}
}
</style>
@yield('head')
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="page-content">
<h1 style="font-size: 4.8em">{{$chapter->name}}</h1>
<p>{{ $chapter->description }}</p>
@if(count($pages) > 0)
<ul class="contents">
@foreach($pages as $page)
<li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
@endforeach
</ul>
@endif
@foreach($pages as $page)
<div class="page-break"></div>
<h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
{!! $page->html !!}
@endforeach
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -10,6 +10,14 @@
</div> </div>
<div class="col-sm-4 faded"> <div class="col-sm-4 faded">
<div class="action-buttons"> <div class="action-buttons">
<span dropdown class="dropdown-container">
<div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.pages_export') }}</div>
<ul class="wide">
<li><a href="{{ $chapter->getUrl('/export/html') }}" target="_blank">{{ trans('entities.pages_export_html') }} <span class="text-muted float right">.html</span></a></li>
<li><a href="{{ $chapter->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.pages_export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
<li><a href="{{ $chapter->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.pages_export_text') }} <span class="text-muted float right">.txt</span></a></li>
</ul>
</span>
@if(userCan('page-create', $chapter)) @if(userCan('page-create', $chapter))
<a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a> <a href="{{ $chapter->getUrl('/create-page') }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.pages_new') }}</a>
@endif @endif

View File

@ -67,6 +67,9 @@ Route::group(['middleware' => 'auth'], function () {
Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move'); Route::put('/{bookSlug}/chapter/{chapterSlug}/move', 'ChapterController@move');
Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit'); Route::get('/{bookSlug}/chapter/{chapterSlug}/edit', 'ChapterController@edit');
Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict'); Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showRestrict');
Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterController@exportPdf');
Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterController@exportHtml');
Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterController@exportPlainText');
Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict'); Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@restrict');
Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete');
Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy'); Route::delete('/{bookSlug}/chapter/{chapterSlug}', 'ChapterController@destroy');

View File

@ -1,6 +1,7 @@
<?php namespace Tests; <?php namespace Tests;
use BookStack\Chapter;
use BookStack\Page; use BookStack\Page;
class ExportTest extends TestCase class ExportTest extends TestCase
@ -75,4 +76,40 @@ class ExportTest extends TestCase
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html'); $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html');
} }
public function test_chapter_text_export()
{
$chapter = Chapter::first();
$page = $chapter->pages[0];
$this->asEditor();
$resp = $this->get($chapter->getUrl('/export/plaintext'));
$resp->assertStatus(200);
$resp->assertSee($chapter->name);
$resp->assertSee($page->name);
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt');
}
public function test_chapter_pdf_export()
{
$chapter = Chapter::first();
$this->asEditor();
$resp = $this->get($chapter->getUrl('/export/pdf'));
$resp->assertStatus(200);
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf');
}
public function test_chapter_html_export()
{
$chapter = Chapter::first();
$page = $chapter->pages[0];
$this->asEditor();
$resp = $this->get($chapter->getUrl('/export/html'));
$resp->assertStatus(200);
$resp->assertSee($chapter->name);
$resp->assertSee($page->name);
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html');
}
} }