]> BookStack Code Mirror - bookstack/blob - app/Uploads/ImageRepo.php
Filtered scripts in custom HTML head for exports
[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 drawing the the database.
135      * @throws ImageUploadException
136      */
137     public function saveDrawing(string $base64Uri, int $uploadedTo): Image
138     {
139         $name = 'Drawing-' . strval(user()->id) . '-' . strval(time()) . '.png';
140         return $this->imageService->saveNewFromBase64Uri($base64Uri, $name, 'drawio', $uploadedTo);
141     }
142
143
144     /**
145      * Update the details of an image via an array of properties.
146      * @throws ImageUploadException
147      * @throws Exception
148      */
149     public function updateImageDetails(Image $image, $updateDetails): Image
150     {
151         $image->fill($updateDetails);
152         $image->save();
153         $this->loadThumbs($image);
154         return $image;
155     }
156
157     /**
158      * Destroys an Image object along with its revisions, files and thumbnails.
159      * @throws Exception
160      */
161     public function destroyImage(Image $image = null): bool
162     {
163         if ($image) {
164             $this->imageService->destroy($image);
165         }
166         return true;
167     }
168
169     /**
170      * Destroy all images of a certain type.
171      * @throws Exception
172      */
173     public function destroyByType(string $imageType)
174     {
175         $images = $this->image->where('type', '=', $imageType)->get();
176         foreach ($images as $image) {
177             $this->destroyImage($image);
178         }
179     }
180
181
182     /**
183      * Load thumbnails onto an image object.
184      * @throws Exception
185      */
186     public function loadThumbs(Image $image)
187     {
188         $image->thumbs = [
189             'gallery' => $this->getThumbnail($image, 150, 150, false),
190             'display' => $this->getThumbnail($image, 1680, null, true)
191         ];
192     }
193
194     /**
195      * Get the thumbnail for an image.
196      * If $keepRatio is true only the width will be used.
197      * Checks the cache then storage to avoid creating / accessing the filesystem on every check.
198      * @throws Exception
199      */
200     protected function getThumbnail(Image $image, ?int $width = 220, ?int $height = 220, bool $keepRatio = false): ?string
201     {
202         try {
203             return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
204         } catch (Exception $exception) {
205             return null;
206         }
207     }
208
209     /**
210      * Get the raw image data from an Image.
211      */
212     public function getImageData(Image $image): ?string
213     {
214         try {
215             return $this->imageService->getImageData($image);
216         } catch (Exception $exception) {
217             return null;
218         }
219     }
220
221     /**
222      * Get the user visible pages using the given image.
223      */
224     public function getPagesUsingImage(Image $image): array
225     {
226         $pages = Page::visible()
227             ->where('html', 'like', '%' . $image->url . '%')
228             ->get(['id', 'name', 'slug', 'book_id']);
229
230         foreach ($pages as $page) {
231             $page->url = $page->getUrl();
232         }
233
234         return $pages->all();
235     }
236 }