]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/Api/AttachmentApiController.php
Added testing for our request method overrides
[bookstack] / app / Http / Controllers / Api / AttachmentApiController.php
1 <?php
2
3 namespace BookStack\Http\Controllers\Api;
4
5 use BookStack\Entities\Models\Page;
6 use BookStack\Exceptions\FileUploadException;
7 use BookStack\Uploads\Attachment;
8 use BookStack\Uploads\AttachmentService;
9 use Exception;
10 use Illuminate\Contracts\Filesystem\FileNotFoundException;
11 use Illuminate\Http\Request;
12 use Illuminate\Validation\ValidationException;
13
14 class AttachmentApiController extends ApiController
15 {
16     protected $attachmentService;
17
18     public function __construct(AttachmentService $attachmentService)
19     {
20         $this->attachmentService = $attachmentService;
21     }
22
23     /**
24      * Get a listing of attachments visible to the user.
25      * The external property indicates whether the attachment is simple a link.
26      * A false value for the external property would indicate a file upload.
27      */
28     public function list()
29     {
30         return $this->apiListingResponse(Attachment::visible(), [
31             'id', 'name', 'extension', 'uploaded_to', 'external', 'order', 'created_at', 'updated_at', 'created_by', 'updated_by',
32         ]);
33     }
34
35     /**
36      * Create a new attachment in the system.
37      * An uploaded_to value must be provided containing an ID of the page
38      * that this upload will be related to.
39      *
40      * If you're uploading a file the POST data should be provided via
41      * a multipart/form-data type request instead of JSON.
42      *
43      * @throws ValidationException
44      * @throws FileUploadException
45      */
46     public function create(Request $request)
47     {
48         $this->checkPermission('attachment-create-all');
49         $requestData = $this->validate($request, $this->rules()['create']);
50
51         $pageId = $request->get('uploaded_to');
52         $page = Page::visible()->findOrFail($pageId);
53         $this->checkOwnablePermission('page-update', $page);
54
55         if ($request->hasFile('file')) {
56             $uploadedFile = $request->file('file');
57             $attachment = $this->attachmentService->saveNewUpload($uploadedFile, $page->id);
58         } else {
59             $attachment = $this->attachmentService->saveNewFromLink(
60                 $requestData['name'],
61                 $requestData['link'],
62                 $page->id
63             );
64         }
65
66         $this->attachmentService->updateFile($attachment, $requestData);
67
68         return response()->json($attachment);
69     }
70
71     /**
72      * Get the details & content of a single attachment of the given ID.
73      * The attachment link or file content is provided via a 'content' property.
74      * For files the content will be base64 encoded.
75      *
76      * @throws FileNotFoundException
77      */
78     public function read(string $id)
79     {
80         /** @var Attachment $attachment */
81         $attachment = Attachment::visible()
82             ->with(['createdBy', 'updatedBy'])
83             ->findOrFail($id);
84
85         $attachment->setAttribute('links', [
86             'html'     => $attachment->htmlLink(),
87             'markdown' => $attachment->markdownLink(),
88         ]);
89
90         if (!$attachment->external) {
91             $attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
92             $attachment->setAttribute('content', base64_encode($attachmentContents));
93         } else {
94             $attachment->setAttribute('content', $attachment->path);
95         }
96
97         return response()->json($attachment);
98     }
99
100     /**
101      * Update the details of a single attachment.
102      * As per the create endpoint, if a file is being provided as the attachment content
103      * the request should be formatted as a multipart/form-data request instead of JSON.
104      *
105      * @throws ValidationException
106      * @throws FileUploadException
107      */
108     public function update(Request $request, string $id)
109     {
110         $requestData = $this->validate($request, $this->rules()['update']);
111         /** @var Attachment $attachment */
112         $attachment = Attachment::visible()->findOrFail($id);
113
114         $page = $attachment->page;
115         if ($requestData['uploaded_to'] ?? false) {
116             $pageId = $request->get('uploaded_to');
117             $page = Page::visible()->findOrFail($pageId);
118             $attachment->uploaded_to = $requestData['uploaded_to'];
119         }
120
121         $this->checkOwnablePermission('page-view', $page);
122         $this->checkOwnablePermission('page-update', $page);
123         $this->checkOwnablePermission('attachment-update', $attachment);
124
125         if ($request->hasFile('file')) {
126             $uploadedFile = $request->file('file');
127             $attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $attachment);
128         }
129
130         $this->attachmentService->updateFile($attachment, $requestData);
131
132         return response()->json($attachment);
133     }
134
135     /**
136      * Delete an attachment of the given ID.
137      *
138      * @throws Exception
139      */
140     public function delete(string $id)
141     {
142         /** @var Attachment $attachment */
143         $attachment = Attachment::visible()->findOrFail($id);
144         $this->checkOwnablePermission('attachment-delete', $attachment);
145
146         $this->attachmentService->deleteFile($attachment);
147
148         return response('', 204);
149     }
150
151     protected function rules(): array
152     {
153         return [
154             'create' => [
155                 'name'        => ['required', 'min:1', 'max:255', 'string'],
156                 'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
157                 'file'        => array_merge(['required_without:link'], $this->attachmentService->getFileValidationRules()),
158                 'link'        => ['required_without:file', 'min:1', 'max:255', 'safe_url'],
159             ],
160             'update' => [
161                 'name'        => ['min:1', 'max:255', 'string'],
162                 'uploaded_to' => ['integer', 'exists:pages,id'],
163                 'file'        => $this->attachmentService->getFileValidationRules(),
164                 'link'        => ['min:1', 'max:255', 'safe_url'],
165             ],
166         ];
167     }
168 }