mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-06 18:54:33 +08:00
Added conversion of iframes to anchors on PDF export
- Replaced iframe elements with anchor elements wrapped in a paragraph. - Extracted PDF generation action to seperate class for easier mocking within testing. - Added test to cover. For #3077
This commit is contained in:
@ -7,21 +7,24 @@ use BookStack\Entities\Models\Chapter;
|
|||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\Models\Page;
|
||||||
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
|
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
|
||||||
use BookStack\Uploads\ImageService;
|
use BookStack\Uploads\ImageService;
|
||||||
use DomPDF;
|
use DOMDocument;
|
||||||
|
use DOMElement;
|
||||||
|
use DOMXPath;
|
||||||
use Exception;
|
use Exception;
|
||||||
use SnappyPDF;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class ExportFormatter
|
class ExportFormatter
|
||||||
{
|
{
|
||||||
protected $imageService;
|
protected $imageService;
|
||||||
|
protected $pdfGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExportService constructor.
|
* ExportService constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(ImageService $imageService)
|
public function __construct(ImageService $imageService, PdfGenerator $pdfGenerator)
|
||||||
{
|
{
|
||||||
$this->imageService = $imageService;
|
$this->imageService = $imageService;
|
||||||
|
$this->pdfGenerator = $pdfGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,16 +142,40 @@ class ExportFormatter
|
|||||||
*/
|
*/
|
||||||
protected function htmlToPdf(string $html): string
|
protected function htmlToPdf(string $html): string
|
||||||
{
|
{
|
||||||
$containedHtml = $this->containHtml($html);
|
$html = $this->containHtml($html);
|
||||||
$useWKHTML = config('snappy.pdf.binary') !== false && config('app.allow_untrusted_server_fetching') === true;
|
$html = $this->replaceIframesWithLinks($html);
|
||||||
if ($useWKHTML) {
|
return $this->pdfGenerator->fromHtml($html);
|
||||||
$pdf = SnappyPDF::loadHTML($containedHtml);
|
}
|
||||||
$pdf->setOption('print-media-type', true);
|
|
||||||
} else {
|
/**
|
||||||
$pdf = DomPDF::loadHTML($containedHtml);
|
* 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
28
app/Entities/Tools/PdfGenerator.php
Normal file
28
app/Entities/Tools/PdfGenerator.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,6 +6,7 @@ use BookStack\Auth\Role;
|
|||||||
use BookStack\Entities\Models\Book;
|
use BookStack\Entities\Models\Book;
|
||||||
use BookStack\Entities\Models\Chapter;
|
use BookStack\Entities\Models\Chapter;
|
||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\Models\Page;
|
||||||
|
use BookStack\Entities\Tools\PdfGenerator;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
@ -289,6 +290,24 @@ class ExportTest extends TestCase
|
|||||||
$resp->assertDontSee('ExportWizardTheFifth');
|
$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()
|
public function test_page_markdown_export()
|
||||||
{
|
{
|
||||||
$page = Page::query()->first();
|
$page = Page::query()->first();
|
||||||
|
Reference in New Issue
Block a user