Improved export base64 encoding of images

Now will use set storage mechanism to find image files.
Fixes #786

Added test to cover
This commit is contained in:
Dan Brown 2018-04-22 12:23:43 +01:00
parent 6aeb1387aa
commit 67e0c3d2a5
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
3 changed files with 85 additions and 38 deletions

View File

@ -9,14 +9,16 @@ class ExportService
{
protected $entityRepo;
protected $imageService;
/**
* ExportService constructor.
* @param $entityRepo
*/
public function __construct(EntityRepo $entityRepo)
public function __construct(EntityRepo $entityRepo, ImageService $imageService)
{
$this->entityRepo = $entityRepo;
$this->imageService = $imageService;
}
/**
@ -24,6 +26,7 @@ class ExportService
* Includes required CSS & image content. Images are base64 encoded into the HTML.
* @param Page $page
* @return mixed|string
* @throws \Throwable
*/
public function pageToContainedHtml(Page $page)
{
@ -38,6 +41,7 @@ class ExportService
* Convert a chapter to a self-contained HTML file.
* @param Chapter $chapter
* @return mixed|string
* @throws \Throwable
*/
public function chapterToContainedHtml(Chapter $chapter)
{
@ -56,6 +60,7 @@ class ExportService
* Convert a book to a self-contained HTML file.
* @param Book $book
* @return mixed|string
* @throws \Throwable
*/
public function bookToContainedHtml(Book $book)
{
@ -71,6 +76,7 @@ class ExportService
* Convert a page to a PDF file.
* @param Page $page
* @return mixed|string
* @throws \Throwable
*/
public function pageToPdf(Page $page)
{
@ -85,6 +91,7 @@ class ExportService
* Convert a chapter to a PDF file.
* @param Chapter $chapter
* @return mixed|string
* @throws \Throwable
*/
public function chapterToPdf(Chapter $chapter)
{
@ -103,6 +110,7 @@ class ExportService
* Convert a book to a PDF file
* @param Book $book
* @return string
* @throws \Throwable
*/
public function bookToPdf(Book $book)
{
@ -118,6 +126,7 @@ class ExportService
* Convert normal webpage HTML to a PDF.
* @param $html
* @return string
* @throws \Exception
*/
protected function htmlToPdf($html)
{
@ -146,45 +155,14 @@ class ExportService
// Replace image src with base64 encoded image strings
if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) {
foreach ($imageTagsOutput[0] as $index => $imgMatch) {
$oldImgString = $imgMatch;
$oldImgTagString = $imgMatch;
$srcString = $imageTagsOutput[2][$index];
$isLocal = strpos(trim($srcString), 'http') !== 0;
if ($isLocal) {
$pathString = public_path(trim($srcString, '/'));
} else {
$pathString = $srcString;
$imageEncoded = $this->imageService->imageUriToBase64($srcString);
if ($imageEncoded === null) {
$imageEncoded = $srcString;
}
// Attempt to find local files even if url not absolute
$base = baseUrl('/');
if (strpos($srcString, $base) === 0) {
$isLocal = true;
$relString = str_replace($base, '', $srcString);
$pathString = public_path(trim($relString, '/'));
}
if ($isLocal && !file_exists($pathString)) {
continue;
}
try {
if ($isLocal) {
$imageContent = file_get_contents($pathString);
} else {
$ch = curl_init();
curl_setopt_array($ch, [CURLOPT_URL => $pathString, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]);
$imageContent = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
throw new \Exception("Image fetch failed, Received error: " . $err);
}
}
$imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
$newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
} catch (\ErrorException $e) {
$newImageString = '';
}
$htmlContent = str_replace($oldImgString, $newImageString, $htmlContent);
$newImgTagString = str_replace($srcString, $imageEncoded, $oldImgTagString);
$htmlContent = str_replace($oldImgTagString, $newImgTagString, $htmlContent);
}
}

View File

@ -321,6 +321,52 @@ class ImageService extends UploadService
return $image;
}
/**
* Convert a image URI to a Base64 encoded string.
* Attempts to find locally via set storage method first.
* @param string $uri
* @return null|string
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function imageUriToBase64(string $uri)
{
$isLocal = strpos(trim($uri), 'http') !== 0;
// Attempt to find local files even if url not absolute
$base = baseUrl('/');
if (!$isLocal && strpos($uri, $base) === 0) {
$isLocal = true;
$uri = str_replace($base, '', $uri);
}
$imageData = null;
if ($isLocal) {
$uri = trim($uri, '/');
$storage = $this->getStorage();
if ($storage->exists($uri)) {
$imageData = $storage->get($uri);
}
} else {
try {
$ch = curl_init();
curl_setopt_array($ch, [CURLOPT_URL => $uri, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]);
$imageData = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
throw new \Exception("Image fetch failed, Received error: " . $err);
}
} catch (\Exception $e) {}
}
if ($imageData === null) {
return null;
}
return 'data:image/' . pathinfo($uri, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageData);
}
/**
* Gets a public facing url for an image by checking relevant environment variables.
* @param string $filePath

View File

@ -106,6 +106,29 @@ class ImageTest extends TestCase
}
}
public function test_secure_images_included_in_exports()
{
config()->set('filesystems.default', 'local_secure');
$this->asEditor();
$galleryFile = $this->getTestImage('my-secure-test-upload');
$page = Page::first();
$expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload');
$upload = $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
$imageUrl = json_decode($upload->getContent(), true)['url'];
$page->html .= "<img src=\"{$imageUrl}\">";
$page->save();
$upload->assertStatus(200);
$encodedImageContent = base64_encode(file_get_contents($expectedPath));
$export = $this->get($page->getUrl('/export/html'));
$this->assertTrue(str_contains($export->getContent(), $encodedImageContent), 'Uploaded image in export content');
if (file_exists($expectedPath)) {
unlink($expectedPath);
}
}
public function test_system_images_remain_public()
{
config()->set('filesystems.default', 'local_secure');