]> BookStack Code Mirror - bookstack/blob - app/Uploads/Controllers/AttachmentController.php
Tests: Updated comment test to account for new editor usage
[bookstack] / app / Uploads / Controllers / AttachmentController.php
1 <?php
2
3 namespace BookStack\Uploads\Controllers;
4
5 use BookStack\Entities\Queries\PageQueries;
6 use BookStack\Entities\Repos\PageRepo;
7 use BookStack\Exceptions\FileUploadException;
8 use BookStack\Exceptions\NotFoundException;
9 use BookStack\Http\Controller;
10 use BookStack\Uploads\Attachment;
11 use BookStack\Uploads\AttachmentService;
12 use Exception;
13 use Illuminate\Contracts\Filesystem\FileNotFoundException;
14 use Illuminate\Http\Request;
15 use Illuminate\Support\MessageBag;
16 use Illuminate\Validation\ValidationException;
17
18 class AttachmentController extends Controller
19 {
20     public function __construct(
21         protected AttachmentService $attachmentService,
22         protected PageQueries $pageQueries,
23         protected PageRepo $pageRepo
24     ) {
25     }
26
27     /**
28      * Endpoint at which attachments are uploaded to.
29      *
30      * @throws ValidationException
31      * @throws NotFoundException
32      */
33     public function upload(Request $request)
34     {
35         $this->validate($request, [
36             'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
37             'file'        => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
38         ]);
39
40         $pageId = $request->get('uploaded_to');
41         $page = $this->pageQueries->findVisibleByIdOrFail($pageId);
42
43         $this->checkPermission('attachment-create-all');
44         $this->checkOwnablePermission('page-update', $page);
45
46         $uploadedFile = $request->file('file');
47
48         try {
49             $attachment = $this->attachmentService->saveNewUpload($uploadedFile, $pageId);
50         } catch (FileUploadException $e) {
51             return response($e->getMessage(), 500);
52         }
53
54         return response()->json($attachment);
55     }
56
57     /**
58      * Update an uploaded attachment.
59      *
60      * @throws ValidationException
61      */
62     public function uploadUpdate(Request $request, $attachmentId)
63     {
64         $this->validate($request, [
65             'file' => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
66         ]);
67
68         /** @var Attachment $attachment */
69         $attachment = Attachment::query()->findOrFail($attachmentId);
70         $this->checkOwnablePermission('view', $attachment->page);
71         $this->checkOwnablePermission('page-update', $attachment->page);
72         $this->checkOwnablePermission('attachment-create', $attachment);
73
74         $uploadedFile = $request->file('file');
75
76         try {
77             $attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $attachment);
78         } catch (FileUploadException $e) {
79             return response($e->getMessage(), 500);
80         }
81
82         return response()->json($attachment);
83     }
84
85     /**
86      * Get the update form for an attachment.
87      */
88     public function getUpdateForm(string $attachmentId)
89     {
90         /** @var Attachment $attachment */
91         $attachment = Attachment::query()->findOrFail($attachmentId);
92
93         $this->checkOwnablePermission('page-update', $attachment->page);
94         $this->checkOwnablePermission('attachment-create', $attachment);
95
96         return view('attachments.manager-edit-form', [
97             'attachment' => $attachment,
98         ]);
99     }
100
101     /**
102      * Update the details of an existing file.
103      */
104     public function update(Request $request, string $attachmentId)
105     {
106         /** @var Attachment $attachment */
107         $attachment = Attachment::query()->findOrFail($attachmentId);
108
109         try {
110             $this->validate($request, [
111                 'attachment_edit_name' => ['required', 'string', 'min:1', 'max:255'],
112                 'attachment_edit_url'  => ['string', 'min:1', 'max:2000', 'safe_url'],
113             ]);
114         } catch (ValidationException $exception) {
115             return response()->view('attachments.manager-edit-form', array_merge($request->only(['attachment_edit_name', 'attachment_edit_url']), [
116                 'attachment' => $attachment,
117                 'errors'     => new MessageBag($exception->errors()),
118             ]), 422);
119         }
120
121         $this->checkOwnablePermission('page-view', $attachment->page);
122         $this->checkOwnablePermission('page-update', $attachment->page);
123         $this->checkOwnablePermission('attachment-update', $attachment);
124
125         $attachment = $this->attachmentService->updateFile($attachment, [
126             'name' => $request->get('attachment_edit_name'),
127             'link' => $request->get('attachment_edit_url'),
128         ]);
129
130         return view('attachments.manager-edit-form', [
131             'attachment' => $attachment,
132         ]);
133     }
134
135     /**
136      * Attach a link to a page.
137      *
138      * @throws NotFoundException
139      */
140     public function attachLink(Request $request)
141     {
142         $pageId = $request->get('attachment_link_uploaded_to');
143
144         try {
145             $this->validate($request, [
146                 'attachment_link_uploaded_to' => ['required', 'integer', 'exists:pages,id'],
147                 'attachment_link_name'        => ['required', 'string', 'min:1', 'max:255'],
148                 'attachment_link_url'         => ['required', 'string', 'min:1', 'max:2000', 'safe_url'],
149             ]);
150         } catch (ValidationException $exception) {
151             return response()->view('attachments.manager-link-form', array_merge($request->only(['attachment_link_name', 'attachment_link_url']), [
152                 'pageId' => $pageId,
153                 'errors' => new MessageBag($exception->errors()),
154             ]), 422);
155         }
156
157         $page = $this->pageQueries->findVisibleByIdOrFail($pageId);
158
159         $this->checkPermission('attachment-create-all');
160         $this->checkOwnablePermission('page-update', $page);
161
162         $attachmentName = $request->get('attachment_link_name');
163         $link = $request->get('attachment_link_url');
164         $this->attachmentService->saveNewFromLink($attachmentName, $link, intval($pageId));
165
166         return view('attachments.manager-link-form', [
167             'pageId' => $pageId,
168         ]);
169     }
170
171     /**
172      * Get the attachments for a specific page.
173      *
174      * @throws NotFoundException
175      */
176     public function listForPage(int $pageId)
177     {
178         $page = $this->pageQueries->findVisibleByIdOrFail($pageId);
179         $this->checkOwnablePermission('page-view', $page);
180
181         return view('attachments.manager-list', [
182             'attachments' => $page->attachments->all(),
183         ]);
184     }
185
186     /**
187      * Update the attachment sorting.
188      *
189      * @throws ValidationException
190      * @throws NotFoundException
191      */
192     public function sortForPage(Request $request, int $pageId)
193     {
194         $this->validate($request, [
195             'order' => ['required', 'array'],
196         ]);
197         $page = $this->pageQueries->findVisibleByIdOrFail($pageId);
198         $this->checkOwnablePermission('page-update', $page);
199
200         $attachmentOrder = $request->get('order');
201         $this->attachmentService->updateFileOrderWithinPage($attachmentOrder, $pageId);
202
203         return response()->json(['message' => trans('entities.attachments_order_updated')]);
204     }
205
206     /**
207      * Get an attachment from storage.
208      *
209      * @throws FileNotFoundException
210      * @throws NotFoundException
211      */
212     public function get(Request $request, string $attachmentId)
213     {
214         /** @var Attachment $attachment */
215         $attachment = Attachment::query()->findOrFail($attachmentId);
216
217         try {
218             $page = $this->pageQueries->findVisibleByIdOrFail($attachment->uploaded_to);
219         } catch (NotFoundException $exception) {
220             throw new NotFoundException(trans('errors.attachment_not_found'));
221         }
222
223         $this->checkOwnablePermission('page-view', $page);
224
225         if ($attachment->external) {
226             return redirect($attachment->path);
227         }
228
229         $fileName = $attachment->getFileName();
230         $attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment);
231         $attachmentSize = $this->attachmentService->getAttachmentFileSize($attachment);
232
233         if ($request->get('open') === 'true') {
234             return $this->download()->streamedInline($attachmentStream, $fileName, $attachmentSize);
235         }
236
237         return $this->download()->streamedDirectly($attachmentStream, $fileName, $attachmentSize);
238     }
239
240     /**
241      * Delete a specific attachment in the system.
242      *
243      * @throws Exception
244      */
245     public function delete(string $attachmentId)
246     {
247         /** @var Attachment $attachment */
248         $attachment = Attachment::query()->findOrFail($attachmentId);
249         $this->checkOwnablePermission('attachment-delete', $attachment);
250         $this->attachmentService->deleteFile($attachment);
251
252         return response()->json(['message' => trans('entities.attachments_deleted')]);
253     }
254 }