]> BookStack Code Mirror - bookstack/blob - app/Uploads/ImageRepo.php
Added front-end toggle and testing of inline attachments
[bookstack] / app / Uploads / ImageRepo.php
1 <?php namespace BookStack\Uploads;
2
3 use BookStack\Auth\Permissions\PermissionService;
4 use BookStack\Entities\Models\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         $imageQuery = $this->image->newQuery()->where('type', '=', strtolower($type));
75
76         if ($uploadedTo !== null) {
77             $imageQuery = $imageQuery->where('uploaded_to', '=', $uploadedTo);
78         }
79
80         if ($search !== null) {
81             $imageQuery = $imageQuery->where('name', 'LIKE', '%' . $search . '%');
82         }
83
84         // Filter by page access
85         $imageQuery = $this->restrictionService->filterRelatedEntity(Page::class, $imageQuery, 'images', 'uploaded_to');
86
87         if ($whereClause !== null) {
88             $imageQuery = $imageQuery->where($whereClause);
89         }
90
91         return $this->returnPaginated($imageQuery, $page, $pageSize);
92     }
93
94     /**
95      * Get paginated gallery images within a specific page or book.
96      */
97     public function getEntityFiltered(
98         string $type,
99         string $filterType = null,
100         int $page = 0,
101         int $pageSize = 24,
102         int $uploadedTo = null,
103         string $search = null
104     ): array {
105         $contextPage = $this->page->findOrFail($uploadedTo);
106         $parentFilter = null;
107
108         if ($filterType === 'book' || $filterType === 'page') {
109             $parentFilter = function (Builder $query) use ($filterType, $contextPage) {
110                 if ($filterType === 'page') {
111                     $query->where('uploaded_to', '=', $contextPage->id);
112                 } elseif ($filterType === 'book') {
113                     $validPageIds = $contextPage->book->pages()->visible()->get(['id'])->pluck('id')->toArray();
114                     $query->whereIn('uploaded_to', $validPageIds);
115                 }
116             };
117         }
118
119         return $this->getPaginatedByType($type, $page, $pageSize, null, $search, $parentFilter);
120     }
121
122     /**
123      * Save a new image into storage and return the new image.
124      * @throws ImageUploadException
125      */
126     public function saveNew(UploadedFile $uploadFile, string $type, int $uploadedTo = 0, int $resizeWidth = null, int $resizeHeight = null, bool $keepRatio = true): Image
127     {
128         $image = $this->imageService->saveNewFromUpload($uploadFile, $type, $uploadedTo, $resizeWidth, $resizeHeight, $keepRatio);
129         $this->loadThumbs($image);
130         return $image;
131     }
132
133     /**
134      * Save a new image from an existing image data string.
135      * @throws ImageUploadException
136      */
137     public function saveNewFromData(string $imageName, string $imageData, string $type, int $uploadedTo = 0)
138     {
139         $image = $this->imageService->saveNew($imageName, $imageData, $type, $uploadedTo);
140         $this->loadThumbs($image);
141         return $image;
142     }
143
144     /**
145      * Save a drawing the the database.
146      * @throws ImageUploadException
147      */
148     public function saveDrawing(string $base64Uri, int $uploadedTo): Image
149     {
150         $name = 'Drawing-' . strval(user()->id) . '-' . strval(time()) . '.png';
151         return $this->imageService->saveNewFromBase64Uri($base64Uri, $name, 'drawio', $uploadedTo);
152     }
153
154
155     /**
156      * Update the details of an image via an array of properties.
157      * @throws ImageUploadException
158      * @throws Exception
159      */
160     public function updateImageDetails(Image $image, $updateDetails): Image
161     {
162         $image->fill($updateDetails);
163         $image->save();
164         $this->loadThumbs($image);
165         return $image;
166     }
167
168     /**
169      * Destroys an Image object along with its revisions, files and thumbnails.
170      * @throws Exception
171      */
172     public function destroyImage(Image $image = null): bool
173     {
174         if ($image) {
175             $this->imageService->destroy($image);
176         }
177         return true;
178     }
179
180     /**
181      * Destroy all images of a certain type.
182      * @throws Exception
183      */
184     public function destroyByType(string $imageType)
185     {
186         $images = $this->image->where('type', '=', $imageType)->get();
187         foreach ($images as $image) {
188             $this->destroyImage($image);
189         }
190     }
191
192
193     /**
194      * Load thumbnails onto an image object.
195      * @throws Exception
196      */
197     public function loadThumbs(Image $image)
198     {
199         $image->thumbs = [
200             'gallery' => $this->getThumbnail($image, 150, 150, false),
201             'display' => $this->getThumbnail($image, 1680, null, true)
202         ];
203     }
204
205     /**
206      * Get the thumbnail for an image.
207      * If $keepRatio is true only the width will be used.
208      * Checks the cache then storage to avoid creating / accessing the filesystem on every check.
209      * @throws Exception
210      */
211     protected function getThumbnail(Image $image, ?int $width = 220, ?int $height = 220, bool $keepRatio = false): ?string
212     {
213         try {
214             return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
215         } catch (Exception $exception) {
216             return null;
217         }
218     }
219
220     /**
221      * Get the raw image data from an Image.
222      */
223     public function getImageData(Image $image): ?string
224     {
225         try {
226             return $this->imageService->getImageData($image);
227         } catch (Exception $exception) {
228             return null;
229         }
230     }
231
232     /**
233      * Get the user visible pages using the given image.
234      */
235     public function getPagesUsingImage(Image $image): array
236     {
237         $pages = Page::visible()
238             ->where('html', 'like', '%' . $image->url . '%')
239             ->get(['id', 'name', 'slug', 'book_id']);
240
241         foreach ($pages as $page) {
242             $page->url = $page->getUrl();
243         }
244
245         return $pages->all();
246     }
247 }