use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
use BookStack\Uploads\ImageService;
-use DomPDF;
+use DOMDocument;
+use DOMElement;
+use DOMXPath;
use Exception;
-use SnappyPDF;
use Throwable;
class ExportFormatter
{
protected $imageService;
+ protected $pdfGenerator;
/**
* ExportService constructor.
*/
- public function __construct(ImageService $imageService)
+ public function __construct(ImageService $imageService, PdfGenerator $pdfGenerator)
{
$this->imageService = $imageService;
+ $this->pdfGenerator = $pdfGenerator;
}
/**
*/
protected function htmlToPdf(string $html): string
{
- $containedHtml = $this->containHtml($html);
- $useWKHTML = config('snappy.pdf.binary') !== false && config('app.allow_untrusted_server_fetching') === true;
- if ($useWKHTML) {
- $pdf = SnappyPDF::loadHTML($containedHtml);
- $pdf->setOption('print-media-type', true);
- } else {
- $pdf = DomPDF::loadHTML($containedHtml);
+ $html = $this->containHtml($html);
+ $html = $this->replaceIframesWithLinks($html);
+ return $this->pdfGenerator->fromHtml($html);
+ }
+
+ /**
+ * Within the given HTML content, replace any iframe elements
+ * with anchor links within paragraph blocks.
+ */
+ protected function replaceIframesWithLinks(string $html): string
+ {
+ libxml_use_internal_errors(true);
+
+ $doc = new DOMDocument();
+ $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
+ $xPath = new DOMXPath($doc);
+
+
+ $iframes = $xPath->query('//iframe');
+ /** @var DOMElement $iframe */
+ foreach ($iframes as $iframe) {
+ $link = $iframe->getAttribute('src');
+ if (strpos($link, '//') === 0) {
+ $link = 'https:' . $link;
+ }
+
+ $anchor = $doc->createElement('a', $link);
+ $anchor->setAttribute('href', $link);
+ $paragraph = $doc->createElement('p');
+ $paragraph->appendChild($anchor);
+ $iframe->replaceWith($paragraph);
}
- return $pdf->output();
+ return $doc->saveHTML();
}
/**
--- /dev/null
+<?php
+
+namespace BookStack\Entities\Tools;
+
+use Barryvdh\Snappy\Facades\SnappyPdf;
+use Barryvdh\DomPDF\Facade as DomPDF;
+
+class PdfGenerator
+{
+
+ /**
+ * Generate PDF content from the given HTML content.
+ */
+ public function fromHtml(string $html): string
+ {
+ $useWKHTML = config('snappy.pdf.binary') !== false && config('app.allow_untrusted_server_fetching') === true;
+
+ if ($useWKHTML) {
+ $pdf = SnappyPDF::loadHTML($html);
+ $pdf->setOption('print-media-type', true);
+ } else {
+ $pdf = DomPDF::loadHTML($html);
+ }
+
+ return $pdf->output();
+ }
+
+}
\ No newline at end of file
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
+use BookStack\Entities\Tools\PdfGenerator;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Tests\TestCase;
$resp->assertDontSee('ExportWizardTheFifth');
}
+ public function test_page_pdf_export_converts_iframes_to_links()
+ {
+ $page = Page::query()->first()->forceFill([
+ 'html' => '<iframe width="560" height="315" src="//www.youtube.com/embed/ShqUjt33uOs"></iframe>',
+ ]);
+ $page->save();
+
+ $pdfHtml = '';
+ $mockPdfGenerator = $this->mock(PdfGenerator::class);
+ $mockPdfGenerator->shouldReceive('fromHtml')
+ ->with(\Mockery::capture($pdfHtml))
+ ->andReturn('');
+
+ $this->asEditor()->get($page->getUrl('/export/pdf'));
+ $this->assertStringNotContainsString('iframe>', $pdfHtml);
+ $this->assertStringContainsString('<p><a href="https://www.youtube.com/embed/ShqUjt33uOs">https://www.youtube.com/embed/ShqUjt33uOs</a></p>', $pdfHtml);
+ }
+
public function test_page_markdown_export()
{
$page = Page::query()->first();