]> BookStack Code Mirror - bookstack/blobdiff - app/Http/Controllers/Api/AttachmentApiController.php
Add popular PHP templating languages to code editor
[bookstack] / app / Http / Controllers / Api / AttachmentApiController.php
index 2ee1c98a68435f9d39646c655898de977dde031a..7059ca28248456f8e1fc027fc7a39b4a37e13a56 100644 (file)
@@ -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'],
+            ],
+        ];
+    }
 }