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