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