From e6056104bd4ab7c8d6f76bbd85a390326b1bdd9e Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Wed, 23 Mar 2016 22:41:19 -0400 Subject: [PATCH] Display documents and zip download on client invoice page --- .../Controllers/PublicClientController.php | 105 +++++++++++++++++- app/Http/routes.php | 2 + app/Ninja/Repositories/DocumentRepository.php | 1 + composer.json | 3 +- composer.lock | 44 +++++++- .../2016_03_22_168362_add_documents.php | 1 + resources/lang/en/texts.php | 2 + resources/views/invoices/view.blade.php | 19 +++- 8 files changed, 169 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index 500257fedbbc..6c2ad8cd4960 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -19,6 +19,8 @@ use App\Ninja\Repositories\ActivityRepository; use App\Events\InvoiceInvitationWasViewed; use App\Events\QuoteInvitationWasViewed; use App\Services\PaymentService; +use League\Flysystem\Filesystem; +use League\Flysystem\ZipArchive\ZipArchiveAdapter; class PublicClientController extends BaseController { @@ -135,6 +137,10 @@ class PublicClientController extends BaseController 'checkoutComDebug' => $checkoutComDebug, 'phantomjs' => Input::has('phantomjs'), ); + + if($account->isPro() && $this->canCreateInvoiceDocsZip($invoice)){ + $data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}"); + } return View::make('invoices.view', $data); } @@ -375,9 +381,7 @@ class PublicClientController extends BaseController return $invitation; } - - - + public function getDocumentVFSJS($publicId, $name){ if (!$invitation = $this->getInvitation()) { return $this->returnError(); @@ -415,6 +419,101 @@ class PublicClientController extends BaseController return $response; } + protected function canCreateZip(){ + return function_exists('gmp_init'); + } + + protected function canCreateInvoiceDocsZip($invoice){ + if(!$this->canCreateZip())return false; + if(count($invoice->documents) == 1)return false; + + $maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000; + $i = 0; + foreach($invoice->documents as $document){ + if($document->size <= $maxSize)$i++; + if($i > 1){ + return true; + } + } + return false; + } + + public function getInvoiceDocumentsZip($invitationKey){ + if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { + return $this->returnError(); + } + + Session::put('invitation_key', $invitationKey); // track current invitation + + $invoice = $invitation->invoice; + + if(!count($invoice->documents)){ + return Response::view('error', array('error'=>'No documents'), 404); + } + + $documents = $invoice->documents->sortBy('size'); + + $size = 0; + $maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000; + $toZip = array(); + foreach($documents as $document){ + $size += $document->size; + if($size > $maxSize)break; + + if(!empty($toZip[$document->name])){ + $hasSameHash = false; + foreach($toZip[$document->name] as $sameName){ + if($sameName->hash == $document->hash){ + $hasSameHash = true; + break; + } + } + + if(!$hasSameHash){ + // 2 different files with the same name + $toZip[$document->name][] = $document; + } + else{ + // We're not adding this after all + $size -= $document->size; + } + } + else{ + $toZip[$document->name] = array($document); + } + } + + if(!count($toZip)){ + return Response::view('error', array('error'=>'No documents small enough'), 404); + } + + $zip = new \Barracuda\ArchiveStream\ZipArchive($invitation->account->name.' Invoice '.$invoice->invoice_number.'.zip'); + return Response::stream(function() use ($toZip, $zip) { + foreach($toZip as $documentsSameName){ + $i = 0; + foreach($documentsSameName as $document){ + $name = $document->name; + + if($i){ + $nameInfo = pathinfo($document->name); + $name = $nameInfo['filename'].' ('.$i.').'.$nameInfo['extension']; + } + + $fileStream = $document->getStream(); + if($fileStream){ + $zip->init_file_stream_transfer($name, $document->size); + while ($buffer = fread($fileStream, 8192))$zip->stream_file_part($buffer); + fclose($fileStream); + $zip->complete_file_stream(); + } + else{ + $zip->add_file($name, $document->getRaw()); + } + } + } + $zip->finish(); + }, 200); + } public function getDocument($invitationKey, $publicId){ if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { diff --git a/app/Http/routes.php b/app/Http/routes.php index a7f8d7b26e9e..0aaec6a1f925 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -49,6 +49,7 @@ Route::group(['middleware' => 'auth:client'], function() { Route::get('client/dashboard', 'PublicClientController@dashboard'); Route::get('client/document/js/{public_id}/{filename}', 'PublicClientController@getDocumentVFSJS'); Route::get('client/document/{invitation_key}/{public_id}/{filename?}', 'PublicClientController@getDocument'); + Route::get('client/documents/{invitation_key}/{filename?}', 'PublicClientController@getInvoiceDocumentsZip'); }); Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable')); @@ -434,6 +435,7 @@ if (!defined('CONTACT_EMAIL')) { define('MAX_FAILED_LOGINS', 10); define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB define('MAX_EMAIL_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 10000));// Total KB + define('MAX_ZIP_DOCUMENTS_SIZE', env('MAX_EMAIL_DOCUMENTS_SIZE', 30000));// Total KB (uncompressed) define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300));// pixels define('DEFAULT_FONT_SIZE', 9); define('DEFAULT_HEADER_FONT', 1);// Roboto diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index 38d6af6ac1ba..2ce58720de03 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -158,6 +158,7 @@ class DocumentRepository extends BaseRepository $document->path = $filename; $document->type = $documentType; $document->size = $size; + $document->hash = $hash; $document->name = substr($name, -255); if(!empty($imageSize)){ diff --git a/composer.json b/composer.json index 9a52431d4511..3ac3e3ba0e89 100644 --- a/composer.json +++ b/composer.json @@ -68,7 +68,8 @@ "cerdic/css-tidy": "~v1.5", "asgrim/ofxparser": "^1.1", "league/flysystem-aws-s3-v3": "~1.0", - "league/flysystem-rackspace": "~1.0" + "league/flysystem-rackspace": "~1.0", + "barracudanetworks/archivestream-php": "^1.0" }, "require-dev": { "phpunit/phpunit": "~4.0", diff --git a/composer.lock b/composer.lock index 74b6ceefecbc..c3265f7a3d26 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "aa7451471a31552d5d6dce491ec20272", - "content-hash": "d70446a01a12bb41eece33b979adc975", + "hash": "5b6b0afc0a26fa2b1dc48241fd665c1e", + "content-hash": "33f39a8d05247b96374ed20e95499936", "packages": [ { "name": "agmscode/omnipay-agms", @@ -401,6 +401,46 @@ ], "time": "2016-03-22 19:19:22" }, + { + "name": "barracudanetworks/archivestream-php", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/barracudanetworks/ArchiveStream-php.git", + "reference": "9a81c7de7f0cd5ea2150fc3dc00f1c43178362b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barracudanetworks/ArchiveStream-php/zipball/9a81c7de7f0cd5ea2150fc3dc00f1c43178362b6", + "reference": "9a81c7de7f0cd5ea2150fc3dc00f1c43178362b6", + "shasum": "" + }, + "require": { + "ext-gmp": "*", + "ext-mbstring": "*", + "php": ">=5.1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Barracuda\\ArchiveStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A library for dynamically streaming dynamic tar or zip files without the need to have the complete file stored on the server.", + "homepage": "https://github.com/barracudanetworks/ArchiveStream-php", + "keywords": [ + "archive", + "php", + "stream", + "tar", + "zip" + ], + "time": "2016-01-07 06:02:26" + }, { "name": "barryvdh/laravel-debugbar", "version": "v2.2.0", diff --git a/database/migrations/2016_03_22_168362_add_documents.php b/database/migrations/2016_03_22_168362_add_documents.php index bf4ca8c9b771..b57e72da2079 100644 --- a/database/migrations/2016_03_22_168362_add_documents.php +++ b/database/migrations/2016_03_22_168362_add_documents.php @@ -34,6 +34,7 @@ class AddDocuments extends Migration { $t->string('name'); $t->string('type'); $t->string('disk'); + $t->string('hash', 40); $t->unsignedInteger('size'); $t->unsignedInteger('width')->nullable(); $t->unsignedInteger('height')->nullable(); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 50b8b66b3cc9..7eec8138a503 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1098,6 +1098,7 @@ $LANG = array( 'december' => 'December', // Documents + 'documents_header' => 'Documents:', 'email_documents_header' => 'Documents:', 'email_documents_example_1' => 'Widgets Receipt.pdf', 'email_documents_example_2' => 'Final Deliverable.zip', @@ -1106,6 +1107,7 @@ $LANG = array( 'invoice_embed_documents' => 'Embed Documents', 'invoice_embed_documents_help' => 'Include attached images in the invoice.', 'document_email_attachment' => 'Attach Documents', + 'download_documents' => 'Download Documents', ); return $LANG; diff --git a/resources/views/invoices/view.blade.php b/resources/views/invoices/view.blade.php index 2ad564f9fdd7..16479df68444 100644 --- a/resources/views/invoices/view.blade.php +++ b/resources/views/invoices/view.blade.php @@ -24,7 +24,6 @@ @if ($checkoutComToken) @include('partials.checkout_com_payment') @else -

 

@if ($invoice->is_quote) {!! Button::normal(trans('texts.download_pdf'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}   @@ -39,12 +38,28 @@ {{ trans('texts.pay_now') }} @endif @else - {!! Button::normal('Download PDF')->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!} + {!! Button::normal(trans('texts.download_pdf'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!} @endif
+
+ @if(!empty($documentsZipURL)) + {!! Button::normal(trans('texts.download_documents'))->asLinkTo($documentsZipURL)->large() !!} + @endif +
@endif

 

+ @if ($account->isPro() && count($invoice->documents)) +
+

{{ trans('texts.documents_header') }}

+ +
+ @endif + @if ($account->isPro() && $account->invoice_embed_documents) @foreach ($invoice->documents as $document)