1 <?php namespace BookStack\Http\Controllers;
3 use BookStack\Exceptions\FileUploadException;
4 use BookStack\Attachment;
5 use BookStack\Repos\PageRepo;
6 use BookStack\Services\AttachmentService;
7 use Illuminate\Http\Request;
9 class AttachmentController extends Controller
11 protected $attachmentService;
12 protected $attachment;
16 * AttachmentController constructor.
17 * @param AttachmentService $attachmentService
18 * @param Attachment $attachment
19 * @param PageRepo $pageRepo
21 public function __construct(AttachmentService $attachmentService, Attachment $attachment, PageRepo $pageRepo)
23 $this->attachmentService = $attachmentService;
24 $this->attachment = $attachment;
25 $this->pageRepo = $pageRepo;
26 parent::__construct();
31 * Endpoint at which attachments are uploaded to.
32 * @param Request $request
33 * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
35 public function upload(Request $request)
37 $this->validate($request, [
38 'uploaded_to' => 'required|integer|exists:pages,id',
39 'file' => 'required|file'
42 $pageId = $request->get('uploaded_to');
43 $page = $this->pageRepo->getById($pageId, true);
45 $this->checkPermission('attachment-create-all');
46 $this->checkOwnablePermission('page-update', $page);
48 $uploadedFile = $request->file('file');
51 $attachment = $this->attachmentService->saveNewUpload($uploadedFile, $pageId);
52 } catch (FileUploadException $e) {
53 return response($e->getMessage(), 500);
56 return response()->json($attachment);
60 * Update an uploaded attachment.
61 * @param int $attachmentId
62 * @param Request $request
65 public function uploadUpdate($attachmentId, Request $request)
67 $this->validate($request, [
68 'uploaded_to' => 'required|integer|exists:pages,id',
69 'file' => 'required|file'
72 $pageId = $request->get('uploaded_to');
73 $page = $this->pageRepo->getById($pageId, true);
74 $attachment = $this->attachment->findOrFail($attachmentId);
76 $this->checkOwnablePermission('page-update', $page);
77 $this->checkOwnablePermission('attachment-create', $attachment);
79 if (intval($pageId) !== intval($attachment->uploaded_to)) {
80 return $this->jsonError('Page mismatch during attached file update');
83 $uploadedFile = $request->file('file');
86 $attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $attachment);
87 } catch (FileUploadException $e) {
88 return response($e->getMessage(), 500);
91 return response()->json($attachment);
95 * Update the details of an existing file.
96 * @param $attachmentId
97 * @param Request $request
98 * @return Attachment|mixed
100 public function update($attachmentId, Request $request)
102 $this->validate($request, [
103 'uploaded_to' => 'required|integer|exists:pages,id',
104 'name' => 'required|string|min:1|max:255',
105 'link' => 'url|min:1|max:255'
108 $pageId = $request->get('uploaded_to');
109 $page = $this->pageRepo->getById($pageId, true);
110 $attachment = $this->attachment->findOrFail($attachmentId);
112 $this->checkOwnablePermission('page-update', $page);
113 $this->checkOwnablePermission('attachment-create', $attachment);
115 if (intval($pageId) !== intval($attachment->uploaded_to)) {
116 return $this->jsonError('Page mismatch during attachment update');
119 $attachment = $this->attachmentService->updateFile($attachment, $request->all());
124 * Attach a link to a page.
125 * @param Request $request
128 public function attachLink(Request $request)
130 $this->validate($request, [
131 'uploaded_to' => 'required|integer|exists:pages,id',
132 'name' => 'required|string|min:1|max:255',
133 'link' => 'required|url|min:1|max:255'
136 $pageId = $request->get('uploaded_to');
137 $page = $this->pageRepo->getById($pageId, true);
139 $this->checkPermission('attachment-create-all');
140 $this->checkOwnablePermission('page-update', $page);
142 $attachmentName = $request->get('name');
143 $link = $request->get('link');
144 $attachment = $this->attachmentService->saveNewFromLink($attachmentName, $link, $pageId);
146 return response()->json($attachment);
150 * Get the attachments for a specific page.
154 public function listForPage($pageId)
156 $page = $this->pageRepo->getById($pageId, true);
157 $this->checkOwnablePermission('page-view', $page);
158 return response()->json($page->attachments);
162 * Update the attachment sorting.
164 * @param Request $request
167 public function sortForPage($pageId, Request $request)
169 $this->validate($request, [
170 'files' => 'required|array',
171 'files.*.id' => 'required|integer',
173 $page = $this->pageRepo->getById($pageId);
174 $this->checkOwnablePermission('page-update', $page);
176 $attachments = $request->get('files');
177 $this->attachmentService->updateFileOrderWithinPage($attachments, $pageId);
178 return response()->json(['message' => 'Attachment order updated']);
182 * Get an attachment from storage.
183 * @param $attachmentId
184 * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response
186 public function get($attachmentId)
188 $attachment = $this->attachment->findOrFail($attachmentId);
189 $page = $this->pageRepo->getById($attachment->uploaded_to);
190 $this->checkOwnablePermission('page-view', $page);
192 if ($attachment->external) {
193 return redirect($attachment->path);
196 $attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
197 return response($attachmentContents, 200, [
198 'Content-Type' => 'application/octet-stream',
199 'Content-Disposition' => 'attachment; filename="'. $attachment->getFileName() .'"'
204 * Delete a specific attachment in the system.
205 * @param $attachmentId
208 public function delete($attachmentId)
210 $attachment = $this->attachment->findOrFail($attachmentId);
211 $this->checkOwnablePermission('attachment-delete', $attachment);
212 $this->attachmentService->deleteFile($attachment);
213 return response()->json(['message' => 'Attachment deleted']);