| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace BookStack\Uploads; | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | use BookStack\Exceptions\FileUploadException; | 
					
						
							|  |  |  | use Exception; | 
					
						
							| 
									
										
										
										
											2021-06-06 07:51:06 +08:00
										 |  |  | use Illuminate\Contracts\Filesystem\FileNotFoundException; | 
					
						
							| 
									
										
										
										
											2021-11-06 00:18:06 +08:00
										 |  |  | use Illuminate\Contracts\Filesystem\Filesystem as Storage; | 
					
						
							|  |  |  | use Illuminate\Filesystem\FilesystemManager; | 
					
						
							| 
									
										
										
										
											2021-09-26 22:37:55 +08:00
										 |  |  | use Illuminate\Support\Facades\Log; | 
					
						
							| 
									
										
										
										
											2021-09-26 22:48:22 +08:00
										 |  |  | use Illuminate\Support\Str; | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  | use League\Flysystem\Util; | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  | use Symfony\Component\HttpFoundation\File\UploadedFile; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  | class AttachmentService | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2022-04-03 01:07:43 +08:00
										 |  |  |     protected FilesystemManager $fileSystem; | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * AttachmentService constructor. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-11-06 00:18:06 +08:00
										 |  |  |     public function __construct(FilesystemManager $fileSystem) | 
					
						
							| 
									
										
										
										
											2020-12-06 20:58:40 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $this->fileSystem = $fileSystem; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get the storage that will be used for storing files. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-11-06 00:18:06 +08:00
										 |  |  |     protected function getStorageDisk(): Storage | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         return $this->fileSystem->disk($this->getStorageDiskName()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get the name of the storage disk to use. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getStorageDiskName(): string | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2019-06-23 23:01:15 +08:00
										 |  |  |         $storageType = config('filesystems.attachments'); | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         // Change to our secure-attachment disk if any of the local options
 | 
					
						
							|  |  |  |         // are used to prevent escaping that location.
 | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:17 +08:00
										 |  |  |         if ($storageType === 'local' || $storageType === 'local_secure' || $storageType === 'local_secure_restricted') { | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |             $storageType = 'local_secure_attachments'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $storageType; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * 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 | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $path = Util::normalizePath(str_replace('uploads/files/', '', $path)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($this->getStorageDiskName() === 'local_secure_attachments') { | 
					
						
							|  |  |  |             return $path; | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         return 'uploads/files/' . $path; | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-03 01:07:43 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Stream an attachment from storage. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @throws FileNotFoundException | 
					
						
							| 
									
										
										
										
											2022-04-25 01:22:40 +08:00
										 |  |  |      * | 
					
						
							|  |  |  |      * @return resource|null | 
					
						
							| 
									
										
										
										
											2022-04-03 01:07:43 +08:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public function streamAttachmentFromStorage(Attachment $attachment) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->getStorageDisk()->readStream($this->adjustPathForStorageDisk($attachment->path)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Store a new attachment upon user upload. | 
					
						
							| 
									
										
										
										
											2021-10-09 05:23:17 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |      * @throws FileUploadException | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-19 00:46:55 +08:00
										 |  |  |     public function saveNewUpload(UploadedFile $uploadedFile, int $pageId): Attachment | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $attachmentName = $uploadedFile->getClientOriginalName(); | 
					
						
							| 
									
										
										
										
											2019-03-25 03:07:18 +08:00
										 |  |  |         $attachmentPath = $this->putFileInStorage($uploadedFile); | 
					
						
							| 
									
										
										
										
											2021-10-19 00:46:55 +08:00
										 |  |  |         $largestExistingOrder = Attachment::query()->where('uploaded_to', '=', $pageId)->max('order'); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         /** @var Attachment $attachment */ | 
					
						
							|  |  |  |         $attachment = Attachment::query()->forceCreate([ | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |             'name'        => $attachmentName, | 
					
						
							|  |  |  |             'path'        => $attachmentPath, | 
					
						
							|  |  |  |             'extension'   => $uploadedFile->getClientOriginalExtension(), | 
					
						
							| 
									
										
										
										
											2021-10-19 00:46:55 +08:00
										 |  |  |             'uploaded_to' => $pageId, | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |             'created_by'  => user()->id, | 
					
						
							|  |  |  |             'updated_by'  => user()->id, | 
					
						
							|  |  |  |             'order'       => $largestExistingOrder + 1, | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $attachment; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |      * Store an upload, saving to a file and deleting any existing uploads | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |      * attached to that file. | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |      * @throws FileUploadException | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |     public function saveUpdatedUpload(UploadedFile $uploadedFile, Attachment $attachment): Attachment | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         if (!$attachment->external) { | 
					
						
							|  |  |  |             $this->deleteFileInStorage($attachment); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $attachmentName = $uploadedFile->getClientOriginalName(); | 
					
						
							| 
									
										
										
										
											2019-03-25 03:07:18 +08:00
										 |  |  |         $attachmentPath = $this->putFileInStorage($uploadedFile); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         $attachment->name = $attachmentName; | 
					
						
							|  |  |  |         $attachment->path = $attachmentPath; | 
					
						
							|  |  |  |         $attachment->external = false; | 
					
						
							|  |  |  |         $attachment->extension = $uploadedFile->getClientOriginalExtension(); | 
					
						
							|  |  |  |         $attachment->save(); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         return $attachment; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Save a new File attachment from a given link and name. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-10-31 23:01:52 +08:00
										 |  |  |     public function saveNewFromLink(string $name, string $link, int $page_id): Attachment | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order'); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         return Attachment::forceCreate([ | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |             'name'        => $name, | 
					
						
							|  |  |  |             'path'        => $link, | 
					
						
							|  |  |  |             'external'    => true, | 
					
						
							|  |  |  |             'extension'   => '', | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |             'uploaded_to' => $page_id, | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |             'created_by'  => user()->id, | 
					
						
							|  |  |  |             'updated_by'  => user()->id, | 
					
						
							|  |  |  |             'order'       => $largestExistingOrder + 1, | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         ]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2020-07-01 05:12:45 +08:00
										 |  |  |      * Updates the ordering for a listing of attached files. | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-07-01 05:12:45 +08:00
										 |  |  |     public function updateFileOrderWithinPage(array $attachmentOrder, string $pageId) | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2020-07-01 05:12:45 +08:00
										 |  |  |         foreach ($attachmentOrder as $index => $attachmentId) { | 
					
						
							|  |  |  |             Attachment::query()->where('uploaded_to', '=', $pageId) | 
					
						
							|  |  |  |                 ->where('id', '=', $attachmentId) | 
					
						
							|  |  |  |                 ->update(['order' => $index]); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Update the details of a file. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-10-31 23:01:52 +08:00
										 |  |  |     public function updateFile(Attachment $attachment, array $requestData): Attachment | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $attachment->name = $requestData['name']; | 
					
						
							| 
									
										
										
										
											2021-10-19 00:46:55 +08:00
										 |  |  |         $link = trim($requestData['link'] ?? ''); | 
					
						
							| 
									
										
										
										
											2020-10-31 23:01:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-19 00:46:55 +08:00
										 |  |  |         if (!empty($link)) { | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |             if (!$attachment->external) { | 
					
						
							|  |  |  |                 $this->deleteFileInStorage($attachment); | 
					
						
							|  |  |  |                 $attachment->external = true; | 
					
						
							| 
									
										
										
										
											2021-10-20 07:58:56 +08:00
										 |  |  |                 $attachment->extension = ''; | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-10-20 07:58:56 +08:00
										 |  |  |             $attachment->path = $requestData['link']; | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-10-31 23:01:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         $attachment->save(); | 
					
						
							| 
									
										
										
										
											2021-10-20 17:49:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-20 07:58:56 +08:00
										 |  |  |         return $attachment->refresh(); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Delete a File from the database and storage. | 
					
						
							| 
									
										
										
										
											2021-10-09 05:23:17 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |      * @throws Exception | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public function deleteFile(Attachment $attachment) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-10-19 00:46:55 +08:00
										 |  |  |         if (!$attachment->external) { | 
					
						
							|  |  |  |             $this->deleteFileInStorage($attachment); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         $attachment->delete(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Delete a file from the filesystem it sits on. | 
					
						
							|  |  |  |      * Cleans any empty leftover folders. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function deleteFileInStorage(Attachment $attachment) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         $storage = $this->getStorageDisk(); | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         $dirPath = $this->adjustPathForStorageDisk(dirname($attachment->path)); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         $storage->delete($this->adjustPathForStorageDisk($attachment->path)); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         if (count($storage->allFiles($dirPath)) === 0) { | 
					
						
							|  |  |  |             $storage->deleteDirectory($dirPath); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |      * Store a file in storage with the given filename. | 
					
						
							| 
									
										
										
										
											2021-10-09 05:23:17 +08:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |      * @throws FileUploadException | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |     protected function putFileInStorage(UploadedFile $uploadedFile): string | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-11-01 07:53:17 +08:00
										 |  |  |         $storage = $this->getStorageDisk(); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  |         $basePath = 'uploads/files/' . date('Y-m-M') . '/'; | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 19:32:00 +08:00
										 |  |  |         $uploadFileName = Str::random(16) . '-' . $uploadedFile->getClientOriginalExtension(); | 
					
						
							| 
									
										
										
										
											2021-10-09 00:47:14 +08:00
										 |  |  |         while ($storage->exists($this->adjustPathForStorageDisk($basePath . $uploadFileName))) { | 
					
						
							| 
									
										
										
										
											2019-09-14 06:58:40 +08:00
										 |  |  |             $uploadFileName = Str::random(3) . $uploadFileName; | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-03 01:46:48 +08:00
										 |  |  |         $attachmentStream = fopen($uploadedFile->getRealPath(), 'r'); | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |         $attachmentPath = $basePath . $uploadFileName; | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         try { | 
					
						
							| 
									
										
										
										
											2022-04-03 01:46:48 +08:00
										 |  |  |             $storage->writeStream($this->adjustPathForStorageDisk($attachmentPath), $attachmentStream); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         } catch (Exception $e) { | 
					
						
							| 
									
										
										
										
											2021-06-06 07:51:06 +08:00
										 |  |  |             Log::error('Error when attempting file upload:' . $e->getMessage()); | 
					
						
							| 
									
										
										
										
											2021-06-26 23:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  |             throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentPath])); | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-01-13 19:11:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 22:12:26 +08:00
										 |  |  |         return $attachmentPath; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-11-15 06:03:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get the file validation rules for attachments. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getFileValidationRules(): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return ['file', 'max:' . (config('app.upload_limit') * 1000)]; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-01-29 00:58:52 +08:00
										 |  |  | } |