1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2024-11-23 11:22:33 +01: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

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.
*
* @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==';
if (!$this->image_id) {
if (!$this->image_id || !$this->cover) {
return $default;
}
try {
$cover = $this->cover ? url($this->cover->getThumb($width, $height, false)) : $default;
return $this->cover->getThumb($width, $height, false) ?? $default;
} catch (Exception $err) {
$cover = $default;
return $default;
}
return $cover;
}
/**

View File

@ -3,6 +3,7 @@
namespace BookStack\Entities\Models;
use BookStack\Uploads\Image;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
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.
*
* @param int $width - Width of the image
* @param int $height - Height of the image
*
* @return string
* Returns shelf cover image, if cover not exists return default cover image.
*/
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
$default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
if (!$this->image_id) {
if (!$this->image_id || !$this->cover) {
return $default;
}
try {
$cover = $this->cover ? url($this->cover->getThumb($width, $height, false)) : $default;
} catch (\Exception $err) {
$cover = $default;
return $this->cover->getThumb($width, $height, false) ?? $default;
} catch (Exception $err) {
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
*/
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\ImageManager;
use League\Flysystem\WhitespacePathNormalizer;
use Psr\SimpleCache\InvalidArgumentException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\StreamedResponse;
class ImageService
{
protected ImageManager $imageTool;
protected Cache $cache;
protected FilesystemManager $fileSystem;
protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
public function __construct(ImageManager $imageTool, FilesystemManager $fileSystem, Cache $cache)
{
$this->imageTool = $imageTool;
$this->fileSystem = $fileSystem;
$this->cache = $cache;
public function __construct(
protected ImageManager $imageTool,
protected FilesystemManager $fileSystem,
protected Cache $cache
) {
}
/**
@ -206,7 +201,7 @@ class ImageService
* Save image data for the given path in the public space, if possible,
* 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);
@ -269,8 +264,14 @@ class ImageService
*
* @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
if ($keepRatio && $this->isGif($image)) {
return $this->getPublicUrl($image->path);
@ -305,7 +306,7 @@ class ImageService
return $this->getPublicUrl($image->path);
}
if (!$shouldCreate) {
if (!$shouldCreate && !$canCreate) {
return null;
}
@ -559,7 +560,7 @@ class ImageService
// Check the image file exists
&& $disk->exists($imagePath)
// 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
{
if (strpos($path, '/uploads/images/') === 0) {
if (str_starts_with($path, '/uploads/images/')) {
$path = substr($path, 15);
}
// Strip thumbnail element from path if existing
$originalPathSplit = array_filter(explode('/', $path), function (string $part) {
$resizedDir = (strpos($part, 'thumbs-') === 0 || strpos($part, 'scaled-') === 0);
$missingExtension = strpos($part, '.') === false;
$resizedDir = (str_starts_with($part, 'thumbs-') || str_starts_with($part, 'scaled-'));
$missingExtension = !str_contains($part, '.');
return !($resizedDir && $missingExtension);
});
@ -641,9 +642,9 @@ class ImageService
$url = ltrim(trim($url), '/');
// Handle potential relative paths
$isRelative = strpos($url, 'http') !== 0;
$isRelative = !str_starts_with($url, 'http');
if ($isRelative) {
if (strpos(strtolower($url), 'uploads/images') === 0) {
if (str_starts_with(strtolower($url), 'uploads/images')) {
return trim($url, '/');
}
@ -658,7 +659,7 @@ class ImageService
foreach ($potentialHostPaths as $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)), '/');
}
}
@ -679,7 +680,7 @@ class ImageService
// region-based url will be used to prevent http issues.
if (!$storageUrl && config('filesystems.images') === 's3') {
$storageDetails = config('filesystems.disks.s3');
if (strpos($storageDetails['bucket'], '.') === false) {
if (!str_contains($storageDetails['bucket'], '.')) {
$storageUrl = 'https://' . $storageDetails['bucket'] . '.s3.amazonaws.com';
} else {
$storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'];

View File

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