]> BookStack Code Mirror - bookstack/blob - app/Uploads/ImageRepo.php
Added tests and translations for dark-mode components
[bookstack] / app / Uploads / ImageRepo.php
1 <?php namespace BookStack\Uploads;
2
3 use BookStack\Auth\Permissions\PermissionService;
4 use BookStack\Entities\Page;
5 use BookStack\Exceptions\ImageUploadException;
6 use Exception;
7 use Illuminate\Database\Eloquent\Builder;
8 use Symfony\Component\HttpFoundation\File\UploadedFile;
9
10 class ImageRepo
11 {
12
13     protected $image;
14     protected $imageService;
15     protected $restrictionService;
16     protected $page;
17
18     /**
19      * ImageRepo constructor.
20      */
21     public function __construct(
22         Image $image,
23         ImageService $imageService,
24         PermissionService $permissionService,
25         Page $page
26     ) {
27         $this->image = $image;
28         $this->imageService = $imageService;
29         $this->restrictionService = $permissionService;
30         $this->page = $page;
31     }
32
33
34     /**
35      * Get an image with the given id.
36      */
37     public function getById($id): Image
38     {
39         return $this->image->findOrFail($id);
40     }
41
42     /**
43      * Execute a paginated query, returning in a standard format.
44      * Also runs the query through the restriction system.
45      */
46     private function returnPaginated($query, $page = 1, $pageSize = 24): array
47     {
48         $images = $query->orderBy('created_at', 'desc')->skip($pageSize * ($page - 1))->take($pageSize + 1)->get();
49         $hasMore = count($images) > $pageSize;
50
51         $returnImages = $images->take($pageSize);
52         $returnImages->each(function ($image) {
53             $this->loadThumbs($image);
54         });
55
56         return [
57             'images'  => $returnImages,
58             'has_more' => $hasMore
59         ];
60     }
61
62     /**
63      * Fetch a list of images in a paginated format, filtered by image type.
64      * Can be filtered by uploaded to and also by name.
65      */
66     public function getPaginatedByType(
67         string $type,
68         int $page = 0,
69         int $pageSize = 24,
70         int $uploadedTo = null,
71         string $search = null,
72         callable $whereClause = null
73     ): array
74     {
75         $imageQuery = $this->image->newQuery()->where('type', '=', strtolower($type));
76
77         if ($uploadedTo !== null) {
78             $imageQuery = $imageQuery->where('uploaded_to', '=', $uploadedTo);
79         }
80
81         if ($search !== null) {
82             $imageQuery = $imageQuery->where('name', 'LIKE', '%' . $search . '%');
83         }
84
85         // Filter by page access
86         $imageQuery = $this->restrictionService->filterRelatedEntity('page', $imageQuery, 'images', 'uploaded_to');
87
88         if ($whereClause !== null) {
89             $imageQuery = $imageQuery->where($whereClause);
90         }
91
92         return $this->returnPaginated($imageQuery, $page, $pageSize);
93     }
94
95     /**
96      * Get paginated gallery images within a specific page or book.
97      */
98     public function getEntityFiltered(
99         string $type,
100         string $filterType = null,
101         int $page = 0,
102         int $pageSize = 24,
103         int $uploadedTo = null,
104         string $search = null
105     ): array
106     {
107         $contextPage = $this->page->findOrFail($uploadedTo);
108         $parentFilter = null;
109
110         if ($filterType === 'book' || $filterType === 'page') {
111             $parentFilter = function (Builder $query) use ($filterType, $contextPage) {
112                 if ($filterType === 'page') {
113                     $query->where('uploaded_to', '=', $contextPage->id);
114                 } elseif ($filterType === 'book') {
115                     $validPageIds = $contextPage->book->pages()->get(['id'])->pluck('id')->toArray();
116                     $query->whereIn('uploaded_to', $validPageIds);
117                 }
118             };
119         }
120
121         return $this->getPaginatedByType($type, $page, $pageSize, null, $search, $parentFilter);
122     }
123
124     /**
125      * Save a new image into storage and return the new image.
126      * @throws ImageUploadException
127      */
128     public function saveNew(UploadedFile $uploadFile, string $type, int $uploadedTo = 0, int $resizeWidth = null, int $resizeHeight = null, bool $keepRatio = true): Image
129     {
130         $image = $this->imageService->saveNewFromUpload($uploadFile, $type, $uploadedTo, $resizeWidth, $resizeHeight, $keepRatio);
131         $this->loadThumbs($image);
132         return $image;
133     }
134
135     /**
136      * Save a drawing the the database.
137      * @throws ImageUploadException
138      */
139     public function saveDrawing(string $base64Uri, int $uploadedTo): Image
140     {
141         $name = 'Drawing-' . strval(user()->id) . '-' . strval(time()) . '.png';
142         return $this->imageService->saveNewFromBase64Uri($base64Uri, $name, 'drawio', $uploadedTo);
143     }
144
145
146     /**
147      * Update the details of an image via an array of properties.
148      * @throws ImageUploadException
149      * @throws Exception
150      */
151     public function updateImageDetails(Image $image, $updateDetails): Image
152     {
153         $image->fill($updateDetails);
154         $image->save();
155         $this->loadThumbs($image);
156         return $image;
157     }
158
159     /**
160      * Destroys an Image object along with its revisions, files and thumbnails.
161      * @throws Exception
162      */
163     public function destroyImage(Image $image = null): bool
164     {
165         if ($image) {
166             $this->imageService->destroy($image);
167         }
168         return true;
169     }
170
171     /**
172      * Destroy all images of a certain type.
173      * @throws Exception
174      */
175     public function destroyByType(string $imageType)
176     {
177         $images = $this->image->where('type', '=', $imageType)->get();
178         foreach ($images as $image) {
179             $this->destroyImage($image);
180         }
181     }
182
183
184     /**
185      * Load thumbnails onto an image object.
186      * @throws Exception
187      */
188     protected function loadThumbs(Image $image)
189     {
190         $image->thumbs = [
191             'gallery' => $this->getThumbnail($image, 150, 150, false),
192             'display' => $this->getThumbnail($image, 1680, null, true)
193         ];
194     }
195
196     /**
197      * Get the thumbnail for an image.
198      * If $keepRatio is true only the width will be used.
199      * Checks the cache then storage to avoid creating / accessing the filesystem on every check.
200      * @throws Exception
201      */
202     protected function getThumbnail(Image $image, ?int $width = 220, ?int $height = 220, bool $keepRatio = false): ?string
203     {
204         try {
205             return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
206         } catch (Exception $exception) {
207             return null;
208         }
209     }
210
211     /**
212      * Get the raw image data from an Image.
213      */
214     public function getImageData(Image $image): ?string
215     {
216         try {
217             return $this->imageService->getImageData($image);
218         } catch (Exception $exception) {
219             return null;
220         }
221     }
222 }