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