| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace BookStack\Uploads; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-25 23:58:03 +08:00
										 |  |  | use BookStack\Exceptions\ImageUploadException; | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  | use ErrorException; | 
					
						
							| 
									
										
										
										
											2016-02-02 15:34:48 +08:00
										 |  |  | use Exception; | 
					
						
							| 
									
										
										
										
											2022-03-26 20:32:08 +08:00
										 |  |  | use GuzzleHttp\Psr7\Utils; | 
					
						
							| 
									
										
										
										
											2018-09-25 23:58:03 +08:00
										 |  |  | use Illuminate\Contracts\Cache\Repository as Cache; | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  | use Illuminate\Contracts\Filesystem\FileNotFoundException; | 
					
						
							| 
									
										
										
										
											2021-05-16 00:25:51 +08:00
										 |  |  | use Illuminate\Contracts\Filesystem\Filesystem as Storage; | 
					
						
							| 
									
										
										
										
											2021-11-06 08:32:01 +08:00
										 |  |  | use Illuminate\Filesystem\FilesystemAdapter; | 
					
						
							| 
									
										
										
										
											2021-11-06 00:18:06 +08:00
										 |  |  | use Illuminate\Filesystem\FilesystemManager; | 
					
						
							| 
									
										
										
										
											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; | 
					
						
							| 
									
										
										
										
											2016-02-04 04:52:25 +08:00
										 |  |  | use Intervention\Image\Exception\NotSupportedException; | 
					
						
							| 
									
										
										
										
											2022-03-26 20:32:08 +08:00
										 |  |  | use Intervention\Image\Image as InterventionImage; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | use Intervention\Image\ImageManager; | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  | use League\Flysystem\Util; | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  | use Psr\SimpleCache\InvalidArgumentException; | 
					
						
							| 
									
										
										
										
											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
										 |  |  | { | 
					
						
							|  |  |  |     protected $imageTool; | 
					
						
							|  |  |  |     protected $cache; | 
					
						
							|  |  |  |     protected $storageUrl; | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |     protected $image; | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     protected $fileSystem; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 08:24:42 +08:00
										 |  |  |     protected static $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * ImageService constructor. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-11-06 00:18:06 +08:00
										 |  |  |     public function __construct(Image $image, ImageManager $imageTool, FilesystemManager $fileSystem, Cache $cache) | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |         $this->image = $image; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         $this->imageTool = $imageTool; | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |         $this->fileSystem = $fileSystem; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         $this->cache = $cache; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-25 19:41:52 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the storage that will be used for storing images. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-11-06 00:18:06 +08:00
										 |  |  |     protected function getStorageDisk(string $imageType = ''): Storage | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         return $this->fileSystem->disk($this->getStorageDiskName($imageType)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Check if local secure image storage (Fetched behind authentication) | 
					
						
							|  |  |  |      * is currently active in the instance. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function usingSecureImages(): bool | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->getStorageDiskName('gallery') === 'local_secure_images'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Change the originally provided path to fit any disk-specific requirements. | 
					
						
							|  |  |  |      * This also ensures the path is kept to the expected root folders. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function adjustPathForStorageDisk(string $path, string $imageType = ''): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $path = Util::normalizePath(str_replace('uploads/images/', '', $path)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($this->getStorageDiskName($imageType) === 'local_secure_images') { | 
					
						
							|  |  |  |             return $path; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return 'uploads/images/' . $path; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get the name of the storage disk to use. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getStorageDiskName(string $imageType): string | 
					
						
							| 
									
										
										
										
											2018-03-25 19:41:52 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2019-06-23 23:01:15 +08:00
										 |  |  |         $storageType = config('filesystems.images'); | 
					
						
							| 
									
										
										
										
											2018-03-25 19:41:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 23:01:15 +08:00
										 |  |  |         // Ensure system images (App logo) are uploaded to a public space
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         if ($imageType === 'system' && $storageType === 'local_secure') { | 
					
						
							| 
									
										
										
										
											2018-03-25 19:41:52 +08:00
										 |  |  |             $storageType = 'local'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         if ($storageType === 'local_secure') { | 
					
						
							|  |  |  |             $storageType = 'local_secure_images'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $storageType; | 
					
						
							| 
									
										
										
										
											2018-03-25 19:41:52 +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 | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							|  |  |  |      * @return mixed | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							|  |  |  |         int $resizeWidth = null, | 
					
						
							|  |  |  |         int $resizeHeight = null, | 
					
						
							|  |  |  |         bool $keepRatio = true | 
					
						
							| 
									
										
										
										
											2019-05-05 21:54:37 +08:00
										 |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2021-03-15 07:20:21 +08:00
										 |  |  |         $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) { | 
					
						
							|  |  |  |             $imageData = $this->resizeImage($imageData, $resizeWidth, $resizeHeight, $keepRatio); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         $storage = $this->getStorageDisk($type); | 
					
						
							| 
									
										
										
										
											2016-03-06 20:55:08 +08:00
										 |  |  |         $secureUploads = setting('app-secure-images'); | 
					
						
							| 
									
										
										
										
											2020-07-25 18:18:40 +08:00
										 |  |  |         $fileName = $this->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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         while ($storage->exists($this->adjustPathForStorageDisk($imagePath . $fileName, $type))) { | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |             $this->saveImageDataInPublicSpace($storage, $this->adjustPathForStorageDisk($fullPath, $type), $imageData); | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							|  |  |  |             'url'         => $this->getPublicUrl($fullPath), | 
					
						
							|  |  |  |             '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; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |         $image = $this->image->newInstance(); | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |         $image->forceFill($imageDetails)->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
										 |  |  |     /** | 
					
						
							|  |  |  |      * Save image data for the given path in the public space, if possible, | 
					
						
							|  |  |  |      * for the provided storage mechanism. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function saveImageDataInPublicSpace(Storage $storage, string $path, string $data) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $storage->put($path, $data); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-24 19:09:28 +08:00
										 |  |  |         // Set visibility when a non-AWS-s3, s3-like storage option is in use.
 | 
					
						
							|  |  |  |         // Done since this call can break s3-like services but desired for other image stores.
 | 
					
						
							|  |  |  |         // Attempting to set ACL during above put request requires different permissions
 | 
					
						
							|  |  |  |         // hence would technically be a breaking change for actual s3 usage.
 | 
					
						
							| 
									
										
										
										
											2021-05-16 00:25:51 +08:00
										 |  |  |         $usingS3 = strtolower(config('filesystems.images')) === 's3'; | 
					
						
							| 
									
										
										
										
											2021-05-24 19:09:28 +08:00
										 |  |  |         $usingS3Like = $usingS3 && !is_null(config('filesystems.disks.s3.endpoint')); | 
					
						
							|  |  |  |         if (!$usingS3Like) { | 
					
						
							| 
									
										
										
										
											2021-05-16 00:25:51 +08:00
										 |  |  |             $storage->setVisibility($path, 'public'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-25 18:18:40 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Clean up an image file name to be both URL and storage safe. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function cleanImageFileName(string $name): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $name = str_replace(' ', '-', $name); | 
					
						
							|  |  |  |         $nameParts = explode('.', $name); | 
					
						
							|  |  |  |         $extension = array_pop($nameParts); | 
					
						
							| 
									
										
										
										
											2021-03-15 07:20:21 +08:00
										 |  |  |         $name = implode('-', $nameParts); | 
					
						
							| 
									
										
										
										
											2020-07-25 18:18:40 +08:00
										 |  |  |         $name = Str::slug($name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (strlen($name) === 0) { | 
					
						
							|  |  |  |             $name = Str::random(10); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |         return $name . '.' . $extension; | 
					
						
							| 
									
										
										
										
											2020-07-25 18:18:40 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-10-10 01:58:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-19 04:14:33 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Checks if the image is a gif. Returns true if it is, else false. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     protected function isGif(Image $image): bool | 
					
						
							| 
									
										
										
										
											2018-03-28 03:37:01 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |         return strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'gif'; | 
					
						
							| 
									
										
										
										
											2018-03-19 04:14:33 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Check if the given image and image data is apng. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function isApngData(Image $image, string &$imageData): bool | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $isPng = strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'png'; | 
					
						
							|  |  |  |         if (!$isPng) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $initialHeader = substr($imageData, 0, strpos($imageData, 'IDAT')); | 
					
						
							| 
									
										
										
										
											2022-01-06 20:18:11 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |         return strpos($initialHeader, 'acTL') !== false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the thumbnail for an image. | 
					
						
							|  |  |  |      * If $keepRatio is true only the width will be used. | 
					
						
							|  |  |  |      * Checks the cache then storage to avoid creating / accessing the filesystem on every check. | 
					
						
							| 
									
										
										
										
											2021-11-01 21:26:02 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2016-02-04 04:52:25 +08:00
										 |  |  |      * @throws Exception | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  |      * @throws InvalidArgumentException | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  |     public function getThumbnail(Image $image, ?int $width, ?int $height, bool $keepRatio = false): string | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |         // Do not resize GIF images where we're not cropping
 | 
					
						
							| 
									
										
										
										
											2018-03-19 04:14:33 +08:00
										 |  |  |         if ($keepRatio && $this->isGif($image)) { | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |             return $this->getPublicUrl($image->path); | 
					
						
							| 
									
										
										
										
											2018-03-19 04:14:33 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/'; | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |         $imagePath = $image->path; | 
					
						
							| 
									
										
										
										
											2016-10-10 01:58:22 +08:00
										 |  |  |         $thumbFilePath = dirname($imagePath) . $thumbDirName . basename($imagePath); | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |         $thumbCacheKey = 'images::' . $image->id . '::' . $thumbFilePath; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Return path if in cache
 | 
					
						
							|  |  |  |         $cachedThumbPath = $this->cache->get($thumbCacheKey); | 
					
						
							|  |  |  |         if ($cachedThumbPath) { | 
					
						
							|  |  |  |             return $this->getPublicUrl($cachedThumbPath); | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |         // If thumbnail has already been generated, serve that and cache path
 | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         $storage = $this->getStorageDisk($image->type); | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         if ($storage->exists($this->adjustPathForStorageDisk($thumbFilePath, $image->type))) { | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |             $this->cache->put($thumbCacheKey, $thumbFilePath, 60 * 60 * 72); | 
					
						
							| 
									
										
										
										
											2022-01-06 20:18:11 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |             return $this->getPublicUrl($thumbFilePath); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |         $imageData = $storage->get($this->adjustPathForStorageDisk($imagePath, $image->type)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Do not resize apng images where we're not cropping
 | 
					
						
							|  |  |  |         if ($keepRatio && $this->isApngData($image, $imageData)) { | 
					
						
							|  |  |  |             $this->cache->put($thumbCacheKey, $image->path, 60 * 60 * 72); | 
					
						
							| 
									
										
										
										
											2022-01-06 20:18:11 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |             return $this->getPublicUrl($image->path); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |         // If not in cache and thumbnail does not exist, generate thumb and cache path
 | 
					
						
							|  |  |  |         $thumbData = $this->resizeImage($imageData, $width, $height, $keepRatio); | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         $this->saveImageDataInPublicSpace($storage, $this->adjustPathForStorageDisk($thumbFilePath, $image->type), $thumbData); | 
					
						
							| 
									
										
										
										
											2022-01-04 21:10:35 +08:00
										 |  |  |         $this->cache->put($thumbCacheKey, $thumbFilePath, 60 * 60 * 72); | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return $this->getPublicUrl($thumbFilePath); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  |      * Resize the image of given data to the specified size, and return the new image data. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  |      * @throws ImageUploadException | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  |     protected function resizeImage(string $imageData, ?int $width, ?int $height, bool $keepRatio): string | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2016-02-04 04:52:25 +08:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  |             $thumb = $this->imageTool->make($imageData); | 
					
						
							| 
									
										
										
										
											2021-11-01 21:26:02 +08:00
										 |  |  |         } catch (ErrorException|NotSupportedException $e) { | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  |             throw new ImageUploadException(trans('errors.cannot_create_thumbs')); | 
					
						
							| 
									
										
										
										
											2016-02-04 04:52:25 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-26 20:32:08 +08:00
										 |  |  |         $this->orientImageToOriginalExif($thumb, $imageData); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         if ($keepRatio) { | 
					
						
							| 
									
										
										
										
											2019-05-04 22:48:15 +08:00
										 |  |  |             $thumb->resize($width, $height, function ($constraint) { | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |                 $constraint->aspectRatio(); | 
					
						
							|  |  |  |                 $constraint->upsize(); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             $thumb->fit($width, $height); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-12-22 20:44:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |         $thumbData = (string) $thumb->encode(); | 
					
						
							| 
									
										
										
										
											2019-12-22 20:44:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Use original image data if we're keeping the ratio
 | 
					
						
							|  |  |  |         // and the resizing does not save any space.
 | 
					
						
							|  |  |  |         if ($keepRatio && strlen($thumbData) > strlen($imageData)) { | 
					
						
							|  |  |  |             return $imageData; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $thumbData; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-26 20:32:08 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Orientate the given intervention image based upon the given original image data. | 
					
						
							|  |  |  |      * Intervention does have an `orientate` method but the exif data it needs is lost before it | 
					
						
							|  |  |  |      * can be used (At least when created using binary string data) so we need to do some | 
					
						
							|  |  |  |      * implementation on our side to use the original image data. | 
					
						
							|  |  |  |      * Bulk of logic taken from: https://github.com/Intervention/image/blob/b734a4988b2148e7d10364b0609978a88d277536/src/Intervention/Image/Commands/OrientateCommand.php | 
					
						
							| 
									
										
										
										
											2022-03-27 04:38:03 +08:00
										 |  |  |      * Copyright (c) Oliver Vogel, MIT License. | 
					
						
							| 
									
										
										
										
											2022-03-26 20:32:08 +08:00
										 |  |  |      */ | 
					
						
							|  |  |  |     protected function orientImageToOriginalExif(InterventionImage $image, string $originalData): void | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (!extension_loaded('exif')) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $stream = Utils::streamFor($originalData)->detach(); | 
					
						
							| 
									
										
										
										
											2022-03-27 04:38:03 +08:00
										 |  |  |         $exif = @exif_read_data($stream); | 
					
						
							|  |  |  |         $orientation = $exif ? ($exif['Orientation'] ?? null) : null; | 
					
						
							| 
									
										
										
										
											2022-03-26 20:32:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         switch ($orientation) { | 
					
						
							|  |  |  |             case 2: | 
					
						
							|  |  |  |                 $image->flip(); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case 3: | 
					
						
							|  |  |  |                 $image->rotate(180); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case 4: | 
					
						
							|  |  |  |                 $image->rotate(180)->flip(); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case 5: | 
					
						
							|  |  |  |                 $image->rotate(270)->flip(); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case 6: | 
					
						
							|  |  |  |                 $image->rotate(270); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case 7: | 
					
						
							|  |  |  |                 $image->rotate(90)->flip(); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case 8: | 
					
						
							|  |  |  |                 $image->rotate(90); | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the raw data content from an image. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |      * @throws FileNotFoundException | 
					
						
							| 
									
										
										
										
											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
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         $storage = $this->getStorageDisk(); | 
					
						
							| 
									
										
										
										
											2021-10-09 05:23:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         return $storage->get($this->adjustPathForStorageDisk($image->path, $image->type)); | 
					
						
							| 
									
										
										
										
											2017-12-30 23:24:03 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |     public function destroy(Image $image) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         $this->destroyImagesFromPath($image->path, $image->type); | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |         $image->delete(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Destroys an image at the given path. | 
					
						
							| 
									
										
										
										
											2020-07-25 06:41:59 +08:00
										 |  |  |      * Searches for image thumbnails in addition to main provided path. | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |     protected function destroyImagesFromPath(string $path, string $imageType): bool | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         $path = $this->adjustPathForStorageDisk($path, $imageType); | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         $storage = $this->getStorageDisk($imageType); | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |         $imageFolder = dirname($path); | 
					
						
							|  |  |  |         $imageFileName = basename($path); | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         $allImages = collect($storage->allFiles($imageFolder)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |         // Delete image files
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) { | 
					
						
							| 
									
										
										
										
											2020-07-25 06:41:59 +08:00
										 |  |  |             return basename($imagePath) === $imageFileName; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |         }); | 
					
						
							|  |  |  |         $storage->delete($imagesToDelete->all()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Cleanup of empty folders
 | 
					
						
							| 
									
										
										
										
											2018-05-14 00:41:35 +08:00
										 |  |  |         $foldersInvolved = array_merge([$imageFolder], $storage->directories($imageFolder)); | 
					
						
							|  |  |  |         foreach ($foldersInvolved as $directory) { | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |             if ($this->isFolderEmpty($storage, $directory)) { | 
					
						
							| 
									
										
										
										
											2018-01-29 00:58:52 +08:00
										 |  |  |                 $storage->deleteDirectory($directory); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |      * Check whether a folder is empty. | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-11-06 00:18:06 +08:00
										 |  |  |     protected function isFolderEmpty(Storage $storage, string $path): bool | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $files = $storage->files($path); | 
					
						
							|  |  |  |         $folders = $storage->directories($path); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return count($files) === 0 && count($folders) === 0; | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-12-19 06:54:53 +08:00
										 |  |  |     public function deleteUnusedImages(bool $checkRevisions = true, bool $dryRun = true) | 
					
						
							| 
									
										
										
										
											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 = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-21 01:16:01 +08:00
										 |  |  |         $this->image->newQuery()->whereIn('type', $types) | 
					
						
							| 
									
										
										
										
											2020-12-19 06:54:53 +08:00
										 |  |  |             ->chunk(1000, function ($images) use ($checkRevisions, &$deletedPaths, $dryRun) { | 
					
						
							| 
									
										
										
										
											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. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |      * @throws FileNotFoundException | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     public function imageUriToBase64(string $uri): ?string | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |         $storagePath = $this->imageUrlToStoragePath($uri); | 
					
						
							|  |  |  |         if (empty($uri) || is_null($storagePath)) { | 
					
						
							|  |  |  |             return null; | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         $storagePath = $this->adjustPathForStorageDisk($storagePath); | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         $storage = $this->getStorageDisk(); | 
					
						
							| 
									
										
										
										
											2018-04-22 19:23:43 +08:00
										 |  |  |         $imageData = null; | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |         if ($storage->exists($storagePath)) { | 
					
						
							|  |  |  |             $imageData = $storage->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; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 05:36:49 +08:00
										 |  |  |         $extension = pathinfo($uri, PATHINFO_EXTENSION); | 
					
						
							|  |  |  |         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
										 |  |  |     /** | 
					
						
							|  |  |  |      * Check if the given path exists in the local secure image system. | 
					
						
							|  |  |  |      * Returns false if local_secure is not in use. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function pathExistsInLocalSecure(string $imagePath): bool | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-11-06 08:32:01 +08:00
										 |  |  |         /** @var FilesystemAdapter $disk */ | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         $disk = $this->getStorageDisk('gallery'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Check local_secure is active
 | 
					
						
							|  |  |  |         return $this->usingSecureImages() | 
					
						
							| 
									
										
										
										
											2021-11-06 08:32:01 +08:00
										 |  |  |             && $disk instanceof FilesystemAdapter | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |             // Check the image file exists
 | 
					
						
							|  |  |  |             && $disk->exists($imagePath) | 
					
						
							|  |  |  |             // Check the file is likely an image file
 | 
					
						
							|  |  |  |             && strpos($disk->getMimetype($imagePath), 'image/') === 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * For the given path, if existing, provide a response that will stream the image contents. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function streamImageFromStorageResponse(string $imageType, string $path): StreamedResponse | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $disk = $this->getStorageDisk($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); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get a storage path for the given image URL. | 
					
						
							| 
									
										
										
										
											2020-12-06 23:34:18 +08:00
										 |  |  |      * Ensures the path will start with "uploads/images". | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |      * Returns null if the url cannot be resolved to a local URL. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private function imageUrlToStoragePath(string $url): ?string | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2020-12-06 23:34:18 +08:00
										 |  |  |         $url = ltrim(trim($url), '/'); | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Handle potential relative paths
 | 
					
						
							|  |  |  |         $isRelative = strpos($url, 'http') !== 0; | 
					
						
							|  |  |  |         if ($isRelative) { | 
					
						
							| 
									
										
										
										
											2020-12-06 23:34:18 +08:00
										 |  |  |             if (strpos(strtolower($url), 'uploads/images') === 0) { | 
					
						
							|  |  |  |                 return trim($url, '/'); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-06 23:34:18 +08:00
										 |  |  |             return null; | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Handle local images based on paths on the same domain
 | 
					
						
							|  |  |  |         $potentialHostPaths = [ | 
					
						
							| 
									
										
										
										
											2020-12-06 23:34:18 +08:00
										 |  |  |             url('uploads/images/'), | 
					
						
							|  |  |  |             $this->getPublicUrl('/uploads/images/'), | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         foreach ($potentialHostPaths as $potentialBasePath) { | 
					
						
							|  |  |  |             $potentialBasePath = strtolower($potentialBasePath); | 
					
						
							|  |  |  |             if (strpos(strtolower($url), $potentialBasePath) === 0) { | 
					
						
							| 
									
										
										
										
											2020-12-06 23:34:18 +08:00
										 |  |  |                 return 'uploads/images/' . trim(substr($url, strlen($potentialBasePath)), '/'); | 
					
						
							| 
									
										
										
										
											2020-12-06 22:24:22 +08:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Gets a public facing url for an image by checking relevant environment variables. | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |      * If s3-style store is in use it will default to guessing a public bucket URL. | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     private function getPublicUrl(string $filePath): string | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  |         if (is_null($this->storageUrl)) { | 
					
						
							| 
									
										
										
										
											2016-01-10 03:23:35 +08:00
										 |  |  |             $storageUrl = config('filesystems.url'); | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             // Get the standard public s3 url if s3 is set as storage type
 | 
					
						
							| 
									
										
										
										
											2016-07-10 17:28:05 +08:00
										 |  |  |             // Uses the nice, short URL if bucket name has no periods in otherwise the longer
 | 
					
						
							|  |  |  |             // region-based url will be used to prevent http issues.
 | 
					
						
							| 
									
										
										
										
											2019-06-23 23:01:15 +08:00
										 |  |  |             if ($storageUrl == false && config('filesystems.images') === 's3') { | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |                 $storageDetails = config('filesystems.disks.s3'); | 
					
						
							| 
									
										
										
										
											2016-07-10 17:28:05 +08:00
										 |  |  |                 if (strpos($storageDetails['bucket'], '.') === false) { | 
					
						
							|  |  |  |                     $storageUrl = 'https://' . $storageDetails['bucket'] . '.s3.amazonaws.com'; | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket']; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-11-01 19:17:30 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |             $this->storageUrl = $storageUrl; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-04 21:26:39 +08:00
										 |  |  |         $basePath = ($this->storageUrl == false) ? url('/') : $this->storageUrl; | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |         return rtrim($basePath, '/') . $filePath; | 
					
						
							| 
									
										
										
										
											2015-12-10 03:50:17 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-07-09 21:26:53 +08:00
										 |  |  | } |