diff --git a/.env.testing b/.env.testing index 8f13ebd4..987cc8cf 100644 --- a/.env.testing +++ b/.env.testing @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY=base64:IOcs+sRmD3FGF8qveF6VTgxB26b0ShnwmqIZp/fYNGo= +APP_KEY=base64:X7/vUveJtTmrC1J+UFXb9m6l1RBbAoJvNyj7sMC+GFI= APP_DEBUG=true APP_URL=http://localhost APP_DEMO=false diff --git a/composer.json b/composer.json index 3feaae94..41ad4b16 100644 --- a/composer.json +++ b/composer.json @@ -82,6 +82,7 @@ "database/factories" ], "files": [ + "src/Support/errors.php", "src/Support/helpers.php" ] }, diff --git a/public/mix-manifest.json b/public/mix-manifest.json index afffb736..8ab6327b 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,75 +1,75 @@ { "/js/main.js": "/js/main.js", - "/chunks/request.js": "/chunks/request.js?id=37e3e34fbcc98d4c", - "/chunks/request-upload.js": "/chunks/request-upload.js?id=2a6d910114ffb8d2", - "/chunks/setup-wizard.js": "/chunks/setup-wizard.js?id=3c2fc454c3fce8d2", - "/chunks/status-check.js": "/chunks/status-check.js?id=ea0f79fc9a604cff", - "/chunks/purchase-code.js": "/chunks/purchase-code.js?id=c1df85c34d7e9521", - "/chunks/database.js": "/chunks/database.js?id=c686d46622194c7e", - "/chunks/environment-setup.js": "/chunks/environment-setup.js?id=d045f6827f61ac9b", - "/chunks/app-setup.js": "/chunks/app-setup.js?id=c37d19ccd20b6656", - "/chunks/admin-account.js": "/chunks/admin-account.js?id=666e7ee49b02b57c", - "/chunks/shared.js": "/chunks/shared.js?id=557e8a1b4256d65a", - "/chunks/shared/browser.js": "/chunks/shared/browser.js?id=a9710655d75c8079", - "/chunks/shared/single-file.js": "/chunks/shared/single-file.js?id=7865f79c2905e81d", - "/chunks/shared/authenticate.js": "/chunks/shared/authenticate.js?id=ca8cc89fe5982782", - "/chunks/not-found.js": "/chunks/not-found.js?id=4cb8d3a7a2212c3c", - "/chunks/temporary-unavailable.js": "/chunks/temporary-unavailable.js?id=c71981d946a9ca71", - "/chunks/admin.js": "/chunks/admin.js?id=4c86279cd6e85aa5", - "/chunks/dashboard.js": "/chunks/dashboard.js?id=98dade7f03d93826", - "/chunks/invoices.js": "/chunks/invoices.js?id=70fb9a603be2f554", - "/chunks/subscriptions.js": "/chunks/subscriptions.js?id=94e96e1bb505ae59", - "/chunks/pages.js": "/chunks/pages.js?id=d1f5d211e9dfc4ae", - "/chunks/page-edit.js": "/chunks/page-edit.js?id=c241f8733acb584f", - "/chunks/plans.js": "/chunks/plans.js?id=f6e9d2f34fac6d79", - "/chunks/users.js": "/chunks/users.js?id=651b8af7afecc88e", - "/chunks/user-create.js": "/chunks/user-create.js?id=3b0c4a348a5b1857", - "/chunks/plan-create/fixed.js": "/chunks/plan-create/fixed.js?id=81dcec66b3ab0f9c", - "/chunks/plan-create/metered.js": "/chunks/plan-create/metered.js?id=e7d07663f1ec94fb", - "/chunks/user.js": "/chunks/user.js?id=dae4ac26750f99d0", - "/chunks/user-detail.js": "/chunks/user-detail.js?id=207de969e16d9284", - "/chunks/user-storage.js": "/chunks/user-storage.js?id=d56b28f604b1d012", - "/chunks/user-subscription.js": "/chunks/user-subscription.js?id=25a31d9cbbb0507a", - "/chunks/user-password.js": "/chunks/user-password.js?id=be5d5cdf90f1e0de", - "/chunks/user-delete.js": "/chunks/user-delete.js?id=0783eb95a7226ff8", - "/chunks/plan.js": "/chunks/plan.js?id=d8ffa85dc9b68966", - "/chunks/plan-subscribers.js": "/chunks/plan-subscribers.js?id=3010ddb4ba7419e9", - "/chunks/plan-settings.js": "/chunks/plan-settings.js?id=f41fe30f2273279c", - "/chunks/plan-delete.js": "/chunks/plan-delete.js?id=bf5e732edaff3608", - "/chunks/payments.js": "/chunks/payments.js?id=051e8246e2b5c9d0", - "/chunks/payments/billings.js": "/chunks/payments/billings.js?id=893998fa7380c5a0", - "/chunks/payments/settings.js": "/chunks/payments/settings.js?id=189bb7b91cd6a32a", - "/chunks/app-settings.js": "/chunks/app-settings.js?id=692d3291fb9d2cf7", - "/chunks/app-appearance.js": "/chunks/app-appearance.js?id=e8377974f2444a44", - "/chunks/app-index.js": "/chunks/app-index.js?id=9aadc4841d83e821", - "/chunks/app-environment.js": "/chunks/app-environment.js?id=637b39d290081e0d", - "/chunks/app-others.js": "/chunks/app-others.js?id=ac2dccb4b4a87bfb", - "/chunks/app-sign-in-out.js": "/chunks/app-sign-in-out.js?id=04fbf6846bbc6ade", - "/chunks/app-adsense.js": "/chunks/app-adsense.js?id=18842ed46783ea39", - "/chunks/app-server.js": "/chunks/app-server.js?id=6dbdc01c6b0e65b3", - "/chunks/app-language.js": "/chunks/app-language.js?id=db16d24415743d42", - "/chunks/homepage.js": "/chunks/homepage.js?id=828e3e90bf35b652", - "/chunks/dynamic-page.js": "/chunks/dynamic-page.js?id=e110e8923b6ca22f", - "/chunks/contact-us.js": "/chunks/contact-us.js?id=5ca104a75598dd39", - "/chunks/successfully-email-verified.js": "/chunks/successfully-email-verified.js?id=3153532f0d2273c8", - "/chunks/successfully-email-send.js": "/chunks/successfully-email-send.js?id=d630ed9f6f558509", - "/chunks/sign-in.js": "/chunks/sign-in.js?id=8cec25f9f11b217a", - "/chunks/sign-up.js": "/chunks/sign-up.js?id=c1cadbfd4ac0df7b", - "/chunks/forgotten-password.js": "/chunks/forgotten-password.js?id=9c62b8573fbdd567", - "/chunks/create-new-password.js": "/chunks/create-new-password.js?id=926b35b6745d99ba", - "/chunks/settings.js": "/chunks/settings.js?id=efd739cafc57e762", - "/chunks/profile.js": "/chunks/profile.js?id=013b252e73d160d0", - "/chunks/settings-password.js": "/chunks/settings-password.js?id=149343604362b7df", - "/chunks/settings-storage.js": "/chunks/settings-storage.js?id=68765cac4e648e90", - "/chunks/billing.js": "/chunks/billing.js?id=c77093c6a0dc65e3", - "/chunks/platform.js": "/chunks/platform.js?id=90cb99dd8c57c5a0", - "/chunks/files.js": "/chunks/files.js?id=5d6eb9b9f9ecd296", - "/chunks/recent-uploads.js": "/chunks/recent-uploads.js?id=827d3a5dcce159b5", - "/chunks/my-shared-items.js": "/chunks/my-shared-items.js?id=2a4e4e0db02cbcbb", - "/chunks/trash.js": "/chunks/trash.js?id=8362aa0f91231350", - "/chunks/team-folders.js": "/chunks/team-folders.js?id=0a46fecf35a23406", - "/chunks/shared-with-me.js": "/chunks/shared-with-me.js?id=77a33583775c6d8f", - "/chunks/invitation.js": "/chunks/invitation.js?id=64a211c90b505767", + "/chunks/request.js": "/chunks/request.js?id=5b796b8410a96a49", + "/chunks/request-upload.js": "/chunks/request-upload.js?id=b1aaf8357a30794a", + "/chunks/setup-wizard.js": "/chunks/setup-wizard.js?id=19a0784e59d768ec", + "/chunks/status-check.js": "/chunks/status-check.js?id=51a75f0b3b260189", + "/chunks/purchase-code.js": "/chunks/purchase-code.js?id=df5bd89528649783", + "/chunks/database.js": "/chunks/database.js?id=15cc488117dccf7b", + "/chunks/environment-setup.js": "/chunks/environment-setup.js?id=e1ad83583367917a", + "/chunks/app-setup.js": "/chunks/app-setup.js?id=288594cd7f628cf8", + "/chunks/admin-account.js": "/chunks/admin-account.js?id=916450217130f3b8", + "/chunks/shared.js": "/chunks/shared.js?id=3b541c484831dd33", + "/chunks/shared/browser.js": "/chunks/shared/browser.js?id=3dc8fdb008b6ff5f", + "/chunks/shared/single-file.js": "/chunks/shared/single-file.js?id=04889fdd4175d9e1", + "/chunks/shared/authenticate.js": "/chunks/shared/authenticate.js?id=672e931a9fb0b672", + "/chunks/not-found.js": "/chunks/not-found.js?id=9f6ce23ce5d969f1", + "/chunks/temporary-unavailable.js": "/chunks/temporary-unavailable.js?id=f564565faa09d6d6", + "/chunks/admin.js": "/chunks/admin.js?id=45c706127b10950f", + "/chunks/dashboard.js": "/chunks/dashboard.js?id=ec5474f5a9da434c", + "/chunks/invoices.js": "/chunks/invoices.js?id=1416cbf6d1a593ac", + "/chunks/subscriptions.js": "/chunks/subscriptions.js?id=5bf6704f5b599f36", + "/chunks/pages.js": "/chunks/pages.js?id=c8380d571e91e8be", + "/chunks/page-edit.js": "/chunks/page-edit.js?id=fb3f9eda3dc1d15c", + "/chunks/plans.js": "/chunks/plans.js?id=c8506e0e20966ef7", + "/chunks/users.js": "/chunks/users.js?id=ec687ee365c4248a", + "/chunks/user-create.js": "/chunks/user-create.js?id=8dd9d29f024132f5", + "/chunks/plan-create/fixed.js": "/chunks/plan-create/fixed.js?id=b24d8dbe1f0f706f", + "/chunks/plan-create/metered.js": "/chunks/plan-create/metered.js?id=d9f1bcb1fe44a6ae", + "/chunks/user.js": "/chunks/user.js?id=c191b906a0496fe5", + "/chunks/user-detail.js": "/chunks/user-detail.js?id=f9e17ff98354e984", + "/chunks/user-storage.js": "/chunks/user-storage.js?id=a8e0bce4703232a0", + "/chunks/user-subscription.js": "/chunks/user-subscription.js?id=27d046c1122783ea", + "/chunks/user-password.js": "/chunks/user-password.js?id=23d3aee39f539a3c", + "/chunks/user-delete.js": "/chunks/user-delete.js?id=6bea6f8cadf4d74f", + "/chunks/plan.js": "/chunks/plan.js?id=4b267375ea9f19b3", + "/chunks/plan-subscribers.js": "/chunks/plan-subscribers.js?id=a956ceca6865c50c", + "/chunks/plan-settings.js": "/chunks/plan-settings.js?id=715ee86991d5e4db", + "/chunks/plan-delete.js": "/chunks/plan-delete.js?id=1ad77372d342326f", + "/chunks/payments.js": "/chunks/payments.js?id=dc4586691c25de6f", + "/chunks/payments/billings.js": "/chunks/payments/billings.js?id=dd6c9d6a29a47808", + "/chunks/payments/settings.js": "/chunks/payments/settings.js?id=5b139952f337c83e", + "/chunks/app-settings.js": "/chunks/app-settings.js?id=55da23af2b076069", + "/chunks/app-appearance.js": "/chunks/app-appearance.js?id=a694a01f3641712c", + "/chunks/app-index.js": "/chunks/app-index.js?id=efdbfa062749ca00", + "/chunks/app-environment.js": "/chunks/app-environment.js?id=9632034e8ded7d34", + "/chunks/app-others.js": "/chunks/app-others.js?id=0f84e2ed1230558e", + "/chunks/app-sign-in-out.js": "/chunks/app-sign-in-out.js?id=1cfffc99465b9a7a", + "/chunks/app-adsense.js": "/chunks/app-adsense.js?id=a5dc9e715f8561bd", + "/chunks/app-server.js": "/chunks/app-server.js?id=4510d63685353c68", + "/chunks/app-language.js": "/chunks/app-language.js?id=7e5f3d5ec447e397", + "/chunks/homepage.js": "/chunks/homepage.js?id=b6597181c9e4353d", + "/chunks/dynamic-page.js": "/chunks/dynamic-page.js?id=2504793131107b1f", + "/chunks/contact-us.js": "/chunks/contact-us.js?id=9adc7e145be4e160", + "/chunks/successfully-email-verified.js": "/chunks/successfully-email-verified.js?id=25b805ade5230382", + "/chunks/successfully-email-send.js": "/chunks/successfully-email-send.js?id=f4562229776d9f56", + "/chunks/sign-in.js": "/chunks/sign-in.js?id=0d48d229038a3a1e", + "/chunks/sign-up.js": "/chunks/sign-up.js?id=bb92bad614e60d45", + "/chunks/forgotten-password.js": "/chunks/forgotten-password.js?id=50a1bc5e4ed86ec9", + "/chunks/create-new-password.js": "/chunks/create-new-password.js?id=f652de052dba55c1", + "/chunks/settings.js": "/chunks/settings.js?id=b918d0cf757fafe3", + "/chunks/profile.js": "/chunks/profile.js?id=7d3719710c55ceeb", + "/chunks/settings-password.js": "/chunks/settings-password.js?id=11d4331650cac280", + "/chunks/settings-storage.js": "/chunks/settings-storage.js?id=994b669a56fd417b", + "/chunks/billing.js": "/chunks/billing.js?id=4ea77ab501b8c575", + "/chunks/platform.js": "/chunks/platform.js?id=486a1e4ae8ae42df", + "/chunks/files.js": "/chunks/files.js?id=060b1a34dfdbe97c", + "/chunks/recent-uploads.js": "/chunks/recent-uploads.js?id=0f63bbc02ad8f3e1", + "/chunks/my-shared-items.js": "/chunks/my-shared-items.js?id=0a06d32b4cf8b52c", + "/chunks/trash.js": "/chunks/trash.js?id=ac5389500f8f7912", + "/chunks/team-folders.js": "/chunks/team-folders.js?id=c0a03c6937856ca1", + "/chunks/shared-with-me.js": "/chunks/shared-with-me.js?id=88fa008db0c6a4f6", + "/chunks/invitation.js": "/chunks/invitation.js?id=9ed8456c9d6d5ce1", "/css/tailwind.css": "/css/tailwind.css", "/css/app.css": "/css/app.css" } diff --git a/resources/js/components/Others/NotificationsPopup.vue b/resources/js/components/Others/NotificationsPopup.vue index b8331652..6427d827 100644 --- a/resources/js/components/Others/NotificationsPopup.vue +++ b/resources/js/components/Others/NotificationsPopup.vue @@ -4,7 +4,7 @@ - + call( fn () => resolve(DeleteFailedFilesAction::class)() )->everySixHours(); diff --git a/src/App/Users/Controllers/GetAvatarController.php b/src/App/Users/Controllers/GetAvatarController.php index 9ad14b3c..70042817 100644 --- a/src/App/Users/Controllers/GetAvatarController.php +++ b/src/App/Users/Controllers/GetAvatarController.php @@ -1,9 +1,9 @@ attributes['avatar'] && ! is_storage_driver('local')) { + if ($this->attributes['avatar'] && ! isStorageDriver('local')) { foreach (config('vuefilemanager.avatar_sizes') as $item) { $filePath = "avatars/{$item['name']}-{$this->attributes['avatar']}"; diff --git a/src/Domain/Files/Actions/DownloadFileAction.php b/src/Domain/Files/Actions/DownloadFileAction.php index a766bb68..a2773099 100644 --- a/src/Domain/Files/Actions/DownloadFileAction.php +++ b/src/Domain/Files/Actions/DownloadFileAction.php @@ -2,36 +2,44 @@ namespace Domain\Files\Actions; use Domain\Files\Models\File; +use Illuminate\Http\Response; +use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Storage; -use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; class DownloadFileAction { /** * Call and download file */ - public function __invoke( - File $file, - string $user_id, - ): BinaryFileResponse { + public function __invoke(File $file): Response|StreamedResponse|RedirectResponse + { // Get file path - $path = "files/$user_id/$file->basename"; - - // Check if file exist - if (! Storage::exists($path)) { - abort(404); - } + $filePath = "files/$file->user_id/$file->basename"; // Get pretty name - $pretty_name = get_pretty_name($file->basename, $file->name, $file->mimetype); + $fileName = getPrettyName($file->basename, $file->name, $file->mimetype); - return response() - ->download(Storage::path($path), $pretty_name, [ - 'Accept-Ranges' => 'bytes', - 'Content-Type' => Storage::mimeType($path), - 'Content-Length' => Storage::size($path), - 'Content-Range' => 'bytes 0-600/' . Storage::size($path), - 'Content-Disposition' => "attachment; filename=$pretty_name", - ]); + // Check if file exist + if (! Storage::exists($filePath)) { + return response('The file not found.', 404); + } + + // Format response header + $header = [ + 'ResponseAcceptRanges' => 'bytes', + 'ResponseContentType' => Storage::mimeType($filePath), + 'ResponseContentLength' => Storage::size($filePath), + 'ResponseContentRange' => 'bytes 0-600/' . Storage::size($filePath), + 'ResponseContentDisposition' => "attachment; filename=$fileName", + ]; + + // If s3 redirect to temporary download url + if (isStorageDriver('s3')) { + return redirect()->away(Storage::temporaryUrl($filePath, now()->addHour(), $header)); + } + + // Download file + return Storage::download($filePath, $fileName, $header); } } diff --git a/src/Domain/Files/Actions/DownloadThumbnailAction.php b/src/Domain/Files/Actions/DownloadThumbnailAction.php index 8161e962..f8eb2b3f 100644 --- a/src/Domain/Files/Actions/DownloadThumbnailAction.php +++ b/src/Domain/Files/Actions/DownloadThumbnailAction.php @@ -2,6 +2,7 @@ namespace Domain\Files\Actions; use Domain\Files\Models\File; +use Illuminate\Http\Response; use Illuminate\Support\Facades\Storage; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -13,18 +14,18 @@ class DownloadThumbnailAction public function __invoke( string $filename, File $file - ): StreamedResponse { + ): StreamedResponse|Response { // Get file path - $path = "/files/$file->user_id/$filename"; + $filePath = "/files/$file->user_id/$filename"; // Check if file exist - if (! Storage::exists($path)) { + if (! Storage::exists($filePath)) { // Get original file path $substituteFilePath = "/files/$file->user_id/$file->basename"; // Check if original file exist if (! Storage::exists($substituteFilePath)) { - abort(404); + return response('File not found', 404); } // Return image thumbnail @@ -32,6 +33,6 @@ class DownloadThumbnailAction } // Return image thumbnail - return Storage::download($path, $filename); + return Storage::download($filePath, $filename); } } diff --git a/src/Domain/Files/Actions/UploadFileAction.php b/src/Domain/Files/Actions/UploadFileAction.php index e10e7598..fb8fc781 100644 --- a/src/Domain/Files/Actions/UploadFileAction.php +++ b/src/Domain/Files/Actions/UploadFileAction.php @@ -82,7 +82,7 @@ class UploadFileAction ($this->createImageThumbnail)($fileName, $file, $user->id); // Move files to external storage - if (! is_storage_driver('local')) { + if (! isStorageDriver('local')) { ($this->moveFileToExternalStorage)($fileName, $user->id); } diff --git a/src/Domain/Files/Controllers/FileAccess/GetFileController.php b/src/Domain/Files/Controllers/FileAccess/GetFileController.php index 2bf37cf5..db7c45ef 100644 --- a/src/Domain/Files/Controllers/FileAccess/GetFileController.php +++ b/src/Domain/Files/Controllers/FileAccess/GetFileController.php @@ -2,12 +2,13 @@ namespace Domain\Files\Controllers\FileAccess; use Gate; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Response; use App\Http\Controllers\Controller; -use Domain\Files\Models\File as UserFile; +use Domain\Files\Models\File; use Domain\Files\Actions\DownloadFileAction; use Domain\Traffic\Actions\RecordDownloadAction; -use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; class GetFileController extends Controller { @@ -19,31 +20,30 @@ class GetFileController extends Controller public function __invoke( string $filename, - ): Response|BinaryFileResponse { - $file = UserFile::withTrashed() + ): Response|RedirectResponse|StreamedResponse { + // Get requested file + $file = File::withTrashed() ->where('basename', $filename) ->firstOrFail(); // Check if user can download file if (! $file->owner->canDownload()) { - return response([ - 'type' => 'error', - 'message' => 'This user action is not allowed.', - ], 401); + return response(userActionNotAllowedError(), 401); } + // Check if user has privileges to download file if (! Gate::any(['can-edit', 'can-view'], [$file, null])) { - abort(403, 'Access Denied'); + return response(accessDeniedError(), 403); } // TODO: resolve video buffering // Store user download size ($this->recordDownload)( - file_size: (int) $file->getRawOriginal('filesize'), + file_size: $file->filesize, user_id: $file->user_id, ); - return ($this->downloadFile)($file, $file->user_id); + return ($this->downloadFile)($file); } } diff --git a/src/Domain/Files/Controllers/FileAccess/GetThumbnailController.php b/src/Domain/Files/Controllers/FileAccess/GetThumbnailController.php index abb08dcf..734b30d0 100644 --- a/src/Domain/Files/Controllers/FileAccess/GetThumbnailController.php +++ b/src/Domain/Files/Controllers/FileAccess/GetThumbnailController.php @@ -6,6 +6,7 @@ use Illuminate\Http\Request; use Domain\Files\Models\File; use App\Http\Controllers\Controller; use Domain\Files\Actions\DownloadThumbnailAction; +use Illuminate\Http\Response; use Symfony\Component\HttpFoundation\StreamedResponse; use Illuminate\Contracts\Filesystem\FileNotFoundException; @@ -13,19 +14,21 @@ class GetThumbnailController extends Controller { public function __construct( private DownloadThumbnailAction $downloadThumbnail, - ) { - } + ) {} public function __invoke( Request $request, string $filename, - ): FileNotFoundException | StreamedResponse { + ): FileNotFoundException | StreamedResponse | Response { + + // Get requested thumbnail $file = File::withTrashed() ->where('basename', substr($filename, 3)) ->firstOrFail(); + // Check if user has privileges to download file if (! Gate::any(['can-edit', 'can-view'], [$file, null])) { - abort(403, 'Access Denied'); + return response(accessDeniedError(), 403); } return ($this->downloadThumbnail)($filename, $file); diff --git a/src/Domain/Files/Controllers/FileAccess/VisitorGetFileController.php b/src/Domain/Files/Controllers/FileAccess/VisitorGetFileController.php index de76edbf..2188a617 100644 --- a/src/Domain/Files/Controllers/FileAccess/VisitorGetFileController.php +++ b/src/Domain/Files/Controllers/FileAccess/VisitorGetFileController.php @@ -2,14 +2,15 @@ namespace Domain\Files\Controllers\FileAccess; use Domain\Files\Models\File; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Response; use Domain\Sharing\Models\Share; use App\Http\Controllers\Controller; use Domain\Files\Actions\DownloadFileAction; use Domain\Traffic\Actions\RecordDownloadAction; use Domain\Sharing\Actions\ProtectShareRecordAction; -use Symfony\Component\HttpFoundation\BinaryFileResponse; use Domain\Sharing\Actions\VerifyAccessToItemWithinAction; +use Symfony\Component\HttpFoundation\StreamedResponse; /** * Get file public @@ -27,13 +28,10 @@ class VisitorGetFileController extends Controller public function __invoke( $filename, Share $shared, - ): BinaryFileResponse|Response { + ): StreamedResponse|RedirectResponse|Response { // Check if user can download file if (! $shared->user->canDownload()) { - return response([ - 'type' => 'error', - 'message' => 'This user action is not allowed.', - ], 401); + return response(userActionNotAllowedError(), 401); } // Check ability to access protected share files @@ -49,11 +47,11 @@ class VisitorGetFileController extends Controller // Store user download size ($this->recordDownload)( - file_size: (int) $file->getRawOriginal('filesize'), + file_size: $file->filesize, user_id: $shared->user_id, ); // Finally, download file - return ($this->downloadFile)($file, $shared->user_id); + return ($this->downloadFile)($file); } } diff --git a/src/Domain/Files/Controllers/FileAccess/VisitorGetThumbnailController.php b/src/Domain/Files/Controllers/FileAccess/VisitorGetThumbnailController.php index f94949fc..563bca0f 100644 --- a/src/Domain/Files/Controllers/FileAccess/VisitorGetThumbnailController.php +++ b/src/Domain/Files/Controllers/FileAccess/VisitorGetThumbnailController.php @@ -40,7 +40,7 @@ class VisitorGetThumbnailController extends Controller // Store user download size ($this->recordDownload)( - file_size: (int) $file->getRawOriginal('filesize'), + file_size: $file->filesize, user_id: $shared->user_id, ); diff --git a/src/Domain/Files/Models/File.php b/src/Domain/Files/Models/File.php index 61a964cc..23ee40d3 100644 --- a/src/Domain/Files/Models/File.php +++ b/src/Domain/Files/Models/File.php @@ -25,7 +25,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; * @property string user_id * @property string parent_id * @property string thumbnail - * @property string filesize + * @property int filesize * @property string type * @property string basename * @property string name @@ -100,7 +100,7 @@ class File extends Model ->all(); // Generate thumbnail link for external storage service - if ($this->type === 'image' && ! is_storage_driver('local')) { + if ($this->type === 'image' && ! isStorageDriver('local')) { foreach ($thumbnail_sizes as $item) { $filePath = "files/{$this->user_id}/{$item['name']}-{$this->basename}"; @@ -139,23 +139,6 @@ class File extends Model */ public function getFileUrlAttribute(): string { - // Get file from external storage - if (! is_storage_driver('local')) { - $file_pretty_name = is_storage_driver('backblaze') - ? Str::snake(mb_strtolower($this->attributes['name'])) - : get_pretty_name($this->attributes['basename'], $this->attributes['name'], $this->attributes['mimetype']); - - $header = [ - 'ResponseAcceptRanges' => 'bytes', - 'ResponseContentType' => $this->attributes['mimetype'], - 'ResponseContentLength' => $this->attributes['filesize'], - 'ResponseContentRange' => 'bytes 0-600/' . $this->attributes['filesize'], - 'ResponseContentDisposition' => 'attachment; filename=' . $file_pretty_name, - ]; - - return Storage::temporaryUrl("files/$this->user_id/{$this->attributes['basename']}", now()->addDay(), $header); - } - // Get thumbnail from local storage $route = route('file', ['name' => $this->attributes['basename']]); @@ -166,7 +149,6 @@ class File extends Model // Set upload request public url if ($this->uploadRequestAccess) { - // TODO: review request for s3 $route .= "/upload-request/$this->uploadRequestAccess"; } diff --git a/src/Domain/Settings/Controllers/GetAppImageController.php b/src/Domain/Settings/Controllers/GetAppImageController.php index 92c808e9..742c3b41 100644 --- a/src/Domain/Settings/Controllers/GetAppImageController.php +++ b/src/Domain/Settings/Controllers/GetAppImageController.php @@ -1,9 +1,9 @@ recordDownload)( - file_size: (int)$file->getRawOriginal('filesize'), + file_size: $file->filesize, user_id: $share->user_id, ); - // Get file path - $path = "files/$share->user_id/$file->basename"; - - // Check if file exist - if (!Storage::exists($path)) { - abort(404); - } - - // Get pretty name - $pretty_name = get_pretty_name($file->basename, $file->name, $file->mimetype); - - // If s3 redirect to temporary url - if (is_storage_driver('s3')) { - return redirect()->away(Storage::temporaryUrl($path, now()->addHour(), [ - 'ResponseAcceptRanges' => 'bytes', - 'ResponseContentType' => Storage::mimeType($path), - 'ResponseContentLength' => Storage::size($path), - 'ResponseContentRange' => 'bytes 0-600/' . Storage::size($path), - 'ResponseContentDisposition' => "attachment; filename=$pretty_name", - ])); - } - - return Storage::download($path, $pretty_name); + return ($this->downloadFile)($file); } } \ No newline at end of file diff --git a/src/Domain/UploadRequest/Controllers/FileAccess/GetFileFromUploadRequestController.php b/src/Domain/UploadRequest/Controllers/FileAccess/GetFileFromUploadRequestController.php index 7c1cf665..f88da1ec 100644 --- a/src/Domain/UploadRequest/Controllers/FileAccess/GetFileFromUploadRequestController.php +++ b/src/Domain/UploadRequest/Controllers/FileAccess/GetFileFromUploadRequestController.php @@ -2,13 +2,12 @@ namespace Domain\UploadRequest\Controllers\FileAccess; use Domain\Files\Models\File; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Response; use Domain\Files\Actions\DownloadFileAction; use Domain\UploadRequest\Models\UploadRequest; use Domain\Traffic\Actions\RecordDownloadAction; -use Illuminate\Contracts\Foundation\Application; -use Illuminate\Contracts\Routing\ResponseFactory; -use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; /** * Get shared file record @@ -24,7 +23,7 @@ class GetFileFromUploadRequestController public function __invoke( string $filename, UploadRequest $uploadRequest - ): Application|ResponseFactory|Response|BinaryFileResponse { + ): StreamedResponse|RedirectResponse|Response { // Get file $file = File::where('user_id', $uploadRequest->user_id) ->where('basename', $filename) @@ -32,11 +31,11 @@ class GetFileFromUploadRequestController // Store user download size ($this->recordDownload)( - file_size: (int) $file->getRawOriginal('filesize'), + file_size: $file->filesize, user_id: $uploadRequest->user_id, ); // Finally, download file - return ($this->downloadFile)($file, $uploadRequest->user_id); + return ($this->downloadFile)($file); } } diff --git a/src/Domain/UploadRequest/Controllers/FileAccess/GetThumbnailFromUploadRequestController.php b/src/Domain/UploadRequest/Controllers/FileAccess/GetThumbnailFromUploadRequestController.php index b616b862..ca349724 100644 --- a/src/Domain/UploadRequest/Controllers/FileAccess/GetThumbnailFromUploadRequestController.php +++ b/src/Domain/UploadRequest/Controllers/FileAccess/GetThumbnailFromUploadRequestController.php @@ -33,7 +33,7 @@ class GetThumbnailFromUploadRequestController extends Controller // Store user download size ($this->recordDownload)( - file_size: (int) $file->getRawOriginal('filesize'), + file_size: $file->filesize, user_id: $uploadRequest->user_id, ); diff --git a/src/Domain/Zip/Actions/ZipAction.php b/src/Domain/Zip/Actions/ZipAction.php index b489174b..900bbec2 100644 --- a/src/Domain/Zip/Actions/ZipAction.php +++ b/src/Domain/Zip/Actions/ZipAction.php @@ -38,12 +38,12 @@ class ZipAction // Add file into zip if (Storage::exists($filePath)) { // local disk - if (is_storage_driver('local')) { + if (isStorageDriver('local')) { $zip->add(Storage::path($filePath), $file->name); } // s3 client - if (is_storage_driver('s3')) { + if (isStorageDriver('s3')) { $bucketName = config('filesystems.disks.s3.bucket'); $zip->add("s3://$bucketName/$filePath", $file->name); @@ -75,12 +75,12 @@ class ZipAction $zipDestination = "{$file['folder_path']}/{$file['name']}"; // local disk - if (is_storage_driver('local')) { + if (isStorageDriver('local')) { $zip->add(Storage::path($filePath), $zipDestination); } // s3 client - if (is_storage_driver('s3')) { + if (isStorageDriver('s3')) { $bucketName = config('filesystems.disks.s3.bucket'); $zip->add("s3://$bucketName/$filePath", $zipDestination); diff --git a/src/Support/errors.php b/src/Support/errors.php new file mode 100644 index 00000000..fd61cac1 --- /dev/null +++ b/src/Support/errors.php @@ -0,0 +1,15 @@ + 'error', + 'message' => 'Access Denied', + ]; +} + +function userActionNotAllowedError(): array { + return [ + 'type' => 'error', + 'message' => 'This user action is not allowed.', + ]; +} \ No newline at end of file diff --git a/src/Support/helpers.php b/src/Support/helpers.php index 267e7e83..9bcddb43 100644 --- a/src/Support/helpers.php +++ b/src/Support/helpers.php @@ -269,13 +269,13 @@ if (! function_exists('get_storage')) { } } -if (! function_exists('is_storage_driver')) { +if (! function_exists('isStorageDriver')) { /** * Check if is running AWS s3 as storage * * @return bool */ - function is_storage_driver($driver) + function isStorageDriver($driver) { if (is_array($driver)) { return in_array(config('filesystems.default'), $driver); @@ -722,7 +722,7 @@ if (! function_exists('get_file_type_from_mimetype')) { } } -if (! function_exists('get_pretty_name')) { +if (! function_exists('getPrettyName')) { /** * Format pretty name file * @@ -731,11 +731,11 @@ if (! function_exists('get_pretty_name')) { * @param $mimetype * @return string */ - function get_pretty_name($basename, $name, $mimetype) + function getPrettyName($basename, $name, $mimetype): string { $file_extension = substr(strrchr($basename, '.'), 1); - if (strpos($name, $file_extension) !== false) { + if (str_contains($name, $file_extension)) { return $name; } diff --git a/tests/Domain/Admin/DashboardTest.php b/tests/Domain/Admin/DashboardTest.php index 81b1083c..9f2b972d 100644 --- a/tests/Domain/Admin/DashboardTest.php +++ b/tests/Domain/Admin/DashboardTest.php @@ -1,4 +1,5 @@ getJson('/api/admin/dashboard') ->assertStatus(200) ->assertJsonFragment([ - 'app' => [ - 'earnings' => '$0.00', - 'isRunningCron' => false, - 'license' => 'extended', - 'version' => config('vuefilemanager.version'), + 'app' => [ + 'shouldUpgradeTranslations' => true, + 'earnings' => '$0.00', + 'isRunningCron' => false, + 'license' => 'extended', + 'version' => config('vuefilemanager.version'), ], 'users' => [ 'total' => 1, 'usersPremiumTotal' => 0, ], - 'used' => '2.00MB', + 'used' => '2.00MB', ]); } @@ -53,8 +55,7 @@ class DashboardTest extends TestCase ->create(['role' => 'admin']); $users->each( - fn ($user) => - $this + fn($user) => $this ->actingAs($admin) ->getJson('/api/admin/dashboard/newbies') ->assertStatus(200) diff --git a/tests/Domain/Files/ContentAccessTest.php b/tests/Domain/Files/ContentAccessTest.php index 99d8d2c8..94b9725a 100644 --- a/tests/Domain/Files/ContentAccessTest.php +++ b/tests/Domain/Files/ContentAccessTest.php @@ -61,13 +61,13 @@ class ContentAccessTest extends TestCase ->create([ 'user_id' => $user->id, 'basename' => $file->name, - 'name' => 'fake-file.pdf', + 'name' => $file->name, ]); $this ->actingAs($user) ->get("file/$file->name") - ->assertOk(); + ->assertDownload($file->name); } /** @@ -93,7 +93,7 @@ class ContentAccessTest extends TestCase $this ->actingAs($user) ->get("thumbnail/xs-$thumbnail->name") - ->assertStatus(200); + ->assertDownload("xs-$thumbnail->name"); } /** diff --git a/tests/Domain/Sharing/VisitorAccessToItemsTest.php b/tests/Domain/Sharing/VisitorAccessToItemsTest.php index 5e739ed4..b4138fc8 100644 --- a/tests/Domain/Sharing/VisitorAccessToItemsTest.php +++ b/tests/Domain/Sharing/VisitorAccessToItemsTest.php @@ -49,14 +49,16 @@ class VisitorAccessToItemsTest extends TestCase ])]; $this->withCookies($cookie) - ->get("/file/$document->name/$share->token") - ->assertStatus(200); + ->get("/file/$document->name/shared/$share->token") + ->assertStatus(200) + ->assertDownload($document->name); } if (! $is_protected) { // Get shared file - $this->get("/file/$document->name/$share->token") - ->assertStatus(200); + $this->get("/file/$document->name/shared/$share->token") + ->assertStatus(200) + ->assertDownload($document->name); } }); } @@ -236,13 +238,13 @@ class VisitorAccessToItemsTest extends TestCase ]; $this->withCookies($cookie) - ->get("/thumbnail/xs-$fileName/$share->token") - ->assertStatus(200); + ->get("/thumbnail/xs-$fileName/shared/$share->token") + ->assertDownload("xs-$fileName"); } if (! $is_protected) { - $this->get("/thumbnail/xs-$fileName/$share->token") - ->assertStatus(200); + $this->get("/thumbnail/xs-$fileName/shared/$share->token") + ->assertDownload("xs-$fileName"); } $this->assertDatabaseMissing('traffic', [ diff --git a/tests/Domain/Traffic/TrafficTest.php b/tests/Domain/Traffic/TrafficTest.php index cebcb9a0..f5e4df3f 100644 --- a/tests/Domain/Traffic/TrafficTest.php +++ b/tests/Domain/Traffic/TrafficTest.php @@ -186,7 +186,8 @@ class TrafficTest extends TestCase ]); $this->get("/file/$document->name/shared/$share->token") - ->assertStatus(200); + ->assertStatus(200) + ->assertDownload($document->name); $this->assertDatabaseHas('traffic', [ 'user_id' => $this->user->id, diff --git a/tests/Domain/UploadRequest/UploadRequestAccessTest.php b/tests/Domain/UploadRequest/UploadRequestAccessTest.php index 38e6b631..56b18523 100644 --- a/tests/Domain/UploadRequest/UploadRequestAccessTest.php +++ b/tests/Domain/UploadRequest/UploadRequestAccessTest.php @@ -37,12 +37,12 @@ class UploadRequestAccessTest extends TestCase 'parent_id' => $uploadRequest->id, 'basename' => $file->name, 'user_id' => $user->id, - 'name' => 'fake-file.pdf', + 'name' => $file->name, ]); $this ->get("/file/$file->name/upload-request/$uploadRequest->id") - ->assertOk(); + ->assertDownload($file->name); } /** @@ -75,7 +75,7 @@ class UploadRequestAccessTest extends TestCase $this ->get("/thumbnail/xs-$thumbnail->name/upload-request/$uploadRequest->id") - ->assertOk(); + ->assertDownload("xs-$thumbnail->name"); } /**