Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions app/Entities/Models/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,19 @@ public function getUrl(string $path = ''): string

/**
* 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;
}

/**
Expand Down
20 changes: 7 additions & 13 deletions app/Entities/Models/Bookshelf.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -49,28 +50,21 @@ public function getUrl(string $path = ''): string
}

/**
* 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;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion app/Entities/Tools/ExportFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ protected function containHtml(string $htmlContent): string
foreach ($imageTagsOutput[0] as $index => $imgMatch) {
$oldImgTagString = $imgMatch;
$srcString = $imageTagsOutput[2][$index];
$imageEncoded = $this->imageService->imageUriToBase64($srcString);
$imageEncoded = $this->imageService->imageUrlToBase64($srcString);
if ($imageEncoded === null) {
$imageEncoded = $srcString;
}
Expand Down
42 changes: 41 additions & 1 deletion app/Exceptions/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\PostTooLargeException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Throwable;

Expand All @@ -35,6 +37,15 @@ class Handler extends ExceptionHandler
'password_confirmation',
];

/**
* A function to run upon out of memory.
* If it returns a response, that will be provided back to the request
* upon an out of memory event.
*
* @var ?callable<?\Illuminate\Http\Response>
*/
protected $onOutOfMemory = null;

/**
* Report or log an exception.
*
Expand All @@ -59,19 +70,48 @@ public function report(Throwable $exception)
*/
public function render($request, Throwable $e)
{
if ($e instanceof FatalError && str_contains($e->getMessage(), 'bytes exhausted (tried to allocate') && $this->onOutOfMemory) {
$response = call_user_func($this->onOutOfMemory);
if ($response) {
return $response;
}
}

if ($e instanceof PostTooLargeException) {
$e = new NotifyException(trans('errors.server_post_limit'), '/', 413);
}

if ($this->isApiRequest($request)) {
return $this->renderApiException($e);
}

return parent::render($request, $e);
}

/**
* Provide a function to be called when an out of memory event occurs.
* If the callable returns a response, this response will be returned
* to the request upon error.
*/
public function prepareForOutOfMemory(callable $onOutOfMemory)
{
$this->onOutOfMemory = $onOutOfMemory;
}

/**
* Forget the current out of memory handler, if existing.
*/
public function forgetOutOfMemoryHandler()
{
$this->onOutOfMemory = null;
}

/**
* Check if the given request is an API request.
*/
protected function isApiRequest(Request $request): bool
{
return strpos($request->path(), 'api/') === 0;
return str_starts_with($request->path(), 'api/');
}

/**
Expand Down
27 changes: 18 additions & 9 deletions app/Uploads/Controllers/DrawioImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,44 @@
use BookStack\Exceptions\ImageUploadException;
use BookStack\Http\Controller;
use BookStack\Uploads\ImageRepo;
use BookStack\Uploads\ImageResizer;
use BookStack\Util\OutOfMemoryHandler;
use Exception;
use Illuminate\Http\Request;

class DrawioImageController extends Controller
{
protected $imageRepo;

public function __construct(ImageRepo $imageRepo)
{
$this->imageRepo = $imageRepo;
public function __construct(
protected ImageRepo $imageRepo
) {
}

/**
* Get a list of gallery images, in a list.
* Can be paged and filtered by entity.
*/
public function list(Request $request)
public function list(Request $request, ImageResizer $resizer)
{
$page = $request->get('page', 1);
$searchTerm = $request->get('search', null);
$uploadedToFilter = $request->get('uploaded_to', null);
$parentTypeFilter = $request->get('filter_type', null);

$imgData = $this->imageRepo->getEntityFiltered('drawio', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm);

return view('pages.parts.image-manager-list', [
$viewData = [
'warning' => '',
'images' => $imgData['images'],
'hasMore' => $imgData['has_more'],
]);
];

new OutOfMemoryHandler(function () use ($viewData) {
$viewData['warning'] = trans('errors.image_gallery_thumbnail_memory_limit');
return response()->view('pages.parts.image-manager-list', $viewData, 200);
});

$resizer->loadGalleryThumbnailsForMany($imgData['images']);

return view('pages.parts.image-manager-list', $viewData);
}

/**
Expand Down
25 changes: 21 additions & 4 deletions app/Uploads/Controllers/GalleryImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
use BookStack\Exceptions\ImageUploadException;
use BookStack\Http\Controller;
use BookStack\Uploads\ImageRepo;
use BookStack\Uploads\ImageResizer;
use BookStack\Util\OutOfMemoryHandler;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;

class GalleryImageController extends Controller
Expand All @@ -19,19 +23,28 @@ public function __construct(
* Get a list of gallery images, in a list.
* Can be paged and filtered by entity.
*/
public function list(Request $request)
public function list(Request $request, ImageResizer $resizer)
{
$page = $request->get('page', 1);
$searchTerm = $request->get('search', null);
$uploadedToFilter = $request->get('uploaded_to', null);
$parentTypeFilter = $request->get('filter_type', null);

$imgData = $this->imageRepo->getEntityFiltered('gallery', $parentTypeFilter, $page, 30, $uploadedToFilter, $searchTerm);

return view('pages.parts.image-manager-list', [
$viewData = [
'warning' => '',
'images' => $imgData['images'],
'hasMore' => $imgData['has_more'],
]);
];

new OutOfMemoryHandler(function () use ($viewData) {
$viewData['warning'] = trans('errors.image_gallery_thumbnail_memory_limit');
return response()->view('pages.parts.image-manager-list', $viewData, 200);
});

$resizer->loadGalleryThumbnailsForMany($imgData['images']);

return view('pages.parts.image-manager-list', $viewData);
}

/**
Expand All @@ -51,6 +64,10 @@ public function create(Request $request)
return $this->jsonError(implode("\n", $exception->errors()['file']));
}

new OutOfMemoryHandler(function () {
return $this->jsonError(trans('errors.image_upload_memory_limit'));
});

try {
$imageUpload = $request->file('file');
$uploadedTo = $request->get('uploaded_to', 0);
Expand Down
57 changes: 43 additions & 14 deletions app/Uploads/Controllers/ImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@

use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\NotifyException;
use BookStack\Http\Controller;
use BookStack\Uploads\Image;
use BookStack\Uploads\ImageRepo;
use BookStack\Uploads\ImageResizer;
use BookStack\Uploads\ImageService;
use BookStack\Util\OutOfMemoryHandler;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class ImageController extends Controller
{
public function __construct(
protected ImageRepo $imageRepo,
protected ImageService $imageService
protected ImageService $imageService,
protected ImageResizer $imageResizer,
) {
}

Expand All @@ -38,23 +41,18 @@ public function showImage(string $path)

/**
* Update image details.
*
* @throws ImageUploadException
* @throws ValidationException
*/
public function update(Request $request, string $id)
{
$this->validate($request, [
$data = $this->validate($request, [
'name' => ['required', 'min:2', 'string'],
]);

$image = $this->imageRepo->getById($id);
$this->checkImagePermission($image);
$this->checkOwnablePermission('image-update', $image);

$image = $this->imageRepo->updateImageDetails($image, $request->all());

$this->imageRepo->loadThumbs($image);
$image = $this->imageRepo->updateImageDetails($image, $data);

return view('pages.parts.image-manager-form', [
'image' => $image,
Expand All @@ -76,6 +74,10 @@ public function updateFile(Request $request, string $id)
$this->checkOwnablePermission('image-update', $image);
$file = $request->file('file');

new OutOfMemoryHandler(function () {
return $this->jsonError(trans('errors.image_upload_memory_limit'));
});

try {
$this->imageRepo->updateImageFile($image, $file);
} catch (ImageUploadException $exception) {
Expand All @@ -99,12 +101,20 @@ public function edit(Request $request, string $id)
$dependantPages = $this->imageRepo->getPagesUsingImage($image);
}

$this->imageRepo->loadThumbs($image);

return view('pages.parts.image-manager-form', [
$viewData = [
'image' => $image,
'dependantPages' => $dependantPages ?? null,
]);
'warning' => '',
];

new OutOfMemoryHandler(function () use ($viewData) {
$viewData['warning'] = trans('errors.image_thumbnail_memory_limit');
return response()->view('pages.parts.image-manager-form', $viewData);
});

$this->imageResizer->loadGalleryThumbnailsForImage($image, false);

return view('pages.parts.image-manager-form', $viewData);
}

/**
Expand All @@ -123,10 +133,29 @@ public function destroy(string $id)
return response('');
}

/**
* Rebuild the thumbnails for the given image.
*/
public function rebuildThumbnails(string $id)
{
$image = $this->imageRepo->getById($id);
$this->checkImagePermission($image);
$this->checkOwnablePermission('image-update', $image);

new OutOfMemoryHandler(function () {
return $this->jsonError(trans('errors.image_thumbnail_memory_limit'));
});

$this->imageResizer->loadGalleryThumbnailsForImage($image, true);

return response(trans('components.image_rebuild_thumbs_success'));
}

/**
* Check related page permission and ensure type is drawio or gallery.
* @throws NotifyException
*/
protected function checkImagePermission(Image $image)
protected function checkImagePermission(Image $image): void
{
if ($image->type !== 'drawio' && $image->type !== 'gallery') {
$this->showPermissionError();
Expand Down
Loading