Added back-end attachments-in-browser support
A query string will cause attachments to be provided inline with an appropriate mime type. Remaining actions: - Tests - Front-end functionality - Config option?
This commit is contained in:
parent
a8471b2c66
commit
888f435651
|
@ -14,16 +14,14 @@ use Illuminate\Validation\ValidationException;
|
||||||
class AttachmentController extends Controller
|
class AttachmentController extends Controller
|
||||||
{
|
{
|
||||||
protected $attachmentService;
|
protected $attachmentService;
|
||||||
protected $attachment;
|
|
||||||
protected $pageRepo;
|
protected $pageRepo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AttachmentController constructor.
|
* AttachmentController constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(AttachmentService $attachmentService, Attachment $attachment, PageRepo $pageRepo)
|
public function __construct(AttachmentService $attachmentService, PageRepo $pageRepo)
|
||||||
{
|
{
|
||||||
$this->attachmentService = $attachmentService;
|
$this->attachmentService = $attachmentService;
|
||||||
$this->attachment = $attachment;
|
|
||||||
$this->pageRepo = $pageRepo;
|
$this->pageRepo = $pageRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +65,7 @@ class AttachmentController extends Controller
|
||||||
'file' => 'required|file'
|
'file' => 'required|file'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$attachment = $this->attachment->newQuery()->findOrFail($attachmentId);
|
$attachment = Attachment::query()->findOrFail($attachmentId);
|
||||||
$this->checkOwnablePermission('view', $attachment->page);
|
$this->checkOwnablePermission('view', $attachment->page);
|
||||||
$this->checkOwnablePermission('page-update', $attachment->page);
|
$this->checkOwnablePermission('page-update', $attachment->page);
|
||||||
$this->checkOwnablePermission('attachment-create', $attachment);
|
$this->checkOwnablePermission('attachment-create', $attachment);
|
||||||
|
@ -89,7 +87,7 @@ class AttachmentController extends Controller
|
||||||
*/
|
*/
|
||||||
public function getUpdateForm(string $attachmentId)
|
public function getUpdateForm(string $attachmentId)
|
||||||
{
|
{
|
||||||
$attachment = $this->attachment->findOrFail($attachmentId);
|
$attachment = Attachment::query()->findOrFail($attachmentId);
|
||||||
|
|
||||||
$this->checkOwnablePermission('page-update', $attachment->page);
|
$this->checkOwnablePermission('page-update', $attachment->page);
|
||||||
$this->checkOwnablePermission('attachment-create', $attachment);
|
$this->checkOwnablePermission('attachment-create', $attachment);
|
||||||
|
@ -202,9 +200,10 @@ class AttachmentController extends Controller
|
||||||
* @throws FileNotFoundException
|
* @throws FileNotFoundException
|
||||||
* @throws NotFoundException
|
* @throws NotFoundException
|
||||||
*/
|
*/
|
||||||
public function get(string $attachmentId)
|
public function get(Request $request, string $attachmentId)
|
||||||
{
|
{
|
||||||
$attachment = $this->attachment->findOrFail($attachmentId);
|
/** @var Attachment $attachment */
|
||||||
|
$attachment = Attachment::query()->findOrFail($attachmentId);
|
||||||
try {
|
try {
|
||||||
$page = $this->pageRepo->getById($attachment->uploaded_to);
|
$page = $this->pageRepo->getById($attachment->uploaded_to);
|
||||||
} catch (NotFoundException $exception) {
|
} catch (NotFoundException $exception) {
|
||||||
|
@ -217,8 +216,13 @@ class AttachmentController extends Controller
|
||||||
return redirect($attachment->path);
|
return redirect($attachment->path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$fileName = $attachment->getFileName();
|
||||||
$attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
|
$attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
|
||||||
return $this->downloadResponse($attachmentContents, $attachment->getFileName());
|
|
||||||
|
if ($request->get('open') === 'true') {
|
||||||
|
return $this->inlineDownloadResponse($attachmentContents, $fileName);
|
||||||
|
}
|
||||||
|
return $this->downloadResponse($attachmentContents, $fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -227,7 +231,7 @@ class AttachmentController extends Controller
|
||||||
*/
|
*/
|
||||||
public function delete(string $attachmentId)
|
public function delete(string $attachmentId)
|
||||||
{
|
{
|
||||||
$attachment = $this->attachment->findOrFail($attachmentId);
|
$attachment = Attachment::query()->findOrFail($attachmentId);
|
||||||
$this->checkOwnablePermission('attachment-delete', $attachment);
|
$this->checkOwnablePermission('attachment-delete', $attachment);
|
||||||
$this->attachmentService->deleteFile($attachment);
|
$this->attachmentService->deleteFile($attachment);
|
||||||
return response()->json(['message' => trans('entities.attachments_deleted')]);
|
return response()->json(['message' => trans('entities.attachments_deleted')]);
|
||||||
|
|
|
@ -6,6 +6,7 @@ use BookStack\Facades\Activity;
|
||||||
use BookStack\Interfaces\Loggable;
|
use BookStack\Interfaces\Loggable;
|
||||||
use BookStack\HasCreatorAndUpdater;
|
use BookStack\HasCreatorAndUpdater;
|
||||||
use BookStack\Model;
|
use BookStack\Model;
|
||||||
|
use finfo;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||||
|
@ -121,6 +122,20 @@ abstract class Controller extends BaseController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a file download response that provides the file with a content-type
|
||||||
|
* correct for the file, in a way so the browser can show the content in browser.
|
||||||
|
*/
|
||||||
|
protected function inlineDownloadResponse(string $content, string $fileName): Response
|
||||||
|
{
|
||||||
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||||
|
$mime = $finfo->buffer($content) ?: 'application/octet-stream';
|
||||||
|
return response()->make($content, 200, [
|
||||||
|
'Content-Type' => $mime,
|
||||||
|
'Content-Disposition' => 'inline; filename="' . $fileName . '"'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a positive, successful notification to the user on next view load.
|
* Show a positive, successful notification to the user on next view load.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,8 +3,10 @@
|
||||||
use BookStack\Exceptions\FileUploadException;
|
use BookStack\Exceptions\FileUploadException;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
|
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
|
||||||
|
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||||
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
|
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Log;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
|
||||||
class AttachmentService
|
class AttachmentService
|
||||||
|
@ -38,11 +40,9 @@ class AttachmentService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an attachment from storage.
|
* Get an attachment from storage.
|
||||||
* @param Attachment $attachment
|
* @throws FileNotFoundException
|
||||||
* @return string
|
|
||||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
|
||||||
*/
|
*/
|
||||||
public function getAttachmentFromStorage(Attachment $attachment)
|
public function getAttachmentFromStorage(Attachment $attachment): string
|
||||||
{
|
{
|
||||||
return $this->getStorage()->get($attachment->path);
|
return $this->getStorage()->get($attachment->path);
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ class AttachmentService
|
||||||
try {
|
try {
|
||||||
$storage->put($attachmentPath, $attachmentData);
|
$storage->put($attachmentPath, $attachmentData);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
\Log::error('Error when attempting file upload:' . $e->getMessage());
|
Log::error('Error when attempting file upload:' . $e->getMessage());
|
||||||
throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentPath]));
|
throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentPath]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"php": "^7.3|^8.0",
|
"php": "^7.3|^8.0",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
|
"ext-fileinfo": "*",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
|
|
Loading…
Reference in New Issue