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