]> BookStack Code Mirror - bookstack/blob - app/Uploads/AttachmentService.php
26e3411112a6184c9900315bd23742d3baf14d1b
[bookstack] / app / Uploads / AttachmentService.php
1 <?php namespace BookStack\Uploads;
2
3 use BookStack\Exceptions\FileUploadException;
4 use BookStack\Uploads\Attachment;
5 use BookStack\Uploads\UploadService;
6 use Exception;
7 use Symfony\Component\HttpFoundation\File\UploadedFile;
8
9 class AttachmentService extends UploadService
10 {
11
12     /**
13      * Get the storage that will be used for storing files.
14      * @return \Illuminate\Contracts\Filesystem\Filesystem
15      */
16     protected function getStorage()
17     {
18         $storageType = config('filesystems.default');
19
20         // Override default location if set to local public to ensure not visible.
21         if ($storageType === 'local') {
22             $storageType = 'local_secure';
23         }
24
25         return $this->fileSystem->disk($storageType);
26     }
27
28     /**
29      * Get an attachment from storage.
30      * @param Attachment $attachment
31      * @return string
32      * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
33      */
34     public function getAttachmentFromStorage(Attachment $attachment)
35     {
36         return $this->getStorage()->get($attachment->path);
37     }
38
39     /**
40      * Store a new attachment upon user upload.
41      * @param UploadedFile $uploadedFile
42      * @param int $page_id
43      * @return Attachment
44      * @throws FileUploadException
45      */
46     public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
47     {
48         $attachmentName = $uploadedFile->getClientOriginalName();
49         $attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
50         $largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
51
52         $attachment = Attachment::forceCreate([
53             'name' => $attachmentName,
54             'path' => $attachmentPath,
55             'extension' => $uploadedFile->getClientOriginalExtension(),
56             'uploaded_to' => $page_id,
57             'created_by' => user()->id,
58             'updated_by' => user()->id,
59             'order' => $largestExistingOrder + 1
60         ]);
61
62         return $attachment;
63     }
64
65     /**
66      * Store a upload, saving to a file and deleting any existing uploads
67      * attached to that file.
68      * @param UploadedFile $uploadedFile
69      * @param Attachment $attachment
70      * @return Attachment
71      * @throws FileUploadException
72      */
73     public function saveUpdatedUpload(UploadedFile $uploadedFile, Attachment $attachment)
74     {
75         if (!$attachment->external) {
76             $this->deleteFileInStorage($attachment);
77         }
78
79         $attachmentName = $uploadedFile->getClientOriginalName();
80         $attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
81
82         $attachment->name = $attachmentName;
83         $attachment->path = $attachmentPath;
84         $attachment->external = false;
85         $attachment->extension = $uploadedFile->getClientOriginalExtension();
86         $attachment->save();
87         return $attachment;
88     }
89
90     /**
91      * Save a new File attachment from a given link and name.
92      * @param string $name
93      * @param string $link
94      * @param int $page_id
95      * @return Attachment
96      */
97     public function saveNewFromLink($name, $link, $page_id)
98     {
99         $largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
100         return Attachment::forceCreate([
101             'name' => $name,
102             'path' => $link,
103             'external' => true,
104             'extension' => '',
105             'uploaded_to' => $page_id,
106             'created_by' => user()->id,
107             'updated_by' => user()->id,
108             'order' => $largestExistingOrder + 1
109         ]);
110     }
111
112     /**
113      * Updates the file ordering for a listing of attached files.
114      * @param array $attachmentList
115      * @param $pageId
116      */
117     public function updateFileOrderWithinPage($attachmentList, $pageId)
118     {
119         foreach ($attachmentList as $index => $attachment) {
120             Attachment::where('uploaded_to', '=', $pageId)->where('id', '=', $attachment['id'])->update(['order' => $index]);
121         }
122     }
123
124
125     /**
126      * Update the details of a file.
127      * @param Attachment $attachment
128      * @param $requestData
129      * @return Attachment
130      */
131     public function updateFile(Attachment $attachment, $requestData)
132     {
133         $attachment->name = $requestData['name'];
134         if (isset($requestData['link']) && trim($requestData['link']) !== '') {
135             $attachment->path = $requestData['link'];
136             if (!$attachment->external) {
137                 $this->deleteFileInStorage($attachment);
138                 $attachment->external = true;
139             }
140         }
141         $attachment->save();
142         return $attachment;
143     }
144
145     /**
146      * Delete a File from the database and storage.
147      * @param Attachment $attachment
148      * @throws Exception
149      */
150     public function deleteFile(Attachment $attachment)
151     {
152         if ($attachment->external) {
153             $attachment->delete();
154             return;
155         }
156         
157         $this->deleteFileInStorage($attachment);
158         $attachment->delete();
159     }
160
161     /**
162      * Delete a file from the filesystem it sits on.
163      * Cleans any empty leftover folders.
164      * @param Attachment $attachment
165      */
166     protected function deleteFileInStorage(Attachment $attachment)
167     {
168         $storage = $this->getStorage();
169         $dirPath = dirname($attachment->path);
170
171         $storage->delete($attachment->path);
172         if (count($storage->allFiles($dirPath)) === 0) {
173             $storage->deleteDirectory($dirPath);
174         }
175     }
176
177     /**
178      * Store a file in storage with the given filename
179      * @param $attachmentName
180      * @param UploadedFile $uploadedFile
181      * @return string
182      * @throws FileUploadException
183      */
184     protected function putFileInStorage($attachmentName, UploadedFile $uploadedFile)
185     {
186         $attachmentData = file_get_contents($uploadedFile->getRealPath());
187
188         $storage = $this->getStorage();
189         $basePath = 'uploads/files/' . Date('Y-m-M') . '/';
190
191         $uploadFileName = $attachmentName;
192         while ($storage->exists($basePath . $uploadFileName)) {
193             $uploadFileName = str_random(3) . $uploadFileName;
194         }
195
196         $attachmentPath = $basePath . $uploadFileName;
197         try {
198             $storage->put($attachmentPath, $attachmentData);
199         } catch (Exception $e) {
200             throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentPath]));
201         }
202
203         return $attachmentPath;
204     }
205 }