diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php
index 408192ff9..fe9ece5b2 100644
--- a/app/Http/Controllers/BookController.php
+++ b/app/Http/Controllers/BookController.php
@@ -3,6 +3,7 @@
use Activity;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
+use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Views;
@@ -12,16 +13,19 @@ class BookController extends Controller
protected $entityRepo;
protected $userRepo;
+ protected $exportService;
/**
* BookController constructor.
* @param EntityRepo $entityRepo
* @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->userRepo = $userRepo;
+ $this->exportService = $exportService;
parent::__construct();
}
@@ -258,4 +262,49 @@ class BookController extends Controller
session()->flash('success', trans('entities.books_permissions_updated'));
return redirect($book->getUrl());
}
+
+ /**
+ * Export a book as a PDF file.
+ * @param string $bookSlug
+ * @return mixed
+ */
+ public function exportPdf($bookSlug)
+ {
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
+ $pdfContent = $this->exportService->bookToPdf($book);
+ return response()->make($pdfContent, 200, [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.pdf'
+ ]);
+ }
+
+ /**
+ * Export a book as a contained HTML file.
+ * @param string $bookSlug
+ * @return mixed
+ */
+ public function exportHtml($bookSlug)
+ {
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
+ $htmlContent = $this->exportService->bookToContainedHtml($book);
+ return response()->make($htmlContent, 200, [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.html'
+ ]);
+ }
+
+ /**
+ * Export a book as a plain text file.
+ * @param $bookSlug
+ * @return mixed
+ */
+ public function exportPlainText($bookSlug)
+ {
+ $book = $this->entityRepo->getBySlug('book', $bookSlug);
+ $htmlContent = $this->exportService->bookToPlainText($book);
+ return response()->make($htmlContent, 200, [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.txt'
+ ]);
+ }
}
diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php
index 623cb9c4d..4a29c20d6 100644
--- a/app/Http/Controllers/PageController.php
+++ b/app/Http/Controllers/PageController.php
@@ -439,7 +439,6 @@ class PageController extends Controller
{
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
$pdfContent = $this->exportService->pageToPdf($page);
-// return $pdfContent;
return response()->make($pdfContent, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $pageSlug . '.pdf'
diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php
index 7b262c3de..4db69137f 100644
--- a/app/Repos/EntityRepo.php
+++ b/app/Repos/EntityRepo.php
@@ -313,11 +313,12 @@ class EntityRepo
* Loads the book slug onto child elements to prevent access database access for getting the slug.
* @param Book $book
* @param bool $filterDrafts
+ * @param bool $renderPages
* @return mixed
*/
- public function getBookChildren(Book $book, $filterDrafts = false)
+ public function getBookChildren(Book $book, $filterDrafts = false, $renderPages = false)
{
- $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts)->get();
+ $q = $this->permissionService->bookChildrenQuery($book->id, $filterDrafts, $renderPages)->get();
$entities = [];
$parents = [];
$tree = [];
@@ -325,6 +326,10 @@ class EntityRepo
foreach ($q as $index => $rawEntity) {
if ($rawEntity->entity_type === 'BookStack\\Page') {
$entities[$index] = $this->page->newFromBuilder($rawEntity);
+ if ($renderPages) {
+ $entities[$index]->html = $rawEntity->description;
+ $entities[$index]->html = $this->renderPage($entities[$index]);
+ };
} else if ($rawEntity->entity_type === 'BookStack\\Chapter') {
$entities[$index] = $this->chapter->newFromBuilder($rawEntity);
$key = $entities[$index]->entity_type . ':' . $entities[$index]->id;
diff --git a/app/Services/ExportService.php b/app/Services/ExportService.php
index e51577a22..3ac698718 100644
--- a/app/Services/ExportService.php
+++ b/app/Services/ExportService.php
@@ -1,5 +1,6 @@
$page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render();
+ $pageHtml = view('pages/export', [
+ 'page' => $page,
+ 'pageContent' => $this->entityRepo->renderPage($page)
+ ])->render();
return $this->containHtml($pageHtml);
}
/**
- * Convert a page to a pdf file.
+ * Convert a book to a self-contained HTML file.
+ * @param Book $book
+ * @return mixed|string
+ */
+ public function bookToContainedHtml(Book $book)
+ {
+ $bookTree = $this->entityRepo->getBookChildren($book, true, true);
+ $html = view('books/export', [
+ 'book' => $book,
+ 'bookChildren' => $bookTree
+ ])->render();
+ return $this->containHtml($html);
+ }
+
+ /**
+ * Convert a page to a PDF file.
* @param Page $page
* @return mixed|string
*/
public function pageToPdf(Page $page)
{
- $cssContent = file_get_contents(public_path('/css/export-styles.css'));
- $pageHtml = view('pages/pdf', ['page' => $page, 'pageContent' => $this->entityRepo->renderPage($page), 'css' => $cssContent])->render();
+ $html = view('pages/pdf', [
+ 'page' => $page,
+ 'pageContent' => $this->entityRepo->renderPage($page)
+ ])->render();
+ return $this->htmlToPdf($html);
+ }
+
+ /**
+ * Convert a book to a PDF file
+ * @param Book $book
+ * @return string
+ */
+ public function bookToPdf(Book $book)
+ {
+ $bookTree = $this->entityRepo->getBookChildren($book, true, true);
+ $html = view('books/export', [
+ 'book' => $book,
+ 'bookChildren' => $bookTree
+ ])->render();
+ return $this->htmlToPdf($html);
+ }
+
+ /**
+ * Convert normal webpage HTML to a PDF.
+ * @param $html
+ * @return string
+ */
+ protected function htmlToPdf($html)
+ {
+ $containedHtml = $this->containHtml($html);
$useWKHTML = config('snappy.pdf.binary') !== false;
- $containedHtml = $this->containHtml($pageHtml);
if ($useWKHTML) {
$pdf = \SnappyPDF::loadHTML($containedHtml);
+ $pdf->setOption('print-media-type', true);
} else {
$pdf = \PDF::loadHTML($containedHtml);
}
@@ -122,6 +168,29 @@ class ExportService
return $text;
}
+ /**
+ * Convert a book into a plain text string.
+ * @param Book $book
+ * @return string
+ */
+ public function bookToPlainText(Book $book)
+ {
+ $bookTree = $this->entityRepo->getBookChildren($book, true, true);
+ $text = $book->name . "\n\n";
+ foreach ($bookTree as $bookChild) {
+ if ($bookChild->isA('chapter')) {
+ $text .= $bookChild->name . "\n\n";
+ $text .= $bookChild->description . "\n\n";
+ foreach ($bookChild->pages as $page) {
+ $text .= $this->pageToPlainText($page);
+ }
+ } else {
+ $text .= $this->pageToPlainText($bookChild);
+ }
+ }
+ return $text;
+ }
+
}
diff --git a/app/Services/PermissionService.php b/app/Services/PermissionService.php
index 72a810b6b..8b47e1246 100644
--- a/app/Services/PermissionService.php
+++ b/app/Services/PermissionService.php
@@ -474,11 +474,13 @@ class PermissionService
/**
* Get the children of a book in an efficient single query, Filtered by the permission system.
* @param integer $book_id
- * @param bool $filterDrafts
+ * @param bool $filterDrafts
+ * @param bool $fetchPageContent
* @return \Illuminate\Database\Query\Builder
*/
- public function bookChildrenQuery($book_id, $filterDrafts = false) {
- $pageSelect = $this->db->table('pages')->selectRaw("'BookStack\\\\Page' as entity_type, id, slug, name, text, '' as description, book_id, priority, chapter_id, draft")->where('book_id', '=', $book_id)->where(function($query) use ($filterDrafts) {
+ public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false) {
+ $pageContentSelect = $fetchPageContent ? 'html' : "''";
+ $pageSelect = $this->db->table('pages')->selectRaw("'BookStack\\\\Page' as entity_type, id, slug, name, text, {$pageContentSelect} as description, book_id, priority, chapter_id, draft")->where('book_id', '=', $book_id)->where(function($query) use ($filterDrafts) {
$query->where('draft', '=', 0);
if (!$filterDrafts) {
$query->orWhere(function($query) {
diff --git a/config/dompdf.php b/config/dompdf.php
index 1eb1d9782..036e1bb3c 100644
--- a/config/dompdf.php
+++ b/config/dompdf.php
@@ -143,7 +143,7 @@ return [
* the desired content might be different (e.g. screen or projection view of html file).
* Therefore allow specification of content here.
*/
- "DOMPDF_DEFAULT_MEDIA_TYPE" => "screen",
+ "DOMPDF_DEFAULT_MEDIA_TYPE" => "print",
/**
* The default paper size.
diff --git a/resources/views/books/export.blade.php b/resources/views/books/export.blade.php
new file mode 100644
index 000000000..e5fbada44
--- /dev/null
+++ b/resources/views/books/export.blade.php
@@ -0,0 +1,78 @@
+
+
+
+
+ {{ $book->name }}
+
+
+ @yield('head')
+
+
+
+
+
+
+
+
{{$book->name}}
+
+
{{ $book->description }}
+
+ @if(count($bookChildren) > 0)
+
+ @foreach($bookChildren as $bookChild)
+ - {{ $bookChild->name }}
+ @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
+
+ @endif
+ @endforeach
+
+ @endif
+
+ @foreach($bookChildren as $bookChild)
+
+
{{ $bookChild->name }}
+ @if($bookChild->isA('chapter'))
+
{{ $bookChild->description }}
+ @if(count($bookChild->pages) > 0)
+ @foreach($bookChild->pages as $page)
+
+
{{$bookChild->name}}
+
{{ $page->name }}
+ {!! $page->html !!}
+ @endforeach
+ @endif
+ @else
+ {!! $bookChild->html !!}
+ @endif
+ @endforeach
+
+
+
+
+
+
+
diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php
index 6b4e7f88a..99ffe80e1 100644
--- a/resources/views/books/show.blade.php
+++ b/resources/views/books/show.blade.php
@@ -10,6 +10,14 @@