1 <?php namespace BookStack\Services;
3 use BookStack\Exceptions\FileUploadException;
4 use BookStack\Attachment;
6 use Symfony\Component\HttpFoundation\File\UploadedFile;
8 class AttachmentService extends UploadService
12 * Get an attachment from storage.
13 * @param Attachment $attachment
16 public function getAttachmentFromStorage(Attachment $attachment)
18 $attachmentPath = $this->getStorageBasePath() . $attachment->path;
19 return $this->getStorage()->get($attachmentPath);
23 * Store a new attachment upon user upload.
24 * @param UploadedFile $uploadedFile
27 * @throws FileUploadException
29 public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
31 $attachmentName = $uploadedFile->getClientOriginalName();
32 $attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
33 $largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
35 $attachment = Attachment::forceCreate([
36 'name' => $attachmentName,
37 'path' => $attachmentPath,
38 'extension' => $uploadedFile->getClientOriginalExtension(),
39 'uploaded_to' => $page_id,
40 'created_by' => user()->id,
41 'updated_by' => user()->id,
42 'order' => $largestExistingOrder + 1
49 * Store a upload, saving to a file and deleting any existing uploads
50 * attached to that file.
51 * @param UploadedFile $uploadedFile
52 * @param Attachment $attachment
54 * @throws FileUploadException
56 public function saveUpdatedUpload(UploadedFile $uploadedFile, Attachment $attachment)
58 if (!$attachment->external) {
59 $this->deleteFileInStorage($attachment);
62 $attachmentName = $uploadedFile->getClientOriginalName();
63 $attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
65 $attachment->name = $attachmentName;
66 $attachment->path = $attachmentPath;
67 $attachment->external = false;
68 $attachment->extension = $uploadedFile->getClientOriginalExtension();
74 * Save a new File attachment from a given link and name.
80 public function saveNewFromLink($name, $link, $page_id)
82 $largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
83 return Attachment::forceCreate([
88 'uploaded_to' => $page_id,
89 'created_by' => user()->id,
90 'updated_by' => user()->id,
91 'order' => $largestExistingOrder + 1
96 * Get the file storage base path, amended for storage type.
97 * This allows us to keep a generic path in the database.
100 private function getStorageBasePath()
102 return $this->isLocal() ? 'storage/' : '';
106 * Updates the file ordering for a listing of attached files.
107 * @param array $attachmentList
110 public function updateFileOrderWithinPage($attachmentList, $pageId)
112 foreach ($attachmentList as $index => $attachment) {
113 Attachment::where('uploaded_to', '=', $pageId)->where('id', '=', $attachment['id'])->update(['order' => $index]);
119 * Update the details of a file.
120 * @param Attachment $attachment
121 * @param $requestData
124 public function updateFile(Attachment $attachment, $requestData)
126 $attachment->name = $requestData['name'];
127 if (isset($requestData['link']) && trim($requestData['link']) !== '') {
128 $attachment->path = $requestData['link'];
129 if (!$attachment->external) {
130 $this->deleteFileInStorage($attachment);
131 $attachment->external = true;
139 * Delete a File from the database and storage.
140 * @param Attachment $attachment
142 public function deleteFile(Attachment $attachment)
144 if ($attachment->external) {
145 $attachment->delete();
149 $this->deleteFileInStorage($attachment);
150 $attachment->delete();
154 * Delete a file from the filesystem it sits on.
155 * Cleans any empty leftover folders.
156 * @param Attachment $attachment
158 protected function deleteFileInStorage(Attachment $attachment)
160 $storedFilePath = $this->getStorageBasePath() . $attachment->path;
161 $storage = $this->getStorage();
162 $dirPath = dirname($storedFilePath);
164 $storage->delete($storedFilePath);
165 if (count($storage->allFiles($dirPath)) === 0) {
166 $storage->deleteDirectory($dirPath);
171 * Store a file in storage with the given filename
172 * @param $attachmentName
173 * @param UploadedFile $uploadedFile
175 * @throws FileUploadException
177 protected function putFileInStorage($attachmentName, UploadedFile $uploadedFile)
179 $attachmentData = file_get_contents($uploadedFile->getRealPath());
181 $storage = $this->getStorage();
182 $attachmentBasePath = 'uploads/files/' . Date('Y-m-M') . '/';
183 $storageBasePath = $this->getStorageBasePath() . $attachmentBasePath;
185 $uploadFileName = $attachmentName;
186 while ($storage->exists($storageBasePath . $uploadFileName)) {
187 $uploadFileName = str_random(3) . $uploadFileName;
190 $attachmentPath = $attachmentBasePath . $uploadFileName;
191 $attachmentStoragePath = $this->getStorageBasePath() . $attachmentPath;
194 $storage->put($attachmentStoragePath, $attachmentData);
195 } catch (Exception $e) {
196 throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentStoragePath]));
198 return $attachmentPath;