3 namespace BookStack\Uploads\Controllers;
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;
12 use Illuminate\Contracts\Filesystem\FileNotFoundException;
13 use Illuminate\Http\Request;
14 use Illuminate\Support\MessageBag;
15 use Illuminate\Validation\ValidationException;
17 class AttachmentController extends Controller
19 public function __construct(
20 protected AttachmentService $attachmentService,
21 protected PageRepo $pageRepo
26 * Endpoint at which attachments are uploaded to.
28 * @throws ValidationException
29 * @throws NotFoundException
31 public function upload(Request $request)
33 $this->validate($request, [
34 'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
35 'file' => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
38 $pageId = $request->get('uploaded_to');
39 $page = $this->pageRepo->getById($pageId);
41 $this->checkPermission('attachment-create-all');
42 $this->checkOwnablePermission('page-update', $page);
44 $uploadedFile = $request->file('file');
47 $attachment = $this->attachmentService->saveNewUpload($uploadedFile, $pageId);
48 } catch (FileUploadException $e) {
49 return response($e->getMessage(), 500);
52 return response()->json($attachment);
56 * Update an uploaded attachment.
58 * @throws ValidationException
60 public function uploadUpdate(Request $request, $attachmentId)
62 $this->validate($request, [
63 'file' => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
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);
72 $uploadedFile = $request->file('file');
75 $attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $attachment);
76 } catch (FileUploadException $e) {
77 return response($e->getMessage(), 500);
80 return response()->json($attachment);
84 * Get the update form for an attachment.
86 public function getUpdateForm(string $attachmentId)
88 /** @var Attachment $attachment */
89 $attachment = Attachment::query()->findOrFail($attachmentId);
91 $this->checkOwnablePermission('page-update', $attachment->page);
92 $this->checkOwnablePermission('attachment-create', $attachment);
94 return view('attachments.manager-edit-form', [
95 'attachment' => $attachment,
100 * Update the details of an existing file.
102 public function update(Request $request, string $attachmentId)
104 /** @var Attachment $attachment */
105 $attachment = Attachment::query()->findOrFail($attachmentId);
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'],
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()),
119 $this->checkOwnablePermission('page-view', $attachment->page);
120 $this->checkOwnablePermission('page-update', $attachment->page);
121 $this->checkOwnablePermission('attachment-update', $attachment);
123 $attachment = $this->attachmentService->updateFile($attachment, [
124 'name' => $request->get('attachment_edit_name'),
125 'link' => $request->get('attachment_edit_url'),
128 return view('attachments.manager-edit-form', [
129 'attachment' => $attachment,
134 * Attach a link to a page.
136 * @throws NotFoundException
138 public function attachLink(Request $request)
140 $pageId = $request->get('attachment_link_uploaded_to');
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'],
148 } catch (ValidationException $exception) {
149 return response()->view('attachments.manager-link-form', array_merge($request->only(['attachment_link_name', 'attachment_link_url']), [
151 'errors' => new MessageBag($exception->errors()),
155 $page = $this->pageRepo->getById($pageId);
157 $this->checkPermission('attachment-create-all');
158 $this->checkOwnablePermission('page-update', $page);
160 $attachmentName = $request->get('attachment_link_name');
161 $link = $request->get('attachment_link_url');
162 $this->attachmentService->saveNewFromLink($attachmentName, $link, intval($pageId));
164 return view('attachments.manager-link-form', [
170 * Get the attachments for a specific page.
172 * @throws NotFoundException
174 public function listForPage(int $pageId)
176 $page = $this->pageRepo->getById($pageId);
177 $this->checkOwnablePermission('page-view', $page);
179 return view('attachments.manager-list', [
180 'attachments' => $page->attachments->all(),
185 * Update the attachment sorting.
187 * @throws ValidationException
188 * @throws NotFoundException
190 public function sortForPage(Request $request, int $pageId)
192 $this->validate($request, [
193 'order' => ['required', 'array'],
195 $page = $this->pageRepo->getById($pageId);
196 $this->checkOwnablePermission('page-update', $page);
198 $attachmentOrder = $request->get('order');
199 $this->attachmentService->updateFileOrderWithinPage($attachmentOrder, $pageId);
201 return response()->json(['message' => trans('entities.attachments_order_updated')]);
205 * Get an attachment from storage.
207 * @throws FileNotFoundException
208 * @throws NotFoundException
210 public function get(Request $request, string $attachmentId)
212 /** @var Attachment $attachment */
213 $attachment = Attachment::query()->findOrFail($attachmentId);
216 $page = $this->pageRepo->getById($attachment->uploaded_to);
217 } catch (NotFoundException $exception) {
218 throw new NotFoundException(trans('errors.attachment_not_found'));
221 $this->checkOwnablePermission('page-view', $page);
223 if ($attachment->external) {
224 return redirect($attachment->path);
227 $fileName = $attachment->getFileName();
228 $attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment);
230 if ($request->get('open') === 'true') {
231 return $this->download()->streamedInline($attachmentStream, $fileName);
234 return $this->download()->streamedDirectly($attachmentStream, $fileName);
238 * Delete a specific attachment in the system.
242 public function delete(string $attachmentId)
244 /** @var Attachment $attachment */
245 $attachment = Attachment::query()->findOrFail($attachmentId);
246 $this->checkOwnablePermission('attachment-delete', $attachment);
247 $this->attachmentService->deleteFile($attachment);
249 return response()->json(['message' => trans('entities.attachments_deleted')]);