| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace BookStack\Uploads; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-08 00:37:36 +08:00
										 |  |  | use BookStack\Entities\Queries\EntityQueries; | 
					
						
							| 
									
										
										
										
											2018-09-25 23:58:03 +08:00
										 |  |  | use BookStack\Exceptions\ImageUploadException; | 
					
						
							| 
									
										
										
										
											2016-02-02 15:34:48 +08:00
										 |  |  | use Exception; | 
					
						
							| 
									
										
										
										
											2021-09-26 22:48:22 +08:00
										 |  |  | use Illuminate\Support\Facades\DB; | 
					
						
							| 
									
										
										
										
											2021-11-01 19:32:00 +08:00
										 |  |  | use Illuminate\Support\Facades\Log; | 
					
						
							| 
									
										
										
										
											2019-09-14 06:58:40 +08:00
										 |  |  | use Illuminate\Support\Str; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | use Symfony\Component\HttpFoundation\File\UploadedFile; | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  | use Symfony\Component\HttpFoundation\StreamedResponse; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  | class ImageService | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-02-07 04:00:44 +08:00
										 |  |  |     protected static array $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; | 
					
						
							| 
									
										
										
										
											2021-11-01 08:24:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:09:29 +08:00
										 |  |  |     public function __construct( | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         protected ImageStorage $storage, | 
					
						
							| 
									
										
										
										
											2023-10-01 03:00:48 +08:00
										 |  |  |         protected ImageResizer $resizer, | 
					
						
							| 
									
										
										
										
											2024-02-08 00:37:36 +08:00
										 |  |  |         protected EntityQueries $queries, | 
					
						
							| 
									
										
										
										
											2023-09-30 19:09:29 +08:00
										 |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 06:30:55 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Saves a new image from an upload. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2016-03-13 21:30:47 +08:00
										 |  |  |      * @throws ImageUploadException | 
					
						
							| 
									
										
										
										
											2015-12-10 06:30:55 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  |     public function saveNewFromUpload( | 
					
						
							|  |  |  |         UploadedFile $uploadedFile, | 
					
						
							|  |  |  |         string $type, | 
					
						
							|  |  |  |         int $uploadedTo = 0, | 
					
						
							| 
									
										
										
										
											2025-02-17 20:45:37 +08:00
										 |  |  |         ?int $resizeWidth = null, | 
					
						
							|  |  |  |         ?int $resizeHeight = null, | 
					
						
							| 
									
										
										
										
											2024-11-17 00:12:45 +08:00
										 |  |  |         bool $keepRatio = true, | 
					
						
							|  |  |  |         string $imageName = '', | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |     ): Image { | 
					
						
							| 
									
										
										
										
											2024-11-17 00:12:45 +08:00
										 |  |  |         $imageName = $imageName ?: $uploadedFile->getClientOriginalName(); | 
					
						
							| 
									
										
										
										
											2015-12-10 06:30:55 +08:00
										 |  |  |         $imageData = file_get_contents($uploadedFile->getRealPath()); | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if ($resizeWidth !== null || $resizeHeight !== null) { | 
					
						
							| 
									
										
										
										
											2023-10-01 03:00:48 +08:00
										 |  |  |             $imageData = $this->resizer->resizeImageData($imageData, $resizeWidth, $resizeHeight, $keepRatio); | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-13 21:30:47 +08:00
										 |  |  |         return $this->saveNew($imageName, $imageData, $type, $uploadedTo); | 
					
						
							| 
									
										
										
										
											2015-12-10 06:30:55 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Save a new image from a uri-encoded base64 string of data. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |      * @throws ImageUploadException | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-12-09 07:46:38 +08:00
										 |  |  |     public function saveNewFromBase64Uri(string $base64Uri, string $name, string $type, int $uploadedTo = 0): Image | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $splitData = explode(';base64,', $base64Uri); | 
					
						
							|  |  |  |         if (count($splitData) < 2) { | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |             throw new ImageUploadException('Invalid base64 image data provided'); | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |         } | 
					
						
							|  |  |  |         $data = base64_decode($splitData[1]); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |         return $this->saveNew($name, $data, $type, $uploadedTo); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-10 06:30:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2020-07-25 18:18:40 +08:00
										 |  |  |      * Save a new image into storage. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2016-01-17 23:20:07 +08:00
										 |  |  |      * @throws ImageUploadException | 
					
						
							| 
									
										
										
										
											2015-12-10 06:30:55 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-12-09 07:46:38 +08:00
										 |  |  |     public function saveNew(string $imageName, string $imageData, string $type, int $uploadedTo = 0): Image | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $disk = $this->storage->getDisk($type); | 
					
						
							| 
									
										
										
										
											2016-03-06 20:55:08 +08:00
										 |  |  |         $secureUploads = setting('app-secure-images'); | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $fileName = $this->storage->cleanImageFileName($imageName); | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |         $imagePath = '/uploads/images/' . $type . '/' . date('Y-m') . '/'; | 
					
						
							| 
									
										
										
										
											2016-10-10 01:58:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-01 02:12:22 +08:00
										 |  |  |         while ($disk->exists($imagePath . $fileName)) { | 
					
						
							| 
									
										
										
										
											2020-07-25 18:18:40 +08:00
										 |  |  |             $fileName = Str::random(3) . $fileName; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-05-20 23:47:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-25 18:18:40 +08:00
										 |  |  |         $fullPath = $imagePath . $fileName; | 
					
						
							| 
									
										
										
										
											2018-05-20 23:47:53 +08:00
										 |  |  |         if ($secureUploads) { | 
					
						
							| 
									
										
										
										
											2020-07-25 18:18:40 +08:00
										 |  |  |             $fullPath = $imagePath . Str::random(16) . '-' . $fileName; | 
					
						
							| 
									
										
										
										
											2018-05-20 23:47:53 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-02 15:34:48 +08:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2023-10-01 02:12:22 +08:00
										 |  |  |             $disk->put($fullPath, $imageData, true); | 
					
						
							| 
									
										
										
										
											2016-02-02 15:34:48 +08:00
										 |  |  |         } catch (Exception $e) { | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  |             Log::error('Error when attempting image upload:' . $e->getMessage()); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-05 00:51:39 +08:00
										 |  |  |             throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $fullPath])); | 
					
						
							| 
									
										
										
										
											2016-02-02 15:34:48 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-17 23:20:07 +08:00
										 |  |  |         $imageDetails = [ | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |             'name'        => $imageName, | 
					
						
							|  |  |  |             'path'        => $fullPath, | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |             'url'         => $this->storage->getPublicUrl($fullPath), | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |             'type'        => $type, | 
					
						
							|  |  |  |             'uploaded_to' => $uploadedTo, | 
					
						
							| 
									
										
										
										
											2016-01-17 23:20:07 +08:00
										 |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-29 19:43:46 +08:00
										 |  |  |         if (user()->id !== 0) { | 
					
						
							|  |  |  |             $userId = user()->id; | 
					
						
							| 
									
										
										
										
											2016-01-17 23:20:07 +08:00
										 |  |  |             $imageDetails['created_by'] = $userId; | 
					
						
							|  |  |  |             $imageDetails['updated_by'] = $userId; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |         $image = (new Image())->forceFill($imageDetails); | 
					
						
							|  |  |  |         $image->save(); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         return $image; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-16 00:25:51 +08:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |      * Replace an existing image file in the system using the given file. | 
					
						
							| 
									
										
										
										
											2021-05-16 00:25:51 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |     public function replaceExistingFromUpload(string $path, string $type, UploadedFile $file): void | 
					
						
							| 
									
										
										
										
											2021-05-16 00:25:51 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $imageData = file_get_contents($file->getRealPath()); | 
					
						
							|  |  |  |         $disk = $this->storage->getDisk($type); | 
					
						
							| 
									
										
										
										
											2023-10-01 02:12:22 +08:00
										 |  |  |         $disk->put($path, $imageData); | 
					
						
							| 
									
										
										
										
											2021-05-16 00:25:51 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the raw data content from an image. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |      * @throws Exception | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     public function getImageData(Image $image): string | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $disk = $this->storage->getDisk(); | 
					
						
							| 
									
										
										
										
											2021-10-09 05:23:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-01 02:12:22 +08:00
										 |  |  |         return $disk->get($image->path); | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-21 20:59:15 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the raw data content from an image. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @throws Exception | 
					
						
							|  |  |  |      * @returns ?resource | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getImageStream(Image $image): mixed | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $disk = $this->storage->getDisk(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $disk->stream($image->path); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |      * Destroy an image along with its revisions, thumbnails and remaining folders. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2018-01-28 22:08:14 +08:00
										 |  |  |      * @throws Exception | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-10-01 02:12:22 +08:00
										 |  |  |     public function destroy(Image $image): void | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2024-11-14 23:59:15 +08:00
										 |  |  |         $this->destroyFileAtPath($image->type, $image->path); | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |         $image->delete(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2024-11-14 23:59:15 +08:00
										 |  |  |      * Destroy the underlying image file at the given path. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function destroyFileAtPath(string $type, string $path): void | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $disk = $this->storage->getDisk($type); | 
					
						
							|  |  |  |         $disk->destroyAllMatchingNameFromPath($path); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |      * Delete gallery and drawings that are not within HTML content of pages or page revisions. | 
					
						
							| 
									
										
										
										
											2018-05-27 21:33:50 +08:00
										 |  |  |      * Checks based off of only the image name. | 
					
						
							|  |  |  |      * Could be much improved to be more specific but kept it generic for now to be safe. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Returns the path of the images that would be/have been deleted. | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-10-01 02:12:22 +08:00
										 |  |  |     public function deleteUnusedImages(bool $checkRevisions = true, bool $dryRun = true): array | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2020-12-19 06:54:53 +08:00
										 |  |  |         $types = ['gallery', 'drawio']; | 
					
						
							| 
									
										
										
										
											2018-05-27 21:33:50 +08:00
										 |  |  |         $deletedPaths = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |         Image::query()->whereIn('type', $types) | 
					
						
							| 
									
										
										
										
											2020-12-19 06:54:53 +08:00
										 |  |  |             ->chunk(1000, function ($images) use ($checkRevisions, &$deletedPaths, $dryRun) { | 
					
						
							| 
									
										
										
										
											2023-05-24 17:34:43 +08:00
										 |  |  |                 /** @var Image $image */ | 
					
						
							| 
									
										
										
										
											2018-09-22 01:48:47 +08:00
										 |  |  |                 foreach ($images as $image) { | 
					
						
							|  |  |  |                     $searchQuery = '%' . basename($image->path) . '%'; | 
					
						
							|  |  |  |                     $inPage = DB::table('pages') | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |                             ->where('html', 'like', $searchQuery)->count() > 0; | 
					
						
							| 
									
										
										
										
											2020-12-19 06:54:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-22 01:48:47 +08:00
										 |  |  |                     $inRevision = false; | 
					
						
							|  |  |  |                     if ($checkRevisions) { | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |                         $inRevision = DB::table('page_revisions') | 
					
						
							|  |  |  |                                 ->where('html', 'like', $searchQuery)->count() > 0; | 
					
						
							| 
									
										
										
										
											2018-09-22 01:48:47 +08:00
										 |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (!$inPage && !$inRevision) { | 
					
						
							|  |  |  |                         $deletedPaths[] = $image->path; | 
					
						
							|  |  |  |                         if (!$dryRun) { | 
					
						
							|  |  |  |                             $this->destroy($image); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-27 21:33:50 +08:00
										 |  |  |         return $deletedPaths; | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |      * Convert an image URI to a Base64 encoded string. | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |      * Attempts to convert the URL to a system storage url then | 
					
						
							|  |  |  |      * fetch the data from the disk or storage location. | 
					
						
							|  |  |  |      * Returns null if the image data cannot be fetched from storage. | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |     public function imageUrlToBase64(string $url): ?string | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $storagePath = $this->storage->urlToPath($url); | 
					
						
							|  |  |  |         if (empty($url) || is_null($storagePath)) { | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |             return null; | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 21:21:43 +08:00
										 |  |  |         // Apply access control when local_secure_restricted images are active
 | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         if ($this->storage->usingSecureRestrictedImages()) { | 
					
						
							| 
									
										
										
										
											2022-09-02 21:21:43 +08:00
										 |  |  |             if (!$this->checkUserHasAccessToRelationOfImageAtPath($storagePath)) { | 
					
						
							|  |  |  |                 return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $disk = $this->storage->getDisk(); | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |         $imageData = null; | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         if ($disk->exists($storagePath)) { | 
					
						
							|  |  |  |             $imageData = $disk->get($storagePath); | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |         if (is_null($imageData)) { | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |             return null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $extension = pathinfo($url, PATHINFO_EXTENSION); | 
					
						
							| 
									
										
										
										
											2019-07-18 05:36:49 +08:00
										 |  |  |         if ($extension === 'svg') { | 
					
						
							|  |  |  |             $extension = 'svg+xml'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return 'data:image/' . $extension . ';base64,' . base64_encode($imageData); | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |      * Check if the given path exists and is accessible in the local secure image system. | 
					
						
							|  |  |  |      * Returns false if local_secure is not in use, if the file does not exist, if the | 
					
						
							|  |  |  |      * file is likely not a valid image, or if permission does not allow access. | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |     public function pathAccessibleInLocalSecure(string $imagePath): bool | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $disk = $this->storage->getDisk('gallery'); | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         if ($this->storage->usingSecureRestrictedImages() && !$this->checkUserHasAccessToRelationOfImageAtPath($imagePath)) { | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         // Check local_secure is active
 | 
					
						
							| 
									
										
										
										
											2023-10-01 02:12:22 +08:00
										 |  |  |         return $disk->usingSecureImages() | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |             // Check the image file exists
 | 
					
						
							|  |  |  |             && $disk->exists($imagePath) | 
					
						
							|  |  |  |             // Check the file is likely an image file
 | 
					
						
							| 
									
										
										
										
											2023-09-30 19:09:29 +08:00
										 |  |  |             && str_starts_with($disk->mimeType($imagePath), 'image/'); | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Check that the current user has access to the relation | 
					
						
							|  |  |  |      * of the image at the given path. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function checkUserHasAccessToRelationOfImageAtPath(string $path): bool | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-10-01 03:00:48 +08:00
										 |  |  |         if (str_starts_with($path, 'uploads/images/')) { | 
					
						
							| 
									
										
										
										
											2022-09-02 21:21:43 +08:00
										 |  |  |             $path = substr($path, 15); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |         // Strip thumbnail element from path if existing
 | 
					
						
							| 
									
										
										
										
											2022-09-02 21:47:44 +08:00
										 |  |  |         $originalPathSplit = array_filter(explode('/', $path), function (string $part) { | 
					
						
							| 
									
										
										
										
											2023-09-30 19:09:29 +08:00
										 |  |  |             $resizedDir = (str_starts_with($part, 'thumbs-') || str_starts_with($part, 'scaled-')); | 
					
						
							|  |  |  |             $missingExtension = !str_contains($part, '.'); | 
					
						
							| 
									
										
										
										
											2022-09-02 21:47:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |             return !($resizedDir && $missingExtension); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Build a database-format image path and search for the image entry
 | 
					
						
							|  |  |  |         $fullPath = '/uploads/images/' . ltrim(implode('/', $originalPathSplit), '/'); | 
					
						
							|  |  |  |         $image = Image::query()->where('path', '=', $fullPath)->first(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (is_null($image)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $imageType = $image->type; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Allow user or system (logo) images
 | 
					
						
							|  |  |  |         // (No specific relation control but may still have access controlled by auth)
 | 
					
						
							|  |  |  |         if ($imageType === 'user' || $imageType === 'system') { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($imageType === 'gallery' || $imageType === 'drawio') { | 
					
						
							| 
									
										
										
										
											2024-02-08 00:37:36 +08:00
										 |  |  |             return $this->queries->pages->visibleForList()->where('id', '=', $image->uploaded_to)->exists(); | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($imageType === 'cover_book') { | 
					
						
							| 
									
										
										
										
											2024-02-08 00:37:36 +08:00
										 |  |  |             return $this->queries->books->visibleForList()->where('id', '=', $image->uploaded_to)->exists(); | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($imageType === 'cover_bookshelf') { | 
					
						
							| 
									
										
										
										
											2024-02-08 00:37:36 +08:00
										 |  |  |             return $this->queries->shelves->visibleForList()->where('id', '=', $image->uploaded_to)->exists(); | 
					
						
							| 
									
										
										
										
											2022-09-01 23:17:14 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * For the given path, if existing, provide a response that will stream the image contents. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function streamImageFromStorageResponse(string $imageType, string $path): StreamedResponse | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-10-01 01:28:42 +08:00
										 |  |  |         $disk = $this->storage->getDisk($imageType); | 
					
						
							| 
									
										
										
										
											2021-11-01 21:26:02 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         return $disk->response($path); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 08:24:42 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Check if the given image extension is supported by BookStack. | 
					
						
							|  |  |  |      * The extension must not be altered in this function. This check should provide a guarantee | 
					
						
							|  |  |  |      * that the provided extension is safe to use for the image to be saved. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static function isExtensionSupported(string $extension): bool | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return in_array($extension, static::$supportedExtensions); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-07-09 21:26:53 +08:00
										 |  |  | } |