0
0
Fork 0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-04-12 16:08:08 +00:00

Images: Reverted some thumbnails to be on-demand generated

Added since we can't always be sure of future image usage, and in many
cases we don't generate ahead-of-time.
Also:
- Simplified image handling on certain models.
- Updated various string handling operations to use newer functions.
This commit is contained in:
Dan Brown 2023-09-30 12:09:29 +01:00
parent 5af3041b9b
commit 5c318a45b8
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
5 changed files with 38 additions and 49 deletions
app

View file

@ -40,26 +40,19 @@ class Book extends Entity implements HasCoverImage
/** /**
* Returns book cover image, if book cover not exists return default cover image. * Returns book cover image, if book cover not exists return default cover image.
*
* @param int $width - Width of the image
* @param int $height - Height of the image
*
* @return string
*/ */
public function getBookCover($width = 440, $height = 250) public function getBookCover(int $width = 440, int $height = 250): string
{ {
$default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; $default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
if (!$this->image_id) { if (!$this->image_id || !$this->cover) {
return $default; return $default;
} }
try { try {
$cover = $this->cover ? url($this->cover->getThumb($width, $height, false)) : $default; return $this->cover->getThumb($width, $height, false) ?? $default;
} catch (Exception $err) { } catch (Exception $err) {
$cover = $default; return $default;
} }
return $cover;
} }
/** /**

View file

@ -3,6 +3,7 @@
namespace BookStack\Entities\Models; namespace BookStack\Entities\Models;
use BookStack\Uploads\Image; use BookStack\Uploads\Image;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -49,28 +50,21 @@ class Bookshelf extends Entity implements HasCoverImage
} }
/** /**
* Returns BookShelf cover image, if cover does not exists return default cover image. * Returns shelf cover image, if cover not exists return default cover image.
*
* @param int $width - Width of the image
* @param int $height - Height of the image
*
* @return string
*/ */
public function getBookCover($width = 440, $height = 250) public function getBookCover(int $width = 440, int $height = 250): string
{ {
// TODO - Make generic, focused on books right now, Perhaps set-up a better image // TODO - Make generic, focused on books right now, Perhaps set-up a better image
$default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; $default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
if (!$this->image_id) { if (!$this->image_id || !$this->cover) {
return $default; return $default;
} }
try { try {
$cover = $this->cover ? url($this->cover->getThumb($width, $height, false)) : $default; return $this->cover->getThumb($width, $height, false) ?? $default;
} catch (\Exception $err) { } catch (Exception $err) {
$cover = $default; return $default;
} }
return $cover;
} }
/** /**

View file

@ -45,13 +45,14 @@ class Image extends Model
} }
/** /**
* Get an (already existing) thumbnail for this image. * Get a thumbnail URL for this image.
* Attempts to generate the thumbnail if not already existing.
* *
* @throws \Exception * @throws \Exception
*/ */
public function getThumb(?int $width, ?int $height, bool $keepRatio = false): ?string public function getThumb(?int $width, ?int $height, bool $keepRatio = false): ?string
{ {
return app()->make(ImageService::class)->getThumbnail($this, $width, $height, $keepRatio); return app()->make(ImageService::class)->getThumbnail($this, $width, $height, $keepRatio, false, true);
} }
/** /**

View file

@ -21,23 +21,18 @@ use Intervention\Image\Exception\NotSupportedException;
use Intervention\Image\Image as InterventionImage; use Intervention\Image\Image as InterventionImage;
use Intervention\Image\ImageManager; use Intervention\Image\ImageManager;
use League\Flysystem\WhitespacePathNormalizer; use League\Flysystem\WhitespacePathNormalizer;
use Psr\SimpleCache\InvalidArgumentException;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
class ImageService class ImageService
{ {
protected ImageManager $imageTool;
protected Cache $cache;
protected FilesystemManager $fileSystem;
protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
public function __construct(ImageManager $imageTool, FilesystemManager $fileSystem, Cache $cache) public function __construct(
{ protected ImageManager $imageTool,
$this->imageTool = $imageTool; protected FilesystemManager $fileSystem,
$this->fileSystem = $fileSystem; protected Cache $cache
$this->cache = $cache; ) {
} }
/** /**
@ -206,7 +201,7 @@ class ImageService
* Save image data for the given path in the public space, if possible, * Save image data for the given path in the public space, if possible,
* for the provided storage mechanism. * for the provided storage mechanism.
*/ */
protected function saveImageDataInPublicSpace(Storage $storage, string $path, string $data) protected function saveImageDataInPublicSpace(Storage $storage, string $path, string $data): void
{ {
$storage->put($path, $data); $storage->put($path, $data);
@ -269,8 +264,14 @@ class ImageService
* *
* @throws Exception * @throws Exception
*/ */
public function getThumbnail(Image $image, ?int $width, ?int $height, bool $keepRatio = false, bool $shouldCreate = false): ?string public function getThumbnail(
{ Image $image,
?int $width,
?int $height,
bool $keepRatio = false,
bool $shouldCreate = false,
bool $canCreate = false,
): ?string {
// Do not resize GIF images where we're not cropping // Do not resize GIF images where we're not cropping
if ($keepRatio && $this->isGif($image)) { if ($keepRatio && $this->isGif($image)) {
return $this->getPublicUrl($image->path); return $this->getPublicUrl($image->path);
@ -305,7 +306,7 @@ class ImageService
return $this->getPublicUrl($image->path); return $this->getPublicUrl($image->path);
} }
if (!$shouldCreate) { if (!$shouldCreate && !$canCreate) {
return null; return null;
} }
@ -559,7 +560,7 @@ class ImageService
// Check the image file exists // Check the image file exists
&& $disk->exists($imagePath) && $disk->exists($imagePath)
// Check the file is likely an image file // Check the file is likely an image file
&& strpos($disk->mimeType($imagePath), 'image/') === 0; && str_starts_with($disk->mimeType($imagePath), 'image/');
} }
/** /**
@ -568,14 +569,14 @@ class ImageService
*/ */
protected function checkUserHasAccessToRelationOfImageAtPath(string $path): bool protected function checkUserHasAccessToRelationOfImageAtPath(string $path): bool
{ {
if (strpos($path, '/uploads/images/') === 0) { if (str_starts_with($path, '/uploads/images/')) {
$path = substr($path, 15); $path = substr($path, 15);
} }
// Strip thumbnail element from path if existing // Strip thumbnail element from path if existing
$originalPathSplit = array_filter(explode('/', $path), function (string $part) { $originalPathSplit = array_filter(explode('/', $path), function (string $part) {
$resizedDir = (strpos($part, 'thumbs-') === 0 || strpos($part, 'scaled-') === 0); $resizedDir = (str_starts_with($part, 'thumbs-') || str_starts_with($part, 'scaled-'));
$missingExtension = strpos($part, '.') === false; $missingExtension = !str_contains($part, '.');
return !($resizedDir && $missingExtension); return !($resizedDir && $missingExtension);
}); });
@ -641,9 +642,9 @@ class ImageService
$url = ltrim(trim($url), '/'); $url = ltrim(trim($url), '/');
// Handle potential relative paths // Handle potential relative paths
$isRelative = strpos($url, 'http') !== 0; $isRelative = !str_starts_with($url, 'http');
if ($isRelative) { if ($isRelative) {
if (strpos(strtolower($url), 'uploads/images') === 0) { if (str_starts_with(strtolower($url), 'uploads/images')) {
return trim($url, '/'); return trim($url, '/');
} }
@ -658,7 +659,7 @@ class ImageService
foreach ($potentialHostPaths as $potentialBasePath) { foreach ($potentialHostPaths as $potentialBasePath) {
$potentialBasePath = strtolower($potentialBasePath); $potentialBasePath = strtolower($potentialBasePath);
if (strpos(strtolower($url), $potentialBasePath) === 0) { if (str_starts_with(strtolower($url), $potentialBasePath)) {
return 'uploads/images/' . trim(substr($url, strlen($potentialBasePath)), '/'); return 'uploads/images/' . trim(substr($url, strlen($potentialBasePath)), '/');
} }
} }
@ -679,7 +680,7 @@ class ImageService
// region-based url will be used to prevent http issues. // region-based url will be used to prevent http issues.
if (!$storageUrl && config('filesystems.images') === 's3') { if (!$storageUrl && config('filesystems.images') === 's3') {
$storageDetails = config('filesystems.disks.s3'); $storageDetails = config('filesystems.disks.s3');
if (strpos($storageDetails['bucket'], '.') === false) { if (!str_contains($storageDetails['bucket'], '.')) {
$storageUrl = 'https://' . $storageDetails['bucket'] . '.s3.amazonaws.com'; $storageUrl = 'https://' . $storageDetails['bucket'] . '.s3.amazonaws.com';
} else { } else {
$storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket']; $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];

View file

@ -244,7 +244,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
} }
try { try {
$avatar = $this->avatar ? url($this->avatar->getThumb($size, $size, false)) : $default; $avatar = $this->avatar?->getThumb($size, $size, false) ?? $default;
} catch (Exception $err) { } catch (Exception $err) {
$avatar = $default; $avatar = $default;
} }