]> BookStack Code Mirror - bookstack/blob - app/Uploads/AttachmentService.php
Tests: Updated comment test to account for new editor usage
[bookstack] / app / Uploads / AttachmentService.php
1 <?php
2
3 namespace BookStack\Uploads;
4
5 use BookStack\Exceptions\FileUploadException;
6 use Exception;
7 use Symfony\Component\HttpFoundation\File\UploadedFile;
8
9 class AttachmentService
10 {
11     public function __construct(
12         protected FileStorage $storage,
13     ) {
14     }
15
16     /**
17      * Stream an attachment from storage.
18      *
19      * @return resource|null
20      */
21     public function streamAttachmentFromStorage(Attachment $attachment)
22     {
23         return $this->storage->getReadStream($attachment->path);
24     }
25
26     /**
27      * Read the file size of an attachment from storage, in bytes.
28      */
29     public function getAttachmentFileSize(Attachment $attachment): int
30     {
31         return $this->storage->getSize($attachment->path);
32     }
33
34     /**
35      * Store a new attachment upon user upload.
36      *
37      * @throws FileUploadException
38      */
39     public function saveNewUpload(UploadedFile $uploadedFile, int $pageId): Attachment
40     {
41         $attachmentName = $uploadedFile->getClientOriginalName();
42         $attachmentPath = $this->putFileInStorage($uploadedFile);
43         $largestExistingOrder = Attachment::query()->where('uploaded_to', '=', $pageId)->max('order');
44
45         /** @var Attachment $attachment */
46         $attachment = Attachment::query()->forceCreate([
47             'name'        => $attachmentName,
48             'path'        => $attachmentPath,
49             'extension'   => $uploadedFile->getClientOriginalExtension(),
50             'uploaded_to' => $pageId,
51             'created_by'  => user()->id,
52             'updated_by'  => user()->id,
53             'order'       => $largestExistingOrder + 1,
54         ]);
55
56         return $attachment;
57     }
58
59     /**
60      * Store an upload, saving to a file and deleting any existing uploads
61      * attached to that file.
62      *
63      * @throws FileUploadException
64      */
65     public function saveUpdatedUpload(UploadedFile $uploadedFile, Attachment $attachment): Attachment
66     {
67         if (!$attachment->external) {
68             $this->deleteFileInStorage($attachment);
69         }
70
71         $attachmentName = $uploadedFile->getClientOriginalName();
72         $attachmentPath = $this->putFileInStorage($uploadedFile);
73
74         $attachment->name = $attachmentName;
75         $attachment->path = $attachmentPath;
76         $attachment->external = false;
77         $attachment->extension = $uploadedFile->getClientOriginalExtension();
78         $attachment->save();
79
80         return $attachment;
81     }
82
83     /**
84      * Save a new File attachment from a given link and name.
85      */
86     public function saveNewFromLink(string $name, string $link, int $page_id): Attachment
87     {
88         $largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
89
90         return Attachment::forceCreate([
91             'name'        => $name,
92             'path'        => $link,
93             'external'    => true,
94             'extension'   => '',
95             'uploaded_to' => $page_id,
96             'created_by'  => user()->id,
97             'updated_by'  => user()->id,
98             'order'       => $largestExistingOrder + 1,
99         ]);
100     }
101
102     /**
103      * Updates the ordering for a listing of attached files.
104      */
105     public function updateFileOrderWithinPage(array $attachmentOrder, string $pageId)
106     {
107         foreach ($attachmentOrder as $index => $attachmentId) {
108             Attachment::query()->where('uploaded_to', '=', $pageId)
109                 ->where('id', '=', $attachmentId)
110                 ->update(['order' => $index]);
111         }
112     }
113
114     /**
115      * Update the details of a file.
116      */
117     public function updateFile(Attachment $attachment, array $requestData): Attachment
118     {
119         if (isset($requestData['name'])) {
120             $attachment->name = $requestData['name'];
121         }
122
123         $link = trim($requestData['link'] ?? '');
124         if (!empty($link)) {
125             if (!$attachment->external) {
126                 $this->deleteFileInStorage($attachment);
127                 $attachment->external = true;
128                 $attachment->extension = '';
129             }
130             $attachment->path = $link;
131         }
132
133         $attachment->save();
134
135         return $attachment->refresh();
136     }
137
138     /**
139      * Delete a File from the database and storage.
140      *
141      * @throws Exception
142      */
143     public function deleteFile(Attachment $attachment)
144     {
145         if (!$attachment->external) {
146             $this->deleteFileInStorage($attachment);
147         }
148
149         $attachment->delete();
150     }
151
152     /**
153      * Delete a file from the filesystem it sits on.
154      * Cleans any empty leftover folders.
155      */
156     public function deleteFileInStorage(Attachment $attachment): void
157     {
158         $this->storage->delete($attachment->path);
159     }
160
161     /**
162      * Store a file in storage with the given filename.
163      *
164      * @throws FileUploadException
165      */
166     protected function putFileInStorage(UploadedFile $uploadedFile): string
167     {
168         $basePath = 'uploads/files/' . date('Y-m-M') . '/';
169
170         return $this->storage->uploadFile(
171             $uploadedFile,
172             $basePath,
173             $uploadedFile->getClientOriginalExtension(),
174             ''
175         );
176     }
177
178     /**
179      * Get the file validation rules for attachments.
180      */
181     public static function getFileValidationRules(): array
182     {
183         return ['file', 'max:' . (config('app.upload_limit') * 1000)];
184     }
185 }