diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index 7e710edaf..a5ab4ea9a 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -732,18 +732,21 @@ class PermissionService } /** - * Filters pages that are a direct relation to another item. + * Add conditions to a query to filter the selection to related entities + * where permissions are granted. + * @param $entityType * @param $query * @param $tableName * @param $entityIdColumn * @return mixed */ - public function filterRelatedPages($query, $tableName, $entityIdColumn) + public function filterRelatedEntity($entityType, $query, $tableName, $entityIdColumn) { $this->currentAction = 'view'; $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn]; - $pageMorphClass = $this->entityProvider->page->getMorphClass(); + $pageMorphClass = $this->entityProvider->get($entityType)->getMorphClass(); + $q = $query->where(function ($query) use ($tableDetails, $pageMorphClass) { $query->where(function ($query) use (&$tableDetails, $pageMorphClass) { $query->whereExists(function ($permissionQuery) use (&$tableDetails, $pageMorphClass) { @@ -761,7 +764,9 @@ class PermissionService }); })->orWhere($tableDetails['entityIdColumn'], '=', 0); }); + $this->clean(); + return $q; } diff --git a/app/Auth/User.php b/app/Auth/User.php index 05e77e13d..12f022b06 100644 --- a/app/Auth/User.php +++ b/app/Auth/User.php @@ -24,7 +24,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon * The attributes that are mass assignable. * @var array */ - protected $fillable = ['name', 'email', 'image_id']; + protected $fillable = ['name', 'email']; /** * The attributes excluded from the model's JSON form. diff --git a/app/Auth/UserRepo.php b/app/Auth/UserRepo.php index 1a73d0072..94ebb27ab 100644 --- a/app/Auth/UserRepo.php +++ b/app/Auth/UserRepo.php @@ -198,7 +198,7 @@ class UserRepo $user->delete(); // Delete user profile images - $profileImages = $images = Image::where('type', '=', 'user')->where('created_by', '=', $user->id)->get(); + $profileImages = Image::where('type', '=', 'user')->where('uploaded_to', '=', $user->id)->get(); foreach ($profileImages as $image) { Images::destroy($image); } diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php index 88ca1bca6..1cff46da1 100644 --- a/app/Entities/Repos/EntityRepo.php +++ b/app/Entities/Repos/EntityRepo.php @@ -762,7 +762,7 @@ class EntityRepo */ public function searchForImage($imageString) { - $pages = $this->entityQuery('page')->where('html', 'like', '%' . $imageString . '%')->get(); + $pages = $this->entityQuery('page')->where('html', 'like', '%' . $imageString . '%')->get(['id', 'name', 'slug', 'book_id']); foreach ($pages as $page) { $page->url = $page->getUrl(); $page->html = ''; diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 24e0d784d..7c8ad5c2f 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -6,6 +6,7 @@ use BookStack\Entities\Book; use BookStack\Entities\EntityContextManager; use BookStack\Entities\Repos\EntityRepo; use BookStack\Entities\ExportService; +use BookStack\Uploads\ImageRepo; use Illuminate\Http\Request; use Illuminate\Http\Response; use Views; @@ -17,6 +18,7 @@ class BookController extends Controller protected $userRepo; protected $exportService; protected $entityContextManager; + protected $imageRepo; /** * BookController constructor. @@ -24,17 +26,20 @@ class BookController extends Controller * @param UserRepo $userRepo * @param ExportService $exportService * @param EntityContextManager $entityContextManager + * @param ImageRepo $imageRepo */ public function __construct( EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService, - EntityContextManager $entityContextManager + EntityContextManager $entityContextManager, + ImageRepo $imageRepo ) { $this->entityRepo = $entityRepo; $this->userRepo = $userRepo; $this->exportService = $exportService; $this->entityContextManager = $entityContextManager; + $this->imageRepo = $imageRepo; parent::__construct(); } @@ -101,13 +106,15 @@ class BookController extends Controller * @param string $shelfSlug * @return Response * @throws \BookStack\Exceptions\NotFoundException + * @throws \BookStack\Exceptions\ImageUploadException */ public function store(Request $request, string $shelfSlug = null) { $this->checkPermission('book-create-all'); $this->validate($request, [ 'name' => 'required|string|max:255', - 'description' => 'string|max:1000' + 'description' => 'string|max:1000', + 'image' => $this->imageRepo->getImageValidationRules(), ]); $bookshelf = null; @@ -117,6 +124,7 @@ class BookController extends Controller } $book = $this->entityRepo->createFromInput('book', $request->all()); + $this->bookUpdateActions($book, $request); Activity::add($book, 'book_create', $book->id); if ($bookshelf) { @@ -170,20 +178,27 @@ class BookController extends Controller /** * Update the specified book in storage. - * @param Request $request + * @param Request $request * @param $slug * @return Response + * @throws \BookStack\Exceptions\ImageUploadException + * @throws \BookStack\Exceptions\NotFoundException */ - public function update(Request $request, $slug) + public function update(Request $request, string $slug) { $book = $this->entityRepo->getBySlug('book', $slug); $this->checkOwnablePermission('book-update', $book); $this->validate($request, [ 'name' => 'required|string|max:255', - 'description' => 'string|max:1000' + 'description' => 'string|max:1000', + 'image' => $this->imageRepo->getImageValidationRules(), ]); + $book = $this->entityRepo->updateFromInput('book', $book, $request->all()); + $this->bookUpdateActions($book, $request); + Activity::add($book, 'book_update', $book->id); + return redirect($book->getUrl()); } @@ -311,7 +326,12 @@ class BookController extends Controller $book = $this->entityRepo->getBySlug('book', $bookSlug); $this->checkOwnablePermission('book-delete', $book); Activity::addMessage('book_delete', 0, $book->name); + + if ($book->cover) { + $this->imageRepo->destroyImage($book->cover); + } $this->entityRepo->destroyBook($book); + return redirect('/books'); } @@ -383,4 +403,28 @@ class BookController extends Controller $textContent = $this->exportService->bookToPlainText($book); return $this->downloadResponse($textContent, $bookSlug . '.txt'); } + + /** + * Common actions to run on book update. + * Handles updating the cover image. + * @param Book $book + * @param Request $request + * @throws \BookStack\Exceptions\ImageUploadException + */ + protected function bookUpdateActions(Book $book, Request $request) + { + // Update the cover image if in request + if ($request->has('image')) { + $newImage = $request->file('image'); + $image = $this->imageRepo->saveNew($newImage, 'cover_book', $book->id, 512, 512, true); + $book->image_id = $image->id; + $book->save(); + } + + if ($request->has('image_reset')) { + $this->imageRepo->destroyImage($book->cover); + $book->image_id = 0; + $book->save(); + } + } } diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index b86bc2e38..dba2503ef 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -5,6 +5,7 @@ use BookStack\Auth\UserRepo; use BookStack\Entities\Bookshelf; use BookStack\Entities\EntityContextManager; use BookStack\Entities\Repos\EntityRepo; +use BookStack\Uploads\ImageRepo; use Illuminate\Http\Request; use Illuminate\Http\Response; use Views; @@ -15,18 +16,21 @@ class BookshelfController extends Controller protected $entityRepo; protected $userRepo; protected $entityContextManager; + protected $imageRepo; /** * BookController constructor. * @param EntityRepo $entityRepo * @param UserRepo $userRepo * @param EntityContextManager $entityContextManager + * @param ImageRepo $imageRepo */ - public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, EntityContextManager $entityContextManager) + public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, EntityContextManager $entityContextManager, ImageRepo $imageRepo) { $this->entityRepo = $entityRepo; $this->userRepo = $userRepo; $this->entityContextManager = $entityContextManager; + $this->imageRepo = $imageRepo; parent::__construct(); } @@ -82,8 +86,9 @@ class BookshelfController extends Controller /** * Store a newly created bookshelf in storage. - * @param Request $request + * @param Request $request * @return Response + * @throws \BookStack\Exceptions\ImageUploadException */ public function store(Request $request) { @@ -91,13 +96,14 @@ class BookshelfController extends Controller $this->validate($request, [ 'name' => 'required|string|max:255', 'description' => 'string|max:1000', + 'image' => $this->imageRepo->getImageValidationRules(), ]); - $bookshelf = $this->entityRepo->createFromInput('bookshelf', $request->all()); - $this->entityRepo->updateShelfBooks($bookshelf, $request->get('books', '')); - Activity::add($bookshelf, 'bookshelf_create'); + $shelf = $this->entityRepo->createFromInput('bookshelf', $request->all()); + $this->shelfUpdateActions($shelf, $request); - return redirect($bookshelf->getUrl()); + Activity::add($shelf, 'bookshelf_create'); + return redirect($shelf->getUrl()); } @@ -109,19 +115,19 @@ class BookshelfController extends Controller */ public function show(string $slug) { - /** @var Bookshelf $bookshelf */ - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); - $this->checkOwnablePermission('book-view', $bookshelf); + /** @var Bookshelf $shelf */ + $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); + $this->checkOwnablePermission('book-view', $shelf); - $books = $this->entityRepo->getBookshelfChildren($bookshelf); - Views::add($bookshelf); - $this->entityContextManager->setShelfContext($bookshelf->id); + $books = $this->entityRepo->getBookshelfChildren($shelf); + Views::add($shelf); + $this->entityContextManager->setShelfContext($shelf->id); - $this->setPageTitle($bookshelf->getShortName()); + $this->setPageTitle($shelf->getShortName()); return view('shelves.show', [ - 'shelf' => $bookshelf, + 'shelf' => $shelf, 'books' => $books, - 'activity' => Activity::entityActivity($bookshelf, 20, 1) + 'activity' => Activity::entityActivity($shelf, 20, 1) ]); } @@ -133,19 +139,19 @@ class BookshelfController extends Controller */ public function edit(string $slug) { - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */ - $this->checkOwnablePermission('bookshelf-update', $bookshelf); + $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $this->checkOwnablePermission('bookshelf-update', $shelf); - $shelfBooks = $this->entityRepo->getBookshelfChildren($bookshelf); + $shelfBooks = $this->entityRepo->getBookshelfChildren($shelf); $shelfBookIds = $shelfBooks->pluck('id'); $books = $this->entityRepo->getAll('book', false, 'update'); $books = $books->filter(function ($book) use ($shelfBookIds) { return !$shelfBookIds->contains($book->id); }); - $this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $bookshelf->getShortName()])); + $this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $shelf->getShortName()])); return view('shelves.edit', [ - 'shelf' => $bookshelf, + 'shelf' => $shelf, 'books' => $books, 'shelfBooks' => $shelfBooks, ]); @@ -154,10 +160,11 @@ class BookshelfController extends Controller /** * Update the specified bookshelf in storage. - * @param Request $request + * @param Request $request * @param string $slug * @return Response * @throws \BookStack\Exceptions\NotFoundException + * @throws \BookStack\Exceptions\ImageUploadException */ public function update(Request $request, string $slug) { @@ -166,10 +173,12 @@ class BookshelfController extends Controller $this->validate($request, [ 'name' => 'required|string|max:255', 'description' => 'string|max:1000', + 'image' => $this->imageRepo->getImageValidationRules(), ]); $shelf = $this->entityRepo->updateFromInput('bookshelf', $shelf, $request->all()); - $this->entityRepo->updateShelfBooks($shelf, $request->get('books', '')); + $this->shelfUpdateActions($shelf, $request); + Activity::add($shelf, 'bookshelf_update'); return redirect($shelf->getUrl()); @@ -184,11 +193,11 @@ class BookshelfController extends Controller */ public function showDelete(string $slug) { - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */ - $this->checkOwnablePermission('bookshelf-delete', $bookshelf); + $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $this->checkOwnablePermission('bookshelf-delete', $shelf); - $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $bookshelf->getShortName()])); - return view('shelves.delete', ['shelf' => $bookshelf]); + $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()])); + return view('shelves.delete', ['shelf' => $shelf]); } /** @@ -200,10 +209,15 @@ class BookshelfController extends Controller */ public function destroy(string $slug) { - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */ - $this->checkOwnablePermission('bookshelf-delete', $bookshelf); - Activity::addMessage('bookshelf_delete', 0, $bookshelf->name); - $this->entityRepo->destroyBookshelf($bookshelf); + $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $shelf Bookshelf */ + $this->checkOwnablePermission('bookshelf-delete', $shelf); + Activity::addMessage('bookshelf_delete', 0, $shelf->name); + + if ($shelf->cover) { + $this->imageRepo->destroyImage($shelf->cover); + } + $this->entityRepo->destroyBookshelf($shelf); + return redirect('/shelves'); } @@ -215,12 +229,12 @@ class BookshelfController extends Controller */ public function showPermissions(string $slug) { - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); - $this->checkOwnablePermission('restrictions-manage', $bookshelf); + $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); + $this->checkOwnablePermission('restrictions-manage', $shelf); $roles = $this->userRepo->getRestrictableRoles(); return view('shelves.permissions', [ - 'shelf' => $bookshelf, + 'shelf' => $shelf, 'roles' => $roles ]); } @@ -235,12 +249,12 @@ class BookshelfController extends Controller */ public function permissions(string $slug, Request $request) { - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); - $this->checkOwnablePermission('restrictions-manage', $bookshelf); + $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); + $this->checkOwnablePermission('restrictions-manage', $shelf); - $this->entityRepo->updateEntityPermissionsFromRequest($request, $bookshelf); + $this->entityRepo->updateEntityPermissionsFromRequest($request, $shelf); session()->flash('success', trans('entities.shelves_permissions_updated')); - return redirect($bookshelf->getUrl()); + return redirect($shelf->getUrl()); } /** @@ -251,11 +265,37 @@ class BookshelfController extends Controller */ public function copyPermissions(string $slug) { - $bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); - $this->checkOwnablePermission('restrictions-manage', $bookshelf); + $shelf = $this->entityRepo->getBySlug('bookshelf', $slug); + $this->checkOwnablePermission('restrictions-manage', $shelf); - $updateCount = $this->entityRepo->copyBookshelfPermissions($bookshelf); + $updateCount = $this->entityRepo->copyBookshelfPermissions($shelf); session()->flash('success', trans('entities.shelves_copy_permission_success', ['count' => $updateCount])); - return redirect($bookshelf->getUrl()); + return redirect($shelf->getUrl()); + } + + /** + * Common actions to run on bookshelf update. + * @param Bookshelf $shelf + * @param Request $request + * @throws \BookStack\Exceptions\ImageUploadException + */ + protected function shelfUpdateActions(Bookshelf $shelf, Request $request) + { + // Update the books that the shelf references + $this->entityRepo->updateShelfBooks($shelf, $request->get('books', '')); + + // Update the cover image if in request + if ($request->has('image')) { + $newImage = $request->file('image'); + $image = $this->imageRepo->saveNew($newImage, 'cover_shelf', $shelf->id, 512, 512, true); + $shelf->image_id = $image->id; + $shelf->save(); + } + + if ($request->has('image_reset')) { + $this->imageRepo->destroyImage($shelf->cover); + $shelf->image_id = 0; + $shelf->save(); + } } } diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php deleted file mode 100644 index 4d6f759b3..000000000 --- a/app/Http/Controllers/ImageController.php +++ /dev/null @@ -1,246 +0,0 @@ -image = $image; - $this->file = $file; - $this->imageRepo = $imageRepo; - parent::__construct(); - } - - /** - * Provide an image file from storage. - * @param string $path - * @return mixed - */ - public function showImage(string $path) - { - $path = storage_path('uploads/images/' . $path); - if (!file_exists($path)) { - abort(404); - } - - return response()->file($path); - } - - /** - * Get all images for a specific type, Paginated - * @param string $type - * @param int $page - * @return \Illuminate\Http\JsonResponse - */ - public function getAllByType($type, $page = 0) - { - $imgData = $this->imageRepo->getPaginatedByType($type, $page); - return response()->json($imgData); - } - - /** - * Search through images within a particular type. - * @param $type - * @param int $page - * @param Request $request - * @return mixed - */ - public function searchByType(Request $request, $type, $page = 0) - { - $this->validate($request, [ - 'term' => 'required|string' - ]); - - $searchTerm = $request->get('term'); - $imgData = $this->imageRepo->searchPaginatedByType($type, $searchTerm, $page, 24); - return response()->json($imgData); - } - - /** - * Get all images for a user. - * @param int $page - * @return \Illuminate\Http\JsonResponse - */ - public function getAllForUserType($page = 0) - { - $imgData = $this->imageRepo->getPaginatedByType('user', $page, 24, $this->currentUser->id); - return response()->json($imgData); - } - - /** - * Get gallery images with a specific filter such as book or page - * @param $filter - * @param int $page - * @param Request $request - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response - */ - public function getGalleryFiltered(Request $request, $filter, $page = 0) - { - $this->validate($request, [ - 'page_id' => 'required|integer' - ]); - - $validFilters = collect(['page', 'book']); - if (!$validFilters->contains($filter)) { - return response('Invalid filter', 500); - } - - $pageId = $request->get('page_id'); - $imgData = $this->imageRepo->getGalleryFiltered(strtolower($filter), $pageId, $page, 24); - - return response()->json($imgData); - } - - /** - * Handles image uploads for use on pages. - * @param string $type - * @param Request $request - * @return \Illuminate\Http\JsonResponse - * @throws \Exception - */ - public function uploadByType($type, Request $request) - { - $this->checkPermission('image-create-all'); - $this->validate($request, [ - 'file' => 'image_extension|no_double_extension|mimes:jpeg,png,gif,bmp,webp,tiff' - ]); - - if (!$this->imageRepo->isValidType($type)) { - return $this->jsonError(trans('errors.image_upload_type_error')); - } - - $imageUpload = $request->file('file'); - - try { - $uploadedTo = $request->get('uploaded_to', 0); - $image = $this->imageRepo->saveNew($imageUpload, $type, $uploadedTo); - } catch (ImageUploadException $e) { - return response($e->getMessage(), 500); - } - - return response()->json($image); - } - - /** - * Upload a drawing to the system. - * @param Request $request - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response - */ - public function uploadDrawing(Request $request) - { - $this->validate($request, [ - 'image' => 'required|string', - 'uploaded_to' => 'required|integer' - ]); - $this->checkPermission('image-create-all'); - $imageBase64Data = $request->get('image'); - - try { - $uploadedTo = $request->get('uploaded_to', 0); - $image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo); - } catch (ImageUploadException $e) { - return response($e->getMessage(), 500); - } - - return response()->json($image); - } - - /** - * Get the content of an image based64 encoded. - * @param $id - * @return \Illuminate\Http\JsonResponse|mixed - */ - public function getBase64Image($id) - { - $image = $this->imageRepo->getById($id); - $imageData = $this->imageRepo->getImageData($image); - if ($imageData === null) { - return $this->jsonError("Image data could not be found"); - } - return response()->json([ - 'content' => base64_encode($imageData) - ]); - } - - /** - * Generate a sized thumbnail for an image. - * @param $id - * @param $width - * @param $height - * @param $crop - * @return \Illuminate\Http\JsonResponse - * @throws ImageUploadException - * @throws \Exception - */ - public function getThumbnail($id, $width, $height, $crop) - { - $this->checkPermission('image-create-all'); - $image = $this->imageRepo->getById($id); - $thumbnailUrl = $this->imageRepo->getThumbnail($image, $width, $height, $crop == 'false'); - return response()->json(['url' => $thumbnailUrl]); - } - - /** - * Update image details - * @param integer $imageId - * @param Request $request - * @return \Illuminate\Http\JsonResponse - * @throws ImageUploadException - * @throws \Exception - */ - public function update($imageId, Request $request) - { - $this->validate($request, [ - 'name' => 'required|min:2|string' - ]); - $image = $this->imageRepo->getById($imageId); - $this->checkOwnablePermission('image-update', $image); - $image = $this->imageRepo->updateImageDetails($image, $request->all()); - return response()->json($image); - } - - /** - * Show the usage of an image on pages. - * @param \BookStack\Entities\Repos\EntityRepo $entityRepo - * @param $id - * @return \Illuminate\Http\JsonResponse - */ - public function usage(EntityRepo $entityRepo, $id) - { - $image = $this->imageRepo->getById($id); - $pageSearch = $entityRepo->searchForImage($image->url); - return response()->json($pageSearch); - } - - /** - * Deletes an image and all thumbnail/image files - * @param int $id - * @return \Illuminate\Http\JsonResponse - * @throws \Exception - */ - public function destroy($id) - { - $image = $this->imageRepo->getById($id); - $this->checkOwnablePermission('image-delete', $image); - - $this->imageRepo->destroyImage($image); - return response()->json(trans('components.images_deleted')); - } -} diff --git a/app/Http/Controllers/Images/DrawioImageController.php b/app/Http/Controllers/Images/DrawioImageController.php new file mode 100644 index 000000000..2deb64d96 --- /dev/null +++ b/app/Http/Controllers/Images/DrawioImageController.php @@ -0,0 +1,89 @@ +imageRepo = $imageRepo; + parent::__construct(); + } + + /** + * Get a list of gallery images, in a list. + * Can be paged and filtered by entity. + * @param Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function list(Request $request) + { + $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 response()->json($imgData); + } + + /** + * Store a new gallery image in the system. + * @param Request $request + * @return Illuminate\Http\JsonResponse + * @throws \Exception + */ + public function create(Request $request) + { + $this->validate($request, [ + 'image' => 'required|string', + 'uploaded_to' => 'required|integer' + ]); + + $this->checkPermission('image-create-all'); + $imageBase64Data = $request->get('image'); + + try { + $uploadedTo = $request->get('uploaded_to', 0); + $image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo); + } catch (ImageUploadException $e) { + return response($e->getMessage(), 500); + } + + return response()->json($image); + } + + /** + * Get the content of an image based64 encoded. + * @param $id + * @return \Illuminate\Http\JsonResponse|mixed + */ + public function getAsBase64($id) + { + $image = $this->imageRepo->getById($id); + $page = $image->getPage(); + if ($image === null || $image->type !== 'drawio' || !userCan('page-view', $page)) { + return $this->jsonError("Image data could not be found"); + } + + $imageData = $this->imageRepo->getImageData($image); + if ($imageData === null) { + return $this->jsonError("Image data could not be found"); + } + return response()->json([ + 'content' => base64_encode($imageData) + ]); + } + +} diff --git a/app/Http/Controllers/Images/GalleryImageController.php b/app/Http/Controllers/Images/GalleryImageController.php new file mode 100644 index 000000000..35087463b --- /dev/null +++ b/app/Http/Controllers/Images/GalleryImageController.php @@ -0,0 +1,65 @@ +imageRepo = $imageRepo; + parent::__construct(); + } + + /** + * Get a list of gallery images, in a list. + * Can be paged and filtered by entity. + * @param Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function list(Request $request) + { + $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, 24, $uploadedToFilter, $searchTerm); + return response()->json($imgData); + } + + /** + * Store a new gallery image in the system. + * @param Request $request + * @return Illuminate\Http\JsonResponse + * @throws \Exception + */ + public function create(Request $request) + { + $this->checkPermission('image-create-all'); + $this->validate($request, [ + 'file' => $this->imageRepo->getImageValidationRules() + ]); + + try { + $imageUpload = $request->file('file'); + $uploadedTo = $request->get('uploaded_to', 0); + $image = $this->imageRepo->saveNew($imageUpload, 'gallery', $uploadedTo); + } catch (ImageUploadException $e) { + return response($e->getMessage(), 500); + } + + return response()->json($image); + } + +} diff --git a/app/Http/Controllers/Images/ImageController.php b/app/Http/Controllers/Images/ImageController.php new file mode 100644 index 000000000..024003f87 --- /dev/null +++ b/app/Http/Controllers/Images/ImageController.php @@ -0,0 +1,115 @@ +image = $image; + $this->file = $file; + $this->imageRepo = $imageRepo; + parent::__construct(); + } + + /** + * Provide an image file from storage. + * @param string $path + * @return mixed + */ + public function showImage(string $path) + { + $path = storage_path('uploads/images/' . $path); + if (!file_exists($path)) { + abort(404); + } + + return response()->file($path); + } + + + /** + * Update image details + * @param integer $id + * @param Request $request + * @return \Illuminate\Http\JsonResponse + * @throws ImageUploadException + * @throws \Exception + */ + public function update($id, Request $request) + { + $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()); + return response()->json($image); + } + + /** + * Show the usage of an image on pages. + * @param \BookStack\Entities\Repos\EntityRepo $entityRepo + * @param $id + * @return \Illuminate\Http\JsonResponse + */ + public function usage(EntityRepo $entityRepo, $id) + { + $image = $this->imageRepo->getById($id); + $this->checkImagePermission($image); + $pageSearch = $entityRepo->searchForImage($image->url); + return response()->json($pageSearch); + } + + /** + * Deletes an image and all thumbnail/image files + * @param int $id + * @return \Illuminate\Http\JsonResponse + * @throws \Exception + */ + public function destroy($id) + { + $image = $this->imageRepo->getById($id); + $this->checkOwnablePermission('image-delete', $image); + $this->checkImagePermission($image); + + $this->imageRepo->destroyImage($image); + return response()->json(trans('components.images_deleted')); + } + + /** + * Check related page permission and ensure type is drawio or gallery. + * @param Image $image + */ + protected function checkImagePermission(Image $image) + { + if ($image->type !== 'drawio' && $image->type !== 'gallery') { + $this->showPermissionError(); + } + + $relatedPage = $image->getPage(); + if ($relatedPage) { + $this->checkOwnablePermission('page-view', $relatedPage); + } + } +} diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 159e19a2b..650833c7f 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -1,6 +1,7 @@ imageRepo = $imageRepo; + parent::__construct(); + } + + /** * Display a listing of the settings. * @return Response @@ -35,6 +49,9 @@ class SettingController extends Controller { $this->preventAccessForDemoUsers(); $this->checkPermission('settings-manage'); + $this->validate($request, [ + 'app_logo' => $this->imageRepo->getImageValidationRules(), + ]); // Cycles through posted settings and update them foreach ($request->all() as $name => $value) { @@ -42,7 +59,21 @@ class SettingController extends Controller continue; } $key = str_replace('setting-', '', trim($name)); - Setting::put($key, $value); + setting()->put($key, $value); + } + + // Update logo image if set + if ($request->has('app_logo')) { + $logoFile = $request->file('app_logo'); + $this->imageRepo->destroyByType('system'); + $image = $this->imageRepo->saveNew($logoFile, 'system', 0, null, 86); + setting()->put('app-logo', $image->url); + } + + // Clear logo image if requested + if ($request->get('app_logo_reset', null)) { + $this->imageRepo->destroyByType('system'); + setting()->remove('app-logo'); } session()->flash('success', trans('settings.settings_save_success')); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 1bb5d46cd..1367f631b 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -4,6 +4,7 @@ use BookStack\Auth\Access\SocialAuthService; use BookStack\Auth\User; use BookStack\Auth\UserRepo; use BookStack\Exceptions\UserUpdateException; +use BookStack\Uploads\ImageRepo; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -12,16 +13,19 @@ class UserController extends Controller protected $user; protected $userRepo; + protected $imageRepo; /** * UserController constructor. - * @param User $user + * @param User $user * @param UserRepo $userRepo + * @param ImageRepo $imageRepo */ - public function __construct(User $user, UserRepo $userRepo) + public function __construct(User $user, UserRepo $userRepo, ImageRepo $imageRepo) { $this->user = $user; $this->userRepo = $userRepo; + $this->imageRepo = $imageRepo; parent::__construct(); } @@ -107,9 +111,7 @@ class UserController extends Controller */ public function edit($id, SocialAuthService $socialAuthService) { - $this->checkPermissionOr('users-manage', function () use ($id) { - return $this->currentUser->id == $id; - }); + $this->checkPermissionOrCurrentUser('users-manage', $id); $user = $this->user->findOrFail($id); @@ -123,24 +125,24 @@ class UserController extends Controller /** * Update the specified user in storage. - * @param Request $request - * @param int $id + * @param Request $request + * @param int $id * @return Response * @throws UserUpdateException + * @throws \BookStack\Exceptions\ImageUploadException */ public function update(Request $request, $id) { $this->preventAccessForDemoUsers(); - $this->checkPermissionOr('users-manage', function () use ($id) { - return $this->currentUser->id == $id; - }); + $this->checkPermissionOrCurrentUser('users-manage', $id); $this->validate($request, [ 'name' => 'min:2', 'email' => 'min:2|email|unique:users,email,' . $id, 'password' => 'min:5|required_with:password_confirm', 'password-confirm' => 'same:password|required_with:password', - 'setting' => 'array' + 'setting' => 'array', + 'profile_image' => $this->imageRepo->getImageValidationRules(), ]); $user = $this->userRepo->getById($id); @@ -170,10 +172,23 @@ class UserController extends Controller } } + // Save profile image if in request + if ($request->has('profile_image')) { + $imageUpload = $request->file('profile_image'); + $this->imageRepo->destroyImage($user->avatar); + $image = $this->imageRepo->saveNew($imageUpload, 'user', $user->id); + $user->image_id = $image->id; + } + + // Delete the profile image if set to + if ($request->has('profile_image_reset')) { + $this->imageRepo->destroyImage($user->avatar); + } + $user->save(); session()->flash('success', trans('settings.users_edit_success')); - $redirectUrl = userCan('users-manage') ? '/settings/users' : '/settings/users/' . $user->id; + $redirectUrl = userCan('users-manage') ? '/settings/users' : ('/settings/users/' . $user->id); return redirect($redirectUrl); } @@ -184,9 +199,7 @@ class UserController extends Controller */ public function delete($id) { - $this->checkPermissionOr('users-manage', function () use ($id) { - return $this->currentUser->id == $id; - }); + $this->checkPermissionOrCurrentUser('users-manage', $id); $user = $this->userRepo->getById($id); $this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name])); @@ -202,9 +215,7 @@ class UserController extends Controller public function destroy($id) { $this->preventAccessForDemoUsers(); - $this->checkPermissionOr('users-manage', function () use ($id) { - return $this->currentUser->id == $id; - }); + $this->checkPermissionOrCurrentUser('users-manage', $id); $user = $this->userRepo->getById($id); diff --git a/app/Uploads/Image.php b/app/Uploads/Image.php index df6d9fb0d..6fa5db2a5 100644 --- a/app/Uploads/Image.php +++ b/app/Uploads/Image.php @@ -1,5 +1,6 @@ belongsTo(Page::class, 'uploaded_to')->first(); + } } diff --git a/app/Uploads/ImageRepo.php b/app/Uploads/ImageRepo.php index 0ef8cad48..16d1bb3d1 100644 --- a/app/Uploads/ImageRepo.php +++ b/app/Uploads/ImageRepo.php @@ -2,6 +2,7 @@ use BookStack\Auth\Permissions\PermissionService; use BookStack\Entities\Page; +use Illuminate\Database\Eloquent\Builder; use Symfony\Component\HttpFoundation\File\UploadedFile; class ImageRepo @@ -19,7 +20,12 @@ class ImageRepo * @param \BookStack\Auth\Permissions\PermissionService $permissionService * @param \BookStack\Entities\Page $page */ - public function __construct(Image $image, ImageService $imageService, PermissionService $permissionService, Page $page) + public function __construct( + Image $image, + ImageService $imageService, + PermissionService $permissionService, + Page $page + ) { $this->image = $image; $this->imageService = $imageService; @@ -31,7 +37,7 @@ class ImageRepo /** * Get an image with the given id. * @param $id - * @return mixed + * @return Image */ public function getById($id) { @@ -44,95 +50,115 @@ class ImageRepo * @param $query * @param int $page * @param int $pageSize + * @param bool $filterOnPage * @return array */ - private function returnPaginated($query, $page = 0, $pageSize = 24) + private function returnPaginated($query, $page = 1, $pageSize = 24) { - $images = $this->restrictionService->filterRelatedPages($query, 'images', 'uploaded_to'); - $images = $images->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get(); + $images = $query->orderBy('created_at', 'desc')->skip($pageSize * ($page - 1))->take($pageSize + 1)->get(); $hasMore = count($images) > $pageSize; - $returnImages = $images->take(24); + $returnImages = $images->take($pageSize); $returnImages->each(function ($image) { $this->loadThumbs($image); }); return [ 'images' => $returnImages, - 'hasMore' => $hasMore + 'has_more' => $hasMore ]; } /** - * Gets a load images paginated, filtered by image type. + * Fetch a list of images in a paginated format, filtered by image type. + * Can be filtered by uploaded to and also by name. * @param string $type * @param int $page * @param int $pageSize - * @param bool|int $userFilter + * @param int $uploadedTo + * @param string|null $search + * @param callable|null $whereClause * @return array */ - public function getPaginatedByType($type, $page = 0, $pageSize = 24, $userFilter = false) + public function getPaginatedByType( + string $type, + int $page = 0, + int $pageSize = 24, + int $uploadedTo = null, + string $search = null, + callable $whereClause = null + ) { - $images = $this->image->where('type', '=', strtolower($type)); + $imageQuery = $this->image->newQuery()->where('type', '=', strtolower($type)); - if ($userFilter !== false) { - $images = $images->where('created_by', '=', $userFilter); + if ($uploadedTo !== null) { + $imageQuery = $imageQuery->where('uploaded_to', '=', $uploadedTo); } - return $this->returnPaginated($images, $page, $pageSize); + if ($search !== null) { + $imageQuery = $imageQuery->where('name', 'LIKE', '%' . $search . '%'); + } + + // Filter by page access + $imageQuery = $this->restrictionService->filterRelatedEntity('page', $imageQuery, 'images', 'uploaded_to'); + + if ($whereClause !== null) { + $imageQuery = $imageQuery->where($whereClause); + } + + return $this->returnPaginated($imageQuery, $page, $pageSize); } /** - * Search for images by query, of a particular type. + * Get paginated gallery images within a specific page or book. * @param string $type + * @param string $filterType * @param int $page * @param int $pageSize - * @param string $searchTerm + * @param int|null $uploadedTo + * @param string|null $search * @return array */ - public function searchPaginatedByType($type, $searchTerm, $page = 0, $pageSize = 24) + public function getEntityFiltered( + string $type, + string $filterType = null, + int $page = 0, + int $pageSize = 24, + int $uploadedTo = null, + string $search = null + ) { - $images = $this->image->where('type', '=', strtolower($type))->where('name', 'LIKE', '%' . $searchTerm . '%'); - return $this->returnPaginated($images, $page, $pageSize); - } + $contextPage = $this->page->findOrFail($uploadedTo); + $parentFilter = null; - /** - * Get gallery images with a particular filter criteria such as - * being within the current book or page. - * @param $filter - * @param $pageId - * @param int $pageNum - * @param int $pageSize - * @return array - */ - public function getGalleryFiltered($filter, $pageId, $pageNum = 0, $pageSize = 24) - { - $images = $this->image->where('type', '=', 'gallery'); - - $page = $this->page->findOrFail($pageId); - - if ($filter === 'page') { - $images = $images->where('uploaded_to', '=', $page->id); - } elseif ($filter === 'book') { - $validPageIds = $page->book->pages->pluck('id')->toArray(); - $images = $images->whereIn('uploaded_to', $validPageIds); + if ($filterType === 'book' || $filterType === 'page') { + $parentFilter = function(Builder $query) use ($filterType, $contextPage) { + if ($filterType === 'page') { + $query->where('uploaded_to', '=', $contextPage->id); + } elseif ($filterType === 'book') { + $validPageIds = $contextPage->book->pages()->get(['id'])->pluck('id')->toArray(); + $query->whereIn('uploaded_to', $validPageIds); + } + }; } - return $this->returnPaginated($images, $pageNum, $pageSize); + return $this->getPaginatedByType($type, $page, $pageSize, null, $search, $parentFilter); } /** * Save a new image into storage and return the new image. * @param UploadedFile $uploadFile - * @param string $type + * @param string $type * @param int $uploadedTo + * @param int|null $resizeWidth + * @param int|null $resizeHeight + * @param bool $keepRatio * @return Image * @throws \BookStack\Exceptions\ImageUploadException - * @throws \Exception */ - public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0) + public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0, int $resizeWidth = null, int $resizeHeight = null, bool $keepRatio = true) { - $image = $this->imageService->saveNewFromUpload($uploadFile, $type, $uploadedTo); + $image = $this->imageService->saveNewFromUpload($uploadFile, $type, $uploadedTo, $resizeWidth, $resizeHeight, $keepRatio); $this->loadThumbs($image); return $image; } @@ -175,12 +201,27 @@ class ImageRepo * @return bool * @throws \Exception */ - public function destroyImage(Image $image) + public function destroyImage(Image $image = null) { - $this->imageService->destroy($image); + if ($image) { + $this->imageService->destroy($image); + } return true; } + /** + * Destroy all images of a certain type. + * @param string $imageType + * @throws \Exception + */ + public function destroyByType(string $imageType) + { + $images = $this->image->where('type', '=', $imageType)->get(); + foreach ($images as $image) { + $this->destroyImage($image); + } + } + /** * Load thumbnails onto an image object. @@ -191,8 +232,8 @@ class ImageRepo protected function loadThumbs(Image $image) { $image->thumbs = [ - 'gallery' => $this->getThumbnail($image, 150, 150), - 'display' => $this->getThumbnail($image, 840, 0, true) + 'gallery' => $this->getThumbnail($image, 150, 150, false), + 'display' => $this->getThumbnail($image, 840, null, true) ]; } @@ -208,7 +249,7 @@ class ImageRepo * @throws \BookStack\Exceptions\ImageUploadException * @throws \Exception */ - public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false) + protected function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false) { try { return $this->imageService->getThumbnail($image, $width, $height, $keepRatio); @@ -232,13 +273,11 @@ class ImageRepo } /** - * Check if the provided image type is valid. - * @param $type - * @return bool + * Get the validation rules for image files. + * @return string */ - public function isValidType($type) + public function getImageValidationRules() { - $validTypes = ['gallery', 'cover', 'system', 'user']; - return in_array($type, $validTypes); + return 'image_extension|no_double_extension|mimes:jpeg,png,gif,bmp,webp,tiff'; } } diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index 1dd8b713d..eaf787c9c 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Filesystem\Factory as FileSystem; use Intervention\Image\Exception\NotSupportedException; use Intervention\Image\ImageManager; +use phpDocumentor\Reflection\Types\Integer; use Symfony\Component\HttpFoundation\File\UploadedFile; class ImageService extends UploadService @@ -57,15 +58,30 @@ class ImageService extends UploadService /** * Saves a new image from an upload. * @param UploadedFile $uploadedFile - * @param string $type + * @param string $type * @param int $uploadedTo + * @param int|null $resizeWidth + * @param int|null $resizeHeight + * @param bool $keepRatio * @return mixed * @throws ImageUploadException */ - public function saveNewFromUpload(UploadedFile $uploadedFile, $type, $uploadedTo = 0) + public function saveNewFromUpload( + UploadedFile $uploadedFile, + string $type, + int $uploadedTo = 0, + int $resizeWidth = null, + int $resizeHeight = null, + bool $keepRatio = true + ) { $imageName = $uploadedFile->getClientOriginalName(); $imageData = file_get_contents($uploadedFile->getRealPath()); + + if ($resizeWidth !== null || $resizeHeight !== null) { + $imageData = $this->resizeImage($imageData, $resizeWidth, $resizeHeight, $keepRatio); + } + return $this->saveNew($imageName, $imageData, $type, $uploadedTo); } @@ -122,7 +138,7 @@ class ImageService extends UploadService $secureUploads = setting('app-secure-images'); $imageName = str_replace(' ', '-', $imageName); - $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/'; + $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m') . '/'; while ($storage->exists($imagePath . $imageName)) { $imageName = str_random(3) . $imageName; @@ -201,8 +217,28 @@ class ImageService extends UploadService return $this->getPublicUrl($thumbFilePath); } + $thumbData = $this->resizeImage($storage->get($imagePath), $width, $height, $keepRatio); + + $storage->put($thumbFilePath, $thumbData); + $storage->setVisibility($thumbFilePath, 'public'); + $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72); + + return $this->getPublicUrl($thumbFilePath); + } + + /** + * Resize image data. + * @param string $imageData + * @param int $width + * @param int $height + * @param bool $keepRatio + * @return string + * @throws ImageUploadException + */ + protected function resizeImage(string $imageData, $width = 220, $height = null, bool $keepRatio = true) + { try { - $thumb = $this->imageTool->make($storage->get($imagePath)); + $thumb = $this->imageTool->make($imageData); } catch (Exception $e) { if ($e instanceof \ErrorException || $e instanceof NotSupportedException) { throw new ImageUploadException(trans('errors.cannot_create_thumbs')); @@ -211,20 +247,14 @@ class ImageService extends UploadService } if ($keepRatio) { - $thumb->resize($width, null, function ($constraint) { + $thumb->resize($width, $height, function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); }); } else { $thumb->fit($width, $height); } - - $thumbData = (string)$thumb->encode(); - $storage->put($thumbFilePath, $thumbData); - $storage->setVisibility($thumbFilePath, 'public'); - $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72); - - return $this->getPublicUrl($thumbFilePath); + return (string)$thumb->encode(); } /** @@ -306,6 +336,7 @@ class ImageService extends UploadService $image = $this->saveNewFromUrl($userAvatarUrl, 'user', $imageName); $image->created_by = $user->id; $image->updated_by = $user->id; + $image->uploaded_to = $user->id; $image->save(); return $image; diff --git a/resources/assets/js/components/image-picker.js b/resources/assets/js/components/image-picker.js index 7cbed4509..7455fa622 100644 --- a/resources/assets/js/components/image-picker.js +++ b/resources/assets/js/components/image-picker.js @@ -4,54 +4,50 @@ class ImagePicker { constructor(elem) { this.elem = elem; this.imageElem = elem.querySelector('img'); - this.input = elem.querySelector('input'); + this.imageInput = elem.querySelector('input[type=file]'); + this.resetInput = elem.querySelector('input[data-reset-input]'); + this.removeInput = elem.querySelector('input[data-remove-input]'); - this.isUsingIds = elem.getAttribute('data-current-id') !== ''; - this.isResizing = elem.getAttribute('data-resize-height') && elem.getAttribute('data-resize-width'); - this.isResizeCropping = elem.getAttribute('data-resize-crop') !== ''; + this.defaultImage = elem.getAttribute('data-default-image'); - let selectButton = elem.querySelector('button[data-action="show-image-manager"]'); - selectButton.addEventListener('click', this.selectImage.bind(this)); - - let resetButton = elem.querySelector('button[data-action="reset-image"]'); + const resetButton = elem.querySelector('button[data-action="reset-image"]'); resetButton.addEventListener('click', this.reset.bind(this)); - let removeButton = elem.querySelector('button[data-action="remove-image"]'); + const removeButton = elem.querySelector('button[data-action="remove-image"]'); if (removeButton) { removeButton.addEventListener('click', this.removeImage.bind(this)); } + + this.imageInput.addEventListener('change', this.fileInputChange.bind(this)); } - selectImage() { - window.ImageManager.show(image => { - if (!this.isResizing) { - this.setImage(image); - return; - } + fileInputChange() { + this.resetInput.setAttribute('disabled', 'disabled'); + if (this.removeInput) { + this.removeInput.setAttribute('disabled', 'disabled'); + } - let requestString = '/images/thumb/' + image.id + '/' + this.elem.getAttribute('data-resize-width') + '/' + this.elem.getAttribute('data-resize-height') + '/' + (this.isResizeCropping ? 'true' : 'false'); - - window.$http.get(window.baseUrl(requestString)).then(resp => { - image.url = resp.data.url; - this.setImage(image); - }); - }); + for (let file of this.imageInput.files) { + this.imageElem.src = window.URL.createObjectURL(file); + } + this.imageElem.classList.remove('none'); } reset() { - this.setImage({id: 0, url: this.elem.getAttribute('data-default-image')}); - } - - setImage(image) { - this.imageElem.src = image.url; - this.input.value = this.isUsingIds ? image.id : image.url; + this.imageInput.value = ''; + this.imageElem.src = this.defaultImage; + this.resetInput.removeAttribute('disabled'); + if (this.removeInput) { + this.removeInput.setAttribute('disabled', 'disabled'); + } this.imageElem.classList.remove('none'); } removeImage() { - this.imageElem.src = this.elem.getAttribute('data-default-image'); + this.imageInput.value = ''; this.imageElem.classList.add('none'); - this.input.value = 'none'; + this.removeInput.removeAttribute('disabled'); + this.resetInput.setAttribute('disabled', 'disabled'); } } diff --git a/resources/assets/js/components/markdown-editor.js b/resources/assets/js/components/markdown-editor.js index 55cf67813..b099a7ca9 100644 --- a/resources/assets/js/components/markdown-editor.js +++ b/resources/assets/js/components/markdown-editor.js @@ -394,9 +394,7 @@ class MarkdownEditor { const drawingId = imgContainer.getAttribute('drawio-diagram'); DrawIO.show(() => { - return window.$http.get(window.baseUrl(`/images/base64/${drawingId}`)).then(resp => { - return `data:image/png;base64,${resp.data.content}`; - }); + return DrawIO.load(drawingId); }, (pngData) => { let data = { diff --git a/resources/assets/js/components/wysiwyg-editor.js b/resources/assets/js/components/wysiwyg-editor.js index ce5cfbf4e..b894c3fa6 100644 --- a/resources/assets/js/components/wysiwyg-editor.js +++ b/resources/assets/js/components/wysiwyg-editor.js @@ -257,39 +257,38 @@ function drawIoPlugin() { DrawIO.show(drawingInit, updateContent); } - function updateContent(pngData) { - let id = "image-" + Math.random().toString(16).slice(2); - let loadingImage = window.baseUrl('/loading.gif'); - let data = { - image: pngData, - uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id')) - }; + async function updateContent(pngData) { + const id = "image-" + Math.random().toString(16).slice(2); + const loadingImage = window.baseUrl('/loading.gif'); + const pageId = Number(document.getElementById('page-editor').getAttribute('page-id')); // Handle updating an existing image if (currentNode) { DrawIO.close(); let imgElem = currentNode.querySelector('img'); - window.$http.post(window.baseUrl(`/images/drawing/upload`), data).then(resp => { - pageEditor.dom.setAttrib(imgElem, 'src', resp.data.url); - pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', resp.data.id); - }).catch(err => { + try { + const img = await DrawIO.upload(pngData, pageId); + pageEditor.dom.setAttrib(imgElem, 'src', img.url); + pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', img.id); + } catch (err) { window.$events.emit('error', trans('errors.image_upload_error')); console.log(err); - }); + } return; } - setTimeout(() => { + setTimeout(async () => { pageEditor.insertContent(`
{{ trans('common.cover_image_description') }}
@include('components.image-picker', [ - 'resizeHeight' => '512', - 'resizeWidth' => '512', - 'showRemove' => false, 'defaultImage' => baseUrl('/book_default_cover.png'), - 'currentImage' => isset($model) ? $model->getBookCover() : baseUrl('/book_default_cover.png') , - 'currentId' => isset($model) && $model->image_id ? $model->image_id : 0, - 'name' => 'image_id', + 'currentImage' => (isset($model) && $model->cover) ? $model->getBookCover() : baseUrl('/book_default_cover.png') , + 'name' => 'image', 'imageClass' => 'cover' ]) diff --git a/resources/views/components/image-manager.blade.php b/resources/views/components/image-manager.blade.php index df577b545..7c9084ad1 100644 --- a/resources/views/components/image-manager.blade.php +++ b/resources/views/components/image-manager.blade.php @@ -10,12 +10,12 @@{{ trans('common.cover_image_description') }}
@include('components.image-picker', [ - 'resizeHeight' => '512', - 'resizeWidth' => '512', - 'showRemove' => false, 'defaultImage' => baseUrl('/book_default_cover.png'), - 'currentImage' => isset($shelf) ? $shelf->getBookCover() : baseUrl('/book_default_cover.png') , - 'currentId' => isset($shelf) && $shelf->image_id ? $shelf->image_id : 0, - 'name' => 'image_id', + 'currentImage' => (isset($shelf) && $shelf->cover) ? $shelf->getBookCover() : baseUrl('/book_default_cover.png') , + 'name' => 'image', 'imageClass' => 'cover' ])