diff --git a/app/Console/Commands/DemoMode.php b/app/Console/Commands/DemoMode.php index d56ded08af4f..35f5bf28ae5c 100644 --- a/app/Console/Commands/DemoMode.php +++ b/app/Console/Commands/DemoMode.php @@ -70,6 +70,31 @@ class DemoMode extends Command { set_time_limit(0); + $cached_tables = config('ninja.cached_tables'); + + foreach ($cached_tables as $name => $class) { + if (! Cache::has($name)) { + // check that the table exists in case the migration is pending + if (! Schema::hasTable((new $class())->getTable())) { + continue; + } + if ($name == 'payment_terms') { + $orderBy = 'num_days'; + } elseif ($name == 'fonts') { + $orderBy = 'sort_order'; + } elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) { + $orderBy = 'name'; + } else { + $orderBy = 'id'; + } + $tableData = $class::orderBy($orderBy)->get(); + if ($tableData->count()) { + Cache::forever($name, $tableData); + } + } + } + + $this->info("Migrating"); Artisan::call('migrate:fresh --force'); @@ -257,7 +282,7 @@ class DemoMode extends Command 'company_id' => $company->id ]); - factory(\App\Models\ClientContact::class, 1)->create([ + factory(\App\Models\ClientContact::class)->create([ 'user_id' => $user->id, 'client_id' => $client->id, 'company_id' => $company->id, @@ -301,7 +326,7 @@ class DemoMode extends Command ]); - factory(\App\Models\VendorContact::class, 1)->create([ + factory(\App\Models\VendorContact::class)->create([ 'user_id' => $client->user->id, 'vendor_id' => $vendor->id, 'company_id' => $client->company_id, diff --git a/app/DataMapper/EmailTemplateDefaults.php b/app/DataMapper/EmailTemplateDefaults.php index 9cc5bfa8302a..abbb7c5f5a67 100644 --- a/app/DataMapper/EmailTemplateDefaults.php +++ b/app/DataMapper/EmailTemplateDefaults.php @@ -151,7 +151,14 @@ class EmailTemplateDefaults public static function emailPaymentTemplate() { - return Parsedown::instance()->line(self::transformText('payment_message')); + $converter = new CommonMarkConverter([ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, + ]); + + return $converter->convertToHtml(self::transformText('payment_message')); + + // return Parsedown::instance()->line(self::transformText('payment_message')); } public static function emailReminder1Subject() 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/Controllers/ClientPortal/UploadController.php b/app/Http/Controllers/ClientPortal/UploadController.php index 78bb9b820173..e736b7a03d98 100644 --- a/app/Http/Controllers/ClientPortal/UploadController.php +++ b/app/Http/Controllers/ClientPortal/UploadController.php @@ -1,5 +1,15 @@ 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/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 5ed126eee41d..cc64be253402 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -122,8 +122,8 @@ class HtmlEngine $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; $data['$entity.terms'] = ['value' => $this->entity->terms ?: ' ', 'label' => ctrans('texts.invoice_terms')]; $data['$terms'] = &$data['$entity.terms']; - // $data['$view_link'] = ['value' => ''. ctrans('texts.view_invoice').'', 'label' => ctrans('texts.view_invoice')]; - $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; + $data['$view_link'] = ['value' => ''. ctrans('texts.view_invoice').'', 'label' => ctrans('texts.view_invoice')]; + // $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; } @@ -132,8 +132,8 @@ class HtmlEngine $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.quote_number')]; $data['$entity.terms'] = ['value' => $this->entity->terms ?: ' ', 'label' => ctrans('texts.quote_terms')]; $data['$terms'] = &$data['$entity.terms']; - // $data['$view_link'] = ['value' => ''. ctrans('texts.view_quote').'', 'label' => ctrans('texts.view_quote')]; - $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')]; + $data['$view_link'] = ['value' => ''. ctrans('texts.view_quote').'', 'label' => ctrans('texts.view_quote')]; + // $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')]; } if ($this->entity_string == 'credit') { @@ -141,8 +141,8 @@ class HtmlEngine $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.credit_number')]; $data['$entity.terms'] = ['value' => $this->entity->terms ?: ' ', 'label' => ctrans('texts.credit_terms')]; $data['$terms'] = &$data['$entity.terms']; - // $data['$view_link'] = ['value' => ''. ctrans('texts.view_credit').'', 'label' => ctrans('texts.view_credit')]; - $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')]; + $data['$view_link'] = ['value' => ''. ctrans('texts.view_credit').'', 'label' => ctrans('texts.view_credit')]; + // $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')]; } $data['$entity_number'] = &$data['$number']; diff --git a/database/migrations/2020_08_18_140557_add_is_public_to_documents_table.php b/database/migrations/2020_08_18_140557_add_is_public_to_documents_table.php index d79529118266..7c92ec677d42 100644 --- a/database/migrations/2020_08_18_140557_add_is_public_to_documents_table.php +++ b/database/migrations/2020_08_18_140557_add_is_public_to_documents_table.php @@ -24,6 +24,7 @@ class AddIsPublicToDocumentsTable extends Migration Schema::table('company_gateways', function (Blueprint $table) { $table->enum('token_billing', ['off', 'always','optin','optout'])->default('off'); + $table->string('label', 255)->nullable(); }); } 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 @@ +
@@ -25,6 +31,7 @@ + @forelse($downloads as $download) + 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') + + @csrf + @livewire('downloads-table') @endsection \ No newline at end of file diff --git a/resources/views/portal/ninja2020/upload/index.blade.php b/resources/views/portal/ninja2020/upload/index.blade.php index 5a3543b54db5..39a7154c46f1 100644 --- a/resources/views/portal/ninja2020/upload/index.blade.php +++ b/resources/views/portal/ninja2020/upload/index.blade.php @@ -2,7 +2,7 @@
- {{ ctrans('texts.allowed_file_types' )}} png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx + {{ ctrans('texts.allowed_file_types' )}} png, ai, svg, jpeg, tiff, pdf, gif, psd, txt, doc, xls, ppt, xlsx, docx, pptx
@csrf
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');
{{ ctrans('texts.name') }} @@ -54,6 +61,9 @@
+ + {{ Illuminate\Support\Str::limit($download->name, 20) }}