3 namespace BookStack\Http\Controllers;
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;
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;
17 class AttachmentController extends Controller
19 protected AttachmentService $attachmentService;
20 protected PageRepo $pageRepo;
23 * AttachmentController constructor.
25 public function __construct(AttachmentService $attachmentService, PageRepo $pageRepo)
27 $this->attachmentService = $attachmentService;
28 $this->pageRepo = $pageRepo;
32 * Endpoint at which attachments are uploaded to.
34 * @throws ValidationException
35 * @throws NotFoundException
37 public function upload(Request $request)
39 $this->validate($request, [
40 'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
41 'file' => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
44 $pageId = $request->get('uploaded_to');
45 $page = $this->pageRepo->getById($pageId);
47 $this->checkPermission('attachment-create-all');
48 $this->checkOwnablePermission('page-update', $page);
50 $uploadedFile = $request->file('file');
53 $attachment = $this->attachmentService->saveNewUpload($uploadedFile, $pageId);
54 } catch (FileUploadException $e) {
55 return response($e->getMessage(), 500);
58 return response()->json($attachment);
62 * Update an uploaded attachment.
64 * @throws ValidationException
66 public function uploadUpdate(Request $request, $attachmentId)
68 $this->validate($request, [
69 'file' => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
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);
78 $uploadedFile = $request->file('file');
81 $attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $attachment);
82 } catch (FileUploadException $e) {
83 return response($e->getMessage(), 500);
86 return response()->json($attachment);
90 * Get the update form for an attachment.
92 public function getUpdateForm(string $attachmentId)
94 /** @var Attachment $attachment */
95 $attachment = Attachment::query()->findOrFail($attachmentId);
97 $this->checkOwnablePermission('page-update', $attachment->page);
98 $this->checkOwnablePermission('attachment-create', $attachment);
100 return view('attachments.manager-edit-form', [
101 'attachment' => $attachment,
106 * Update the details of an existing file.
108 public function update(Request $request, string $attachmentId)
110 /** @var Attachment $attachment */
111 $attachment = Attachment::query()->findOrFail($attachmentId);
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'],
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()),
125 $this->checkOwnablePermission('page-view', $attachment->page);
126 $this->checkOwnablePermission('page-update', $attachment->page);
127 $this->checkOwnablePermission('attachment-update', $attachment);
129 $attachment = $this->attachmentService->updateFile($attachment, [
130 'name' => $request->get('attachment_edit_name'),
131 'link' => $request->get('attachment_edit_url'),
134 return view('attachments.manager-edit-form', [
135 'attachment' => $attachment,
140 * Attach a link to a page.
142 * @throws NotFoundException
144 public function attachLink(Request $request)
146 $pageId = $request->get('attachment_link_uploaded_to');
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'],
154 } catch (ValidationException $exception) {
155 return response()->view('attachments.manager-link-form', array_merge($request->only(['attachment_link_name', 'attachment_link_url']), [
157 'errors' => new MessageBag($exception->errors()),
161 $page = $this->pageRepo->getById($pageId);
163 $this->checkPermission('attachment-create-all');
164 $this->checkOwnablePermission('page-update', $page);
166 $attachmentName = $request->get('attachment_link_name');
167 $link = $request->get('attachment_link_url');
168 $this->attachmentService->saveNewFromLink($attachmentName, $link, intval($pageId));
170 return view('attachments.manager-link-form', [
176 * Get the attachments for a specific page.
178 * @throws NotFoundException
180 public function listForPage(int $pageId)
182 $page = $this->pageRepo->getById($pageId);
183 $this->checkOwnablePermission('page-view', $page);
185 return view('attachments.manager-list', [
186 'attachments' => $page->attachments->all(),
191 * Update the attachment sorting.
193 * @throws ValidationException
194 * @throws NotFoundException
196 public function sortForPage(Request $request, int $pageId)
198 $this->validate($request, [
199 'order' => ['required', 'array'],
201 $page = $this->pageRepo->getById($pageId);
202 $this->checkOwnablePermission('page-update', $page);
204 $attachmentOrder = $request->get('order');
205 $this->attachmentService->updateFileOrderWithinPage($attachmentOrder, $pageId);
207 return response()->json(['message' => trans('entities.attachments_order_updated')]);
211 * Get an attachment from storage.
213 * @throws FileNotFoundException
214 * @throws NotFoundException
216 public function get(Request $request, string $attachmentId)
218 /** @var Attachment $attachment */
219 $attachment = Attachment::query()->findOrFail($attachmentId);
222 $page = $this->pageRepo->getById($attachment->uploaded_to);
223 } catch (NotFoundException $exception) {
224 throw new NotFoundException(trans('errors.attachment_not_found'));
227 $this->checkOwnablePermission('page-view', $page);
229 if ($attachment->external) {
230 return redirect($attachment->path);
233 $fileName = $attachment->getFileName();
234 $attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment);
236 if ($request->get('open') === 'true') {
237 return $this->streamedInlineDownloadResponse($attachmentStream, $fileName);
240 return $this->streamedDownloadResponse($attachmentStream, $fileName);
244 * Delete a specific attachment in the system.
248 public function delete(string $attachmentId)
250 /** @var Attachment $attachment */
251 $attachment = Attachment::query()->findOrFail($attachmentId);
252 $this->checkOwnablePermission('attachment-delete', $attachment);
253 $this->attachmentService->deleteFile($attachment);
255 return response()->json(['message' => trans('entities.attachments_deleted')]);