X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/32f6ea946f00d25b3e70166d4e1bd3ef27d64a33..refs/pull/3879/head:/app/Http/Controllers/Api/AttachmentApiController.php diff --git a/app/Http/Controllers/Api/AttachmentApiController.php b/app/Http/Controllers/Api/AttachmentApiController.php index 2ee1c98a6..7059ca282 100644 --- a/app/Http/Controllers/Api/AttachmentApiController.php +++ b/app/Http/Controllers/Api/AttachmentApiController.php @@ -15,21 +15,6 @@ class AttachmentApiController extends ApiController { protected $attachmentService; - protected $rules = [ - 'create' => [ - 'name' => 'required|min:1|max:255|string', - 'uploaded_to' => 'required|integer|exists:pages,id', - 'file' => 'required_without:link|file', - 'link' => 'required_without:file|min:1|max:255|safe_url' - ], - 'update' => [ - 'name' => 'min:1|max:255|string', - 'uploaded_to' => 'integer|exists:pages,id', - 'file' => 'link|file', - 'link' => 'file|min:1|max:255|safe_url' - ], - ]; - public function __construct(AttachmentService $attachmentService) { $this->attachmentService = $attachmentService; @@ -52,13 +37,16 @@ class AttachmentApiController extends ApiController * An uploaded_to value must be provided containing an ID of the page * that this upload will be related to. * + * If you're uploading a file the POST data should be provided via + * a multipart/form-data type request instead of JSON. + * * @throws ValidationException * @throws FileUploadException */ public function create(Request $request) { $this->checkPermission('attachment-create-all'); - $requestData = $this->validate($request, $this->rules['create']); + $requestData = $this->validate($request, $this->rules()['create']); $pageId = $request->get('uploaded_to'); $page = Page::visible()->findOrFail($pageId); @@ -69,11 +57,14 @@ class AttachmentApiController extends ApiController $attachment = $this->attachmentService->saveNewUpload($uploadedFile, $page->id); } else { $attachment = $this->attachmentService->saveNewFromLink( - $requestData['name'], $requestData['link'], $page->id + $requestData['name'], + $requestData['link'], + $page->id ); } $this->attachmentService->updateFile($attachment, $requestData); + return response()->json($attachment); } @@ -87,32 +78,55 @@ class AttachmentApiController extends ApiController public function read(string $id) { /** @var Attachment $attachment */ - $attachment = Attachment::visible()->findOrFail($id); + $attachment = Attachment::visible() + ->with(['createdBy', 'updatedBy']) + ->findOrFail($id); $attachment->setAttribute('links', [ 'html' => $attachment->htmlLink(), 'markdown' => $attachment->markdownLink(), ]); - if (!$attachment->external) { - $attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment); - $attachment->setAttribute('content', base64_encode($attachmentContents)); - } else { + // Simply return a JSON response of the attachment for link-based attachments + if ($attachment->external) { $attachment->setAttribute('content', $attachment->path); + + return response()->json($attachment); } - return response()->json($attachment); + // Build and split our core JSON, at point of content. + $splitter = 'CONTENT_SPLIT_LOCATION_' . time() . '_' . rand(1, 40000); + $attachment->setAttribute('content', $splitter); + $json = $attachment->toJson(); + $jsonParts = explode($splitter, $json); + // Get a stream for the file data from storage + $stream = $this->attachmentService->streamAttachmentFromStorage($attachment); + + return response()->stream(function () use ($jsonParts, $stream) { + // Output the pre-content JSON data + echo $jsonParts[0]; + + // Stream out our attachment data as base64 content + stream_filter_append($stream, 'convert.base64-encode', STREAM_FILTER_READ); + fpassthru($stream); + fclose($stream); + + // Output our post-content JSON data + echo $jsonParts[1]; + }, 200, ['Content-Type' => 'application/json']); } /** * Update the details of a single attachment. + * As per the create endpoint, if a file is being provided as the attachment content + * the request should be formatted as a multipart/form-data request instead of JSON. * * @throws ValidationException * @throws FileUploadException */ public function update(Request $request, string $id) { - $requestData = $this->validate($request, $this->rules['update']); + $requestData = $this->validate($request, $this->rules()['update']); /** @var Attachment $attachment */ $attachment = Attachment::visible()->findOrFail($id); @@ -129,10 +143,11 @@ class AttachmentApiController extends ApiController if ($request->hasFile('file')) { $uploadedFile = $request->file('file'); - $attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $page->id); + $attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $attachment); } $this->attachmentService->updateFile($attachment, $requestData); + return response()->json($attachment); } @@ -152,4 +167,21 @@ class AttachmentApiController extends ApiController return response('', 204); } + protected function rules(): array + { + return [ + 'create' => [ + 'name' => ['required', 'min:1', 'max:255', 'string'], + 'uploaded_to' => ['required', 'integer', 'exists:pages,id'], + 'file' => array_merge(['required_without:link'], $this->attachmentService->getFileValidationRules()), + 'link' => ['required_without:file', 'min:1', 'max:255', 'safe_url'], + ], + 'update' => [ + 'name' => ['min:1', 'max:255', 'string'], + 'uploaded_to' => ['integer', 'exists:pages,id'], + 'file' => $this->attachmentService->getFileValidationRules(), + 'link' => ['min:1', 'max:255', 'safe_url'], + ], + ]; + } }