diff --git a/app/Http/Controllers/ClientPortal/DownloadController.php b/app/Http/Controllers/ClientPortal/DownloadController.php index 0f9750f24e59..b338e6436b63 100644 --- a/app/Http/Controllers/ClientPortal/DownloadController.php +++ b/app/Http/Controllers/ClientPortal/DownloadController.php @@ -13,10 +13,14 @@ namespace App\Http\Controllers\ClientPortal; use App\Http\Controllers\Controller; +use App\Http\Requests\Document\DownloadMultipleDocumentsRequest; use App\Http\Requests\Document\ShowDocumentRequest; use App\Models\Document; +use App\Utils\TempFile; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Storage; +use ZipStream\Option\Archive; +use ZipStream\ZipStream; class DownloadController extends Controller { @@ -43,10 +47,30 @@ class DownloadController extends Controller /** * @param \App\Http\Requests\Document\ShowDocumentRequest $request * @param \App\Models\Document $download - * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + * @param bool $bulk + * @return mixed */ public function download(ShowDocumentRequest $request, Document $download) { return Storage::disk($download->disk)->download($download->url, $download->name); } + + public function downloadMultiple(DownloadMultipleDocumentsRequest $request) + { + $documents = Document::whereIn('id', $this->transformKeys($request->file_hash)) + ->where('company_id', auth('contact')->user()->company->id) + ->get(); + + $options = new Archive(); + + $options->setSendHttpHeaders(true); + + $zip = new ZipStream('files.zip', $options); + + foreach ($documents as $document) { + $zip->addFileFromPath(basename($document->filePath()), TempFile::path($document->filePath())); + } + + $zip->finish(); + } } diff --git a/app/Http/Requests/Document/DownloadMultipleDocumentsRequest.php b/app/Http/Requests/Document/DownloadMultipleDocumentsRequest.php new file mode 100644 index 000000000000..d29f720d3243 --- /dev/null +++ b/app/Http/Requests/Document/DownloadMultipleDocumentsRequest.php @@ -0,0 +1,31 @@ +user()->can('view', $this->document); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'file_hash' => ['required'], + ]; + } +} diff --git a/app/Http/Requests/Document/ShowDocumentRequest.php b/app/Http/Requests/Document/ShowDocumentRequest.php index 333987632439..5be9f7b248f9 100644 --- a/app/Http/Requests/Document/ShowDocumentRequest.php +++ b/app/Http/Requests/Document/ShowDocumentRequest.php @@ -22,8 +22,7 @@ class ShowDocumentRequest extends Request */ public function authorize() : bool { - return true; - // return auth()->user()->can('view', $this->document); + return auth()->user()->can('view', $this->document); } /** diff --git a/app/Models/Document.php b/app/Models/Document.php index b0281b95e7ff..d662ff5527d4 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -123,4 +123,9 @@ class Document extends BaseModel { Storage::disk($this->disk)->delete($this->url); } + + public function filePath() + { + return Storage::disk($this->disk)->url($this->url); + } } diff --git a/public/js/clients/shared/multiple-downloads.js b/public/js/clients/shared/multiple-downloads.js new file mode 100644 index 000000000000..8b2523d563b8 --- /dev/null +++ b/public/js/clients/shared/multiple-downloads.js @@ -0,0 +1 @@ +!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=16)}({16:function(e,t,n){e.exports=n("W54t")},W54t:function(e,t){window.appendToElement=function(e,t){var n=document.getElementById(e),r=n.querySelector('input[value="'.concat(t,'"]'));if(r)return r.remove();var o=document.createElement("INPUT");o.setAttribute("name","file_hash[]"),o.setAttribute("value",t),o.hidden=!0,n.append(o)}}}); \ No newline at end of file diff --git a/public/mix-manifest.json b/public/mix-manifest.json index a161c0314ecb..745ab7c86beb 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -14,6 +14,7 @@ "/js/clients/payments/sofort.js": "/js/clients/payments/sofort.js?id=ff4ad07a93bd9fb327c1", "/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=b6b33ab51b58b51e1212", "/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=1c5d76fb5f98bd49f6c8", + "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=bf87649ca30c9a3fba59", "/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=86f3b8d82f809268b3de", "/js/setup/setup.js": "/js/setup/setup.js?id=c4cd098778bf824a3470", "/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad" diff --git a/resources/js/clients/shared/multiple-downloads.js b/resources/js/clients/shared/multiple-downloads.js new file mode 100644 index 000000000000..9ce39ba0fd0c --- /dev/null +++ b/resources/js/clients/shared/multiple-downloads.js @@ -0,0 +1,29 @@ +/** + * Invoice Ninja (https://invoiceninja.com) + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://opensource.org/licenses/AAL + */ + +const appendToElement = (parent, value) => { + let _parent = document.getElementById(parent); + + let _possibleElement = _parent.querySelector(`input[value="${value}"]`); + + if (_possibleElement) { + return _possibleElement.remove(); + } + + let _temp = document.createElement('INPUT'); + + _temp.setAttribute('name', 'file_hash[]'); + _temp.setAttribute('value', value); + _temp.hidden = true; + + _parent.append(_temp); +}; + +window.appendToElement = appendToElement; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 8f8229176b84..1f3f6f3d6377 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -3261,5 +3261,7 @@ return [ 'allowed_file_types' => 'Allowed file types:', 'common_codes' => 'Common codes and their meanings', - 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', + 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', + + 'download_selected' => 'Download selected', ]; diff --git a/resources/views/portal/ninja2020/components/livewire/downloads-table.blade.php b/resources/views/portal/ninja2020/components/livewire/downloads-table.blade.php index 19fdb42bd87a..0bc5d5e2e693 100644 --- a/resources/views/portal/ninja2020/components/livewire/downloads-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/downloads-table.blade.php @@ -8,6 +8,12 @@ +
{{ ctrans('texts.name') }}
@@ -54,6 +61,9 @@
@forelse($downloads as $download)
+
+ |
{{ Illuminate\Support\Str::limit($download->name, 20) }}
|
diff --git a/resources/views/portal/ninja2020/downloads/index.blade.php b/resources/views/portal/ninja2020/downloads/index.blade.php
index e6b30d013a3b..64eafeac5cc7 100644
--- a/resources/views/portal/ninja2020/downloads/index.blade.php
+++ b/resources/views/portal/ninja2020/downloads/index.blade.php
@@ -5,8 +5,13 @@
@if($client->getSetting('client_portal_enable_uploads'))
@component('portal.ninja2020.upload.index') @endcomponent
@endif
+
+
@endsection
@section('body')
+
@livewire('downloads-table')
@endsection
\ No newline at end of file
diff --git a/routes/client.php b/routes/client.php
index 60a07ebc1258..b1a32d5d4ed9 100644
--- a/routes/client.php
+++ b/routes/client.php
@@ -60,6 +60,7 @@ Route::group(['middleware' => ['auth:contact','locale'], 'prefix' => 'client', '
Route::get('client/switch_company/{contact}', 'ClientPortal\SwitchCompanyController')->name('switch_company');
+ Route::post('downloads/multiple', 'ClientPortal\DownloadController@downloadMultiple')->name('downloads.multiple');
Route::get('downloads/{download}/download', 'ClientPortal\DownloadController@download')->name('downloads.download');
Route::resource('downloads', 'ClientPortal\DownloadController')->only(['index', 'show']);
diff --git a/webpack.mix.js b/webpack.mix.js
index 1b76293184c0..5edb7af9873c 100644
--- a/webpack.mix.js
+++ b/webpack.mix.js
@@ -61,6 +61,10 @@ mix.js("resources/js/app.js", "public/js")
.js(
"resources/js/clients/shared/pdf.js",
"public/js/clients/shared/pdf.js"
+ )
+ .js(
+ "resources/js/clients/shared/multiple-downloads.js",
+ "public/js/clients/shared/multiple-downloads.js"
);
mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css');
|
---|