From 40ce28c433c37d623d871e70f348e91af6b76790 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 21 Mar 2016 13:42:00 +0200 Subject: [PATCH 001/122] Made dropdown shadows consistent --- public/css/built.css | 8 ++++++++ public/css/style.css | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/public/css/built.css b/public/css/built.css index c932978d31..0ed044f4a2 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -2529,6 +2529,14 @@ font-weight: bold; filter: none; } +.navbar, +ul.dropdown-menu, +.twitter-typeahead .tt-menu { + -moz-box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); + -webkit-box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); + box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); +} + .navbar .active > a { background-color: #09334f !important; background-image: none; diff --git a/public/css/style.css b/public/css/style.css index 7480cbe0f2..92318417d6 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -402,6 +402,14 @@ font-weight: bold; filter: none; } +.navbar, +ul.dropdown-menu, +.twitter-typeahead .tt-menu { + -moz-box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); + -webkit-box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); + box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); +} + .navbar .active > a { background-color: #09334f !important; background-image: none; From 56ad438dd34e23323ae83b8686d1e144d16ab80a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 21 Mar 2016 16:06:50 +0200 Subject: [PATCH 002/122] Add payment details to invoice report --- app/Http/Controllers/ReportController.php | 32 ++++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 1c4c033b9e..f1feb65b4b 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -354,8 +354,8 @@ class ReportController extends BaseController private function generateInvoiceReport($startDate, $endDate, $isExport) { - $columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance']; - + $columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method']; + $account = Auth::user()->account; $displayData = []; $reportTotals = []; @@ -379,19 +379,25 @@ class ReportController extends BaseController }]); foreach ($clients->get() as $client) { - $currencyId = $client->currency_id ?: Auth::user()->account->getCurrencyId(); + foreach ($client->invoices as $invoice) { + + $payments = count($invoice->payments) ? $invoice->payments : [false]; + foreach ($payments as $payment) { + $displayData[] = [ + $isExport ? $client->getDisplayName() : $client->present()->link, + $isExport ? $invoice->invoice_number : $invoice->present()->link, + $invoice->present()->invoice_date, + $account->formatMoney($invoice->amount, $client), + $payment ? $payment->present()->payment_date : '', + $payment ? $account->formatMoney($payment->amount, $client) : '', + $payment ? $payment->present()->method : '', + ]; + if ($payment) { + $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount); + } + } - foreach ($client->invoices as $invoice) { - $displayData[] = [ - $isExport ? $client->getDisplayName() : $client->present()->link, - $isExport ? $invoice->invoice_number : $invoice->present()->link, - $invoice->present()->invoice_date, - $account->formatMoney($invoice->amount, $client), - $account->formatMoney($invoice->getAmountPaid(), $client), - $account->formatMoney($invoice->balance, $client), - ]; $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); - $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $invoice->getAmountPaid()); $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'balance', $invoice->balance); } } From c9e0119bc2a0843aaece20ddbd2fa2cd51ad9ec8 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 21 Mar 2016 22:00:59 +0200 Subject: [PATCH 003/122] Added permissions settings help --- app/Http/Controllers/UserController.php | 2 -- resources/lang/en/texts.php | 7 ++++++- resources/views/users/edit.blade.php | 22 ++++++++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 0e5a8dd70d..2e3f675aa5 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -77,7 +77,6 @@ class UserController extends BaseController 'user' => $user, 'method' => 'PUT', 'url' => 'users/'.$publicId, - 'title' => trans('texts.edit_user'), ]; return View::make('users.edit', $data); @@ -120,7 +119,6 @@ class UserController extends BaseController 'user' => null, 'method' => 'POST', 'url' => 'users', - 'title' => trans('texts.add_user'), ]; return View::make('users.edit', $data); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 6d0f963d28..02fe26bece 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1068,7 +1068,7 @@ $LANG = array( // User Permissions 'owner' => 'Owner', 'administrator' => 'Administrator', - 'administrator_help' => 'Allow user to manage users, change settings, and view and modify all data', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', 'user_create_all' => 'Create clients, invoices, etc.', 'user_view_all' => 'View all clients, invoices, etc.', 'user_edit_all' => 'Edit all clients, invoices, etc.', @@ -1078,6 +1078,11 @@ $LANG = array( 'restore_vendor' => 'Restore Vendor', 'restored_vendor' => 'Successfully restored vendor', 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + ); diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 542d40b737..5cdbeac2d8 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -16,13 +16,24 @@
-

{!! $title !!}

+

{!! trans('texts.user_details') !!}

{!! Former::text('first_name') !!} {!! Former::text('last_name') !!} {!! Former::text('email') !!} + +
+
+ +
+
+

{!! trans('texts.permissions') !!}

+
+
+ + {!! Former::checkbox('is_admin') ->label(' ') ->text(trans('texts.administrator')) @@ -31,17 +42,20 @@ ->value('create_all') ->label(' ') ->id('permissions_create_all') - ->text(trans('texts.user_create_all')) !!} + ->text(trans('texts.user_create_all')) + ->help(trans('texts.create_all_help')) !!} {!! Former::checkbox('permissions[view_all]') ->value('view_all') ->label(' ') ->id('permissions_view_all') - ->text(trans('texts.user_view_all')) !!} + ->text(trans('texts.user_view_all')) + ->help(trans('texts.view_all_help')) !!} {!! Former::checkbox('permissions[edit_all]') ->value('edit_all') ->label(' ') ->id('permissions_edit_all') - ->text(trans('texts.user_edit_all')) !!} + ->text(trans('texts.user_edit_all')) + ->help(trans('texts.edit_all_help')) !!}
From c66e933de54187cbfd7d070787da6fb72de7037b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 21 Mar 2016 22:34:30 +0200 Subject: [PATCH 004/122] Minor improvements to client portal styling --- public/css/built.css | 6 +++--- public/css/built.public.css | 25 +++++++++++++++++-------- public/css/public.style.css | 25 +++++++++++++++++-------- public/css/style.css | 6 +++--- resources/views/public/header.blade.php | 2 +- resources/views/public_list.blade.php | 2 +- 6 files changed, 42 insertions(+), 24 deletions(-) diff --git a/public/css/built.css b/public/css/built.css index 0ed044f4a2..ee26ad4b7e 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -2532,9 +2532,9 @@ font-weight: bold; .navbar, ul.dropdown-menu, .twitter-typeahead .tt-menu { - -moz-box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); - -webkit-box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); - box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); } .navbar .active > a { diff --git a/public/css/built.public.css b/public/css/built.public.css index 1ba4afb311..2845cbabd3 100644 --- a/public/css/built.public.css +++ b/public/css/built.public.css @@ -790,14 +790,23 @@ html { overflow-y: scroll; } -@media screen and (min-width: 700px) { - .navbar-header { - padding-top: 16px; - padding-bottom: 16px; - } - .navbar li a { - padding: 31px 20px 31px 20px; - } + +.navbar-header { + padding-top: 4px; + padding-bottom: 4px; +} +.navbar li a { + padding-top: 18px; + font-weight: 500; + font-size: 15px; + padding-left: 20px; + padding-right: 20px; +} + +.navbar { + -moz-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); } #footer { diff --git a/public/css/public.style.css b/public/css/public.style.css index da5a469dec..b12c175294 100644 --- a/public/css/public.style.css +++ b/public/css/public.style.css @@ -7,14 +7,23 @@ html { overflow-y: scroll; } -@media screen and (min-width: 700px) { - .navbar-header { - padding-top: 16px; - padding-bottom: 16px; - } - .navbar li a { - padding: 31px 20px 31px 20px; - } + +.navbar-header { + padding-top: 4px; + padding-bottom: 4px; +} +.navbar li a { + padding-top: 18px; + font-weight: 500; + font-size: 15px; + padding-left: 20px; + padding-right: 20px; +} + +.navbar { + -moz-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); } #footer { diff --git a/public/css/style.css b/public/css/style.css index 92318417d6..204edf2e88 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -405,9 +405,9 @@ font-weight: bold; .navbar, ul.dropdown-menu, .twitter-typeahead .tt-menu { - -moz-box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); - -webkit-box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); - box-shadow: 5px 5px 20px -10px rgba(80, 80, 80, .82); + -moz-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + -webkit-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); } .navbar .active > a { diff --git a/resources/views/public/header.blade.php b/resources/views/public/header.blade.php index ebc8874b1d..929bda00e4 100644 --- a/resources/views/public/header.blade.php +++ b/resources/views/public/header.blade.php @@ -70,7 +70,7 @@ @if (!isset($hideLogo) || !$hideLogo) {{-- Per our license, please do not remove or modify this link. --}} - + @endif --> -

{{ $title }}

+

{{ $title }}

{!! Datatable::table() ->addColumn($columns) From 56b863a89a1f45e7385e088e1c890d67e62c33f1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 19:50:09 +1100 Subject: [PATCH 005/122] Working on Dashboard API --- app/Http/Controllers/DashboardApiController.php | 1 + app/Http/routes.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 5439efc287..2da50d6dfb 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -159,6 +159,7 @@ class DashboardApiController extends BaseAPIController } $data = [ + 'id' => 1, 'paidToDate' => $paidToDate, 'balances' => $balances, 'averageInvoice' => $averageInvoice, diff --git a/app/Http/routes.php b/app/Http/routes.php index dec31aa26c..e014579095 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -101,7 +101,6 @@ if (Utils::isReseller()) { Route::group(['middleware' => 'auth:user'], function() { Route::get('dashboard', 'DashboardController@index'); - Route::get('dashboard2', 'DashboardApiController@index'); Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('hide_message', 'HomeController@hideMessage'); Route::get('force_inline_pdf', 'UserController@forcePDFJS'); @@ -258,6 +257,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::resource('expenses','ExpenseApiController'); Route::post('add_token', 'AccountApiController@addDeviceToken'); Route::post('update_notifications', 'AccountApiController@updatePushNotifications'); + Route::get('dashboard', 'DashboardApiController@index'); // Vendor Route::resource('vendors', 'VendorApiController'); From 06d796a318feef16e01a3624a98bfadadc4ad5ee Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 20:43:08 +1100 Subject: [PATCH 006/122] Dashboard API --- app/Http/Controllers/DashboardApiController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 2da50d6dfb..bf4636e2b4 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -160,9 +160,9 @@ class DashboardApiController extends BaseAPIController $data = [ 'id' => 1, - 'paidToDate' => $paidToDate, - 'balances' => $balances, - 'averageInvoice' => $averageInvoice, + 'paidToDate' => $paidToDate['value'], + 'balances' => $balances['value'], + 'averageInvoice' => $averageInvoice['invoice_avg'], 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0, 'pastDue' => $pastDue, From 7bc703e5dd364e1001a32e1105c90d106dd25e1c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 20:50:11 +1100 Subject: [PATCH 007/122] Dashboard API --- app/Http/Controllers/DashboardApiController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index bf4636e2b4..b3fea79499 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -160,9 +160,9 @@ class DashboardApiController extends BaseAPIController $data = [ 'id' => 1, - 'paidToDate' => $paidToDate['value'], - 'balances' => $balances['value'], - 'averageInvoice' => $averageInvoice['invoice_avg'], + 'paidToDate' => $paidToDate[0]['value'], + 'balances' => $balances[0]['value'], + 'averageInvoice' => $averageInvoice[0]['invoice_avg'], 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0, 'pastDue' => $pastDue, From 431c9a899f2fe474d1a6fa4fec55807450d6c79d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 20:54:36 +1100 Subject: [PATCH 008/122] Dashboard API --- app/Http/Controllers/DashboardApiController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index b3fea79499..6d32163fc2 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -160,9 +160,9 @@ class DashboardApiController extends BaseAPIController $data = [ 'id' => 1, - 'paidToDate' => $paidToDate[0]['value'], - 'balances' => $balances[0]['value'], - 'averageInvoice' => $averageInvoice[0]['invoice_avg'], + 'paidToDate' => $paidToDate[0]->value, + 'balances' => $balances[0]->value, + 'averageInvoice' => $averageInvoice[0]->invoice_avg, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0, 'pastDue' => $pastDue, From b8560187f5c62cce5113b0c88b654fa3c3a205e3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 21:04:23 +1100 Subject: [PATCH 009/122] Dashboard API --- app/Http/Controllers/DashboardApiController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 6d32163fc2..8f0b23db6a 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -161,7 +161,9 @@ class DashboardApiController extends BaseAPIController $data = [ 'id' => 1, 'paidToDate' => $paidToDate[0]->value, + 'paidToDateCurrency' => $paidToDate[0]->currency_id, 'balances' => $balances[0]->value, + 'balancesCurrency' => $balances[0]->currency_id, 'averageInvoice' => $averageInvoice[0]->invoice_avg, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0, From 85633d346425f29e4d1b5ec35c35d572f6920d64 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 21:06:57 +1100 Subject: [PATCH 010/122] Dashboard API --- app/Http/Controllers/DashboardApiController.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 8f0b23db6a..3a40ad78dc 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -158,6 +158,7 @@ class DashboardApiController extends BaseAPIController } } + /* $data = [ 'id' => 1, 'paidToDate' => $paidToDate[0]->value, @@ -167,13 +168,23 @@ class DashboardApiController extends BaseAPIController 'averageInvoice' => $averageInvoice[0]->invoice_avg, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0, + ]; +*/ + $data = [ + 'account' => Auth::user()->account, + 'paidToDate' => $paidToDate, + 'balances' => $balances, + 'averageInvoice' => $averageInvoice, + 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, + 'activeClients' => $metrics ? $metrics->active_clients : 0, + 'activities' => $activities, 'pastDue' => $pastDue, 'upcoming' => $upcoming, 'payments' => $payments, 'title' => trans('texts.dashboard'), 'hasQuotes' => $hasQuotes, ]; - + return $this->response($data); } From dab023d898093c5622905548b818f9b594c5426c Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 12:08:48 +0200 Subject: [PATCH 011/122] Minor styling changes --- public/css/built.css | 8 +- public/css/built.public.css | 8 +- public/css/public.style.css | 8 +- public/css/style.css | 8 +- .../views/accounts/client_portal.blade.php | 80 ++++++++++--------- .../views/accounts/localization.blade.php | 37 +++++---- .../views/accounts/system_settings.blade.php | 27 ++++--- 7 files changed, 95 insertions(+), 81 deletions(-) diff --git a/public/css/built.css b/public/css/built.css index ee26ad4b7e..e98e86aefe 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -2530,11 +2530,13 @@ font-weight: bold; } .navbar, +.panel-default, ul.dropdown-menu, +canvas, .twitter-typeahead .tt-menu { - -moz-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); - -webkit-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); - box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + box-shadow: 0 0 1px 1px rgba(0,0,0,.05); } .navbar .active > a { diff --git a/public/css/built.public.css b/public/css/built.public.css index 2845cbabd3..cd2d746962 100644 --- a/public/css/built.public.css +++ b/public/css/built.public.css @@ -799,14 +799,16 @@ html { padding-top: 18px; font-weight: 500; font-size: 15px; + font-weight: bold; + color: #ecf0f1; padding-left: 20px; padding-right: 20px; } .navbar { - -moz-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); - -webkit-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); - box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + box-shadow: 0 0 1px 1px rgba(0,0,0,.05); } #footer { diff --git a/public/css/public.style.css b/public/css/public.style.css index b12c175294..bdac74f779 100644 --- a/public/css/public.style.css +++ b/public/css/public.style.css @@ -16,14 +16,16 @@ html { padding-top: 18px; font-weight: 500; font-size: 15px; + font-weight: bold; + color: #ecf0f1; padding-left: 20px; padding-right: 20px; } .navbar { - -moz-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); - -webkit-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); - box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + box-shadow: 0 0 1px 1px rgba(0,0,0,.05); } #footer { diff --git a/public/css/style.css b/public/css/style.css index 204edf2e88..7ddea57785 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -403,11 +403,13 @@ font-weight: bold; } .navbar, +.panel-default, ul.dropdown-menu, +canvas, .twitter-typeahead .tt-menu { - -moz-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); - -webkit-box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); - box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28); + x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + box-shadow: 0 0 1px 1px rgba(0,0,0,.05); } .navbar .active > a { diff --git a/resources/views/accounts/client_portal.blade.php b/resources/views/accounts/client_portal.blade.php index 745574ad72..8737c015d0 100644 --- a/resources/views/accounts/client_portal.blade.php +++ b/resources/views/accounts/client_portal.blade.php @@ -28,45 +28,47 @@ @include('accounts.nav', ['selected' => ACCOUNT_CLIENT_PORTAL])
-
-
-

{!! trans('texts.client_portal') !!}

-
-
-
- {!! Former::checkbox('enable_client_portal') - ->text(trans('texts.enable')) - ->help(trans('texts.enable_client_portal_help')) !!} -
-
- {!! Former::checkbox('enable_portal_password') - ->text(trans('texts.enable_portal_password')) - ->help(trans('texts.enable_portal_password_help')) - ->label(' ') !!} -
-
- {!! Former::checkbox('send_portal_password') - ->text(trans('texts.send_portal_password')) - ->help(trans('texts.send_portal_password_help')) - ->label(' ') !!} -
-
-
-
-
-

{!! trans('texts.custom_css') !!}

-
-
-
- {!! Former::textarea('client_view_css') - ->label(trans('texts.custom_css')) - ->rows(10) - ->raw() - ->autofocus() - ->maxlength(60000) - ->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!} -
-
+
+
+
+

{!! trans('texts.client_portal') !!}

+
+
+
+ {!! Former::checkbox('enable_client_portal') + ->text(trans('texts.enable')) + ->help(trans('texts.enable_client_portal_help')) !!} +
+
+ {!! Former::checkbox('enable_portal_password') + ->text(trans('texts.enable_portal_password')) + ->help(trans('texts.enable_portal_password_help')) + ->label(' ') !!} +
+
+ {!! Former::checkbox('send_portal_password') + ->text(trans('texts.send_portal_password')) + ->help(trans('texts.send_portal_password_help')) + ->label(' ') !!} +
+
+
+
+
+

{!! trans('texts.custom_css') !!}

+
+
+
+ {!! Former::textarea('client_view_css') + ->label(trans('texts.custom_css')) + ->rows(10) + ->raw() + ->autofocus() + ->maxlength(60000) + ->style("min-width:100%;max-width:100%;font-family:'Roboto Mono', 'Lucida Console', Monaco, monospace;font-size:14px;'") !!} +
+
+
diff --git a/resources/views/accounts/localization.blade.php b/resources/views/accounts/localization.blade.php index f2c28e3684..b921c87105 100644 --- a/resources/views/accounts/localization.blade.php +++ b/resources/views/accounts/localization.blade.php @@ -11,26 +11,29 @@ @include('accounts.nav', ['selected' => ACCOUNT_LOCALIZATION])
+ +
-
-
-

{!! trans('texts.localization') !!}

-
-
+
+
+

{!! trans('texts.localization') !!}

+
+
- {!! Former::select('currency_id')->addOption('','') - ->fromQuery($currencies, 'name', 'id') !!} - {!! Former::select('language_id')->addOption('','') - ->fromQuery($languages, 'name', 'id') !!} - {!! Former::select('timezone_id')->addOption('','') - ->fromQuery($timezones, 'location', 'id') !!} - {!! Former::select('date_format_id')->addOption('','') - ->fromQuery($dateFormats, 'label', 'id') !!} - {!! Former::select('datetime_format_id')->addOption('','') - ->fromQuery($datetimeFormats, 'label', 'id') !!} - {!! Former::checkbox('military_time')->text(trans('texts.enable')) !!} - {{-- Former::checkbox('show_currency_code')->text(trans('texts.enable')) --}} + {!! Former::select('currency_id')->addOption('','') + ->fromQuery($currencies, 'name', 'id') !!} + {!! Former::select('language_id')->addOption('','') + ->fromQuery($languages, 'name', 'id') !!} + {!! Former::select('timezone_id')->addOption('','') + ->fromQuery($timezones, 'location', 'id') !!} + {!! Former::select('date_format_id')->addOption('','') + ->fromQuery($dateFormats, 'label', 'id') !!} + {!! Former::select('datetime_format_id')->addOption('','') + ->fromQuery($datetimeFormats, 'label', 'id') !!} + {!! Former::checkbox('military_time')->text(trans('texts.enable')) !!} + {{-- Former::checkbox('show_currency_code')->text(trans('texts.enable')) --}} +
diff --git a/resources/views/accounts/system_settings.blade.php b/resources/views/accounts/system_settings.blade.php index 31be6a9d09..ce889364f9 100644 --- a/resources/views/accounts/system_settings.blade.php +++ b/resources/views/accounts/system_settings.blade.php @@ -6,22 +6,23 @@ @include('accounts.nav', ['selected' => ACCOUNT_SYSTEM_SETTINGS])
- - {!! Former::open('/update_setup') - ->addClass('warn-on-exit') - ->autocomplete('off') - ->rules([ - 'app[url]' => 'required', - //'database[default]' => 'required', - 'database[type][host]' => 'required', - 'database[type][database]' => 'required', - 'database[type][username]' => 'required', - 'database[type][password]' => 'required', - ]) !!} +
+ {!! Former::open('/update_setup') + ->addClass('warn-on-exit') + ->autocomplete('off') + ->rules([ + 'app[url]' => 'required', + //'database[default]' => 'required', + 'database[type][host]' => 'required', + 'database[type][database]' => 'required', + 'database[type][username]' => 'required', + 'database[type][password]' => 'required', + ]) !!} - @include('partials.system_settings') + @include('partials.system_settings') +
From 6e7c19b38edba31f9507274f377dceaa3542d533 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 21:08:58 +1100 Subject: [PATCH 012/122] Dashboard API --- app/Http/Controllers/DashboardApiController.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 3a40ad78dc..5f766a126c 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -171,20 +171,14 @@ class DashboardApiController extends BaseAPIController ]; */ $data = [ - 'account' => Auth::user()->account, + 'id' => 1, 'paidToDate' => $paidToDate, 'balances' => $balances, 'averageInvoice' => $averageInvoice, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0, - 'activities' => $activities, - 'pastDue' => $pastDue, - 'upcoming' => $upcoming, - 'payments' => $payments, - 'title' => trans('texts.dashboard'), - 'hasQuotes' => $hasQuotes, ]; - + return $this->response($data); } From 0ec1fcbd238816f3ffbe743e426d90df3afb1f74 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 21:13:24 +1100 Subject: [PATCH 013/122] Dashboard API --- .../Controllers/DashboardApiController.php | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 5f766a126c..3496105982 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -158,7 +158,7 @@ class DashboardApiController extends BaseAPIController } } - /* + $data = [ 'id' => 1, 'paidToDate' => $paidToDate[0]->value, @@ -166,19 +166,21 @@ class DashboardApiController extends BaseAPIController 'balances' => $balances[0]->value, 'balancesCurrency' => $balances[0]->currency_id, 'averageInvoice' => $averageInvoice[0]->invoice_avg, + 'averageInvoiceCurrency' => $averageInvoice[0]->currency_id, 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, 'activeClients' => $metrics ? $metrics->active_clients : 0, ]; -*/ - $data = [ - 'id' => 1, - 'paidToDate' => $paidToDate, - 'balances' => $balances, - 'averageInvoice' => $averageInvoice, - 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, - 'activeClients' => $metrics ? $metrics->active_clients : 0, - ]; - + + /* + $data = [ + 'id' => 1, + 'paidToDate' => $paidToDate, + 'balances' => $balances, + 'averageInvoice' => $averageInvoice, + 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, + 'activeClients' => $metrics ? $metrics->active_clients : 0, + ]; + */ return $this->response($data); } From d83a02f80fc2e968cd17b13d33f68b2c2f1bbdc1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 21:17:52 +1100 Subject: [PATCH 014/122] Dashboard API --- .../Controllers/DashboardApiController.php | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index 3496105982..de004a97e3 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -158,29 +158,29 @@ class DashboardApiController extends BaseAPIController } } - - $data = [ - 'id' => 1, - 'paidToDate' => $paidToDate[0]->value, - 'paidToDateCurrency' => $paidToDate[0]->currency_id, - 'balances' => $balances[0]->value, - 'balancesCurrency' => $balances[0]->currency_id, - 'averageInvoice' => $averageInvoice[0]->invoice_avg, - 'averageInvoiceCurrency' => $averageInvoice[0]->currency_id, - 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, - 'activeClients' => $metrics ? $metrics->active_clients : 0, - ]; - /* - $data = [ - 'id' => 1, - 'paidToDate' => $paidToDate, - 'balances' => $balances, - 'averageInvoice' => $averageInvoice, - 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, - 'activeClients' => $metrics ? $metrics->active_clients : 0, - ]; - */ + $data = [ + 'id' => 1, + 'paidToDate' => $paidToDate[0]->value, + 'paidToDateCurrency' => $paidToDate[0]->currency_id, + 'balances' => $balances[0]->value, + 'balancesCurrency' => $balances[0]->currency_id, + 'averageInvoice' => $averageInvoice[0]->invoice_avg, + 'averageInvoiceCurrency' => $averageInvoice[0]->currency_id, + 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, + 'activeClients' => $metrics ? $metrics->active_clients : 0, + ]; + + */ + $data = [ + 'id' => 1, + 'paidToDate' => $paidToDate, + 'balances' => $balances, + 'averageInvoice' => $averageInvoice, + 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, + 'activeClients' => $metrics ? $metrics->active_clients : 0, + ]; + return $this->response($data); } From 86cda04806b4150d72f31e9f7193df2ea83f5ab1 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 12:18:20 +0200 Subject: [PATCH 015/122] Minor styling fix --- public/css/built.css | 4 ++-- public/css/style.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/css/built.css b/public/css/built.css index e98e86aefe..c0c7b5c649 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -2532,8 +2532,8 @@ font-weight: bold; .navbar, .panel-default, ul.dropdown-menu, -canvas, -.twitter-typeahead .tt-menu { +.twitter-typeahead .tt-menu +canvas { x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); box-shadow: 0 0 1px 1px rgba(0,0,0,.05); diff --git a/public/css/style.css b/public/css/style.css index 7ddea57785..b44332f3f5 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -405,8 +405,8 @@ font-weight: bold; .navbar, .panel-default, ul.dropdown-menu, -canvas, -.twitter-typeahead .tt-menu { +.twitter-typeahead .tt-menu +canvas { x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); box-shadow: 0 0 1px 1px rgba(0,0,0,.05); From 8e5d15dfbe8c9dad599a4b06503d9f914e92d89b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Mar 2016 21:21:05 +1100 Subject: [PATCH 016/122] Dashboard API --- .../Controllers/DashboardApiController.php | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/DashboardApiController.php b/app/Http/Controllers/DashboardApiController.php index de004a97e3..06393e3ddc 100644 --- a/app/Http/Controllers/DashboardApiController.php +++ b/app/Http/Controllers/DashboardApiController.php @@ -11,7 +11,7 @@ class DashboardApiController extends BaseAPIController { $view_all = !Auth::user()->hasPermission('view_all'); $user_id = Auth::user()->id; - + // total_income, billed_clients, invoice_sent and active_clients $select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients, SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent, @@ -25,17 +25,17 @@ class DashboardApiController extends BaseAPIController ->where('invoices.is_deleted', '=', false) ->where('invoices.is_recurring', '=', false) ->where('invoices.is_quote', '=', false); - + if(!$view_all){ $metrics = $metrics->where(function($query) use($user_id){ $query->where('invoices.user_id', '=', $user_id); $query->orwhere(function($query) use($user_id){ - $query->where('invoices.user_id', '=', null); + $query->where('invoices.user_id', '=', null); $query->where('clients.user_id', '=', $user_id); }); }); } - + $metrics = $metrics->groupBy('accounts.id') ->first(); @@ -45,11 +45,11 @@ class DashboardApiController extends BaseAPIController ->leftJoin('clients', 'accounts.id', '=', 'clients.account_id') ->where('accounts.id', '=', Auth::user()->account_id) ->where('clients.is_deleted', '=', false); - + if(!$view_all){ $paidToDate = $paidToDate->where('clients.user_id', '=', $user_id); } - + $paidToDate = $paidToDate->groupBy('accounts.id') ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->get(); @@ -64,11 +64,11 @@ class DashboardApiController extends BaseAPIController ->where('invoices.is_deleted', '=', false) ->where('invoices.is_quote', '=', false) ->where('invoices.is_recurring', '=', false); - + if(!$view_all){ $averageInvoice = $averageInvoice->where('invoices.user_id', '=', $user_id); } - + $averageInvoice = $averageInvoice->groupBy('accounts.id') ->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END')) ->get(); @@ -96,11 +96,11 @@ class DashboardApiController extends BaseAPIController ->where('invoices.deleted_at', '=', null) ->where('contacts.is_primary', '=', true) ->where('invoices.due_date', '<', date('Y-m-d')); - + if(!$view_all){ $pastDue = $pastDue->where('invoices.user_id', '=', $user_id); } - + $pastDue = $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->orderBy('invoices.due_date', 'asc') ->take(50) @@ -120,11 +120,11 @@ class DashboardApiController extends BaseAPIController ->where('contacts.is_primary', '=', true) ->where('invoices.due_date', '>=', date('Y-m-d')) ->orderBy('invoices.due_date', 'asc'); - + if(!$view_all){ $upcoming = $upcoming->where('invoices.user_id', '=', $user_id); } - + $upcoming = $upcoming->take(50) ->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'is_quote']) ->get(); @@ -139,11 +139,11 @@ class DashboardApiController extends BaseAPIController ->where('clients.is_deleted', '=', false) ->where('contacts.deleted_at', '=', null) ->where('contacts.is_primary', '=', true); - + if(!$view_all){ $payments = $payments->where('payments.user_id', '=', $user_id); } - + $payments = $payments->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id']) ->orderBy('payments.payment_date', 'desc') ->take(50) @@ -158,30 +158,22 @@ class DashboardApiController extends BaseAPIController } } - /* - $data = [ - 'id' => 1, - 'paidToDate' => $paidToDate[0]->value, - 'paidToDateCurrency' => $paidToDate[0]->currency_id, - 'balances' => $balances[0]->value, - 'balancesCurrency' => $balances[0]->currency_id, - 'averageInvoice' => $averageInvoice[0]->invoice_avg, - 'averageInvoiceCurrency' => $averageInvoice[0]->currency_id, - 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, - 'activeClients' => $metrics ? $metrics->active_clients : 0, - ]; - */ - $data = [ - 'id' => 1, - 'paidToDate' => $paidToDate, - 'balances' => $balances, - 'averageInvoice' => $averageInvoice, - 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, - 'activeClients' => $metrics ? $metrics->active_clients : 0, - ]; + $data = [ + 'id' => 1, + 'paidToDate' => $paidToDate[0]->value, + 'paidToDateCurrency' => $paidToDate[0]->currency_id, + 'balances' => $balances[0]->value, + 'balancesCurrency' => $balances[0]->currency_id, + 'averageInvoice' => $averageInvoice[0]->invoice_avg, + 'averageInvoiceCurrency' => $averageInvoice[0]->currency_id, + 'invoicesSent' => $metrics ? $metrics->invoices_sent : 0, + 'activeClients' => $metrics ? $metrics->active_clients : 0, + ]; - return $this->response($data); + + + return $this->response($data); } } From 2a7c84714d6e4bd8110746545f27cffe3c83921b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 14:25:30 +0200 Subject: [PATCH 017/122] Minor style fixes --- public/css/built.css | 2 +- public/css/built.public.css | 1 - public/css/public.style.css | 1 - public/css/style.css | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/public/css/built.css b/public/css/built.css index c0c7b5c649..8e2782ebbf 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -2532,7 +2532,7 @@ font-weight: bold; .navbar, .panel-default, ul.dropdown-menu, -.twitter-typeahead .tt-menu +.twitter-typeahead .tt-menu, canvas { x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); diff --git a/public/css/built.public.css b/public/css/built.public.css index cd2d746962..3e91dc0ed1 100644 --- a/public/css/built.public.css +++ b/public/css/built.public.css @@ -800,7 +800,6 @@ html { font-weight: 500; font-size: 15px; font-weight: bold; - color: #ecf0f1; padding-left: 20px; padding-right: 20px; } diff --git a/public/css/public.style.css b/public/css/public.style.css index bdac74f779..95b158cb9b 100644 --- a/public/css/public.style.css +++ b/public/css/public.style.css @@ -17,7 +17,6 @@ html { font-weight: 500; font-size: 15px; font-weight: bold; - color: #ecf0f1; padding-left: 20px; padding-right: 20px; } diff --git a/public/css/style.css b/public/css/style.css index b44332f3f5..2cf0cdceca 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -405,7 +405,7 @@ font-weight: bold; .navbar, .panel-default, ul.dropdown-menu, -.twitter-typeahead .tt-menu +.twitter-typeahead .tt-menu, canvas { x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); From 958cf4e8b71e2843a79792b4a74418d767aee3e3 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 14:38:22 +0200 Subject: [PATCH 018/122] Added support for Google Maps API key --- .env.example | 4 +++- resources/views/clients/show.blade.php | 2 +- resources/views/vendors/show.blade.php | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 66d2d5251b..c0d6668fb5 100644 --- a/.env.example +++ b/.env.example @@ -41,4 +41,6 @@ API_SECRET=password #GOOGLE_CLIENT_ID= #GOOGLE_CLIENT_SECRET= -#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google \ No newline at end of file +#GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google + +#GOOGLE_MAPS_API_KEY= \ No newline at end of file diff --git a/resources/views/clients/show.blade.php b/resources/views/clients/show.blade.php index 3166f9b800..4af8e7a58f 100644 --- a/resources/views/clients/show.blade.php +++ b/resources/views/clients/show.blade.php @@ -14,7 +14,7 @@ } - + @endif @stop diff --git a/resources/views/vendors/show.blade.php b/resources/views/vendors/show.blade.php index fc6ed7e52c..ada4c3bcff 100644 --- a/resources/views/vendors/show.blade.php +++ b/resources/views/vendors/show.blade.php @@ -14,7 +14,7 @@ } - + @endif @stop From ddbc254d4b2c35d084e46761d1012c6a62718ff0 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 15:17:46 +0200 Subject: [PATCH 019/122] Add link to payments from invoice page --- app/Http/Controllers/InvoiceController.php | 10 +++++++++- app/Ninja/Presenters/PaymentPresenter.php | 11 +++++++++++ resources/lang/en/texts.php | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index dc9cc501db..5a14ea6244 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -89,7 +89,7 @@ class InvoiceController extends BaseController { $account = Auth::user()->account; $invoice = Invoice::scope($publicId) - ->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items') + ->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'payments') ->withTrashed() ->firstOrFail(); @@ -155,6 +155,14 @@ class InvoiceController extends BaseController if (!$invoice->is_recurring && $invoice->balance > 0) { $actions[] = ['url' => 'javascript:onPaymentClick()', 'label' => trans('texts.enter_payment')]; } + + foreach ($invoice->payments as $payment) { + $label = trans("texts.view_payment"); + if (count($invoice->payments) > 1) { + $label .= ' - ' . $account->formatMoney($payment->amount, $invoice->client); + } + $actions[] = ['url' => $payment->present()->url, 'label' => $label]; + } } if (count($actions) > 3) { diff --git a/app/Ninja/Presenters/PaymentPresenter.php b/app/Ninja/Presenters/PaymentPresenter.php index a0a58663e5..a1c3692991 100644 --- a/app/Ninja/Presenters/PaymentPresenter.php +++ b/app/Ninja/Presenters/PaymentPresenter.php @@ -1,5 +1,6 @@ entity->public_id . '/edit'); + } + + public function link() + { + return link_to('/payments/' . $this->entity->public_id . '/edit', $this->entity->getDisplayName()); + } + } \ No newline at end of file diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 02fe26bece..c59c22b370 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1082,7 +1082,7 @@ $LANG = array( 'create_all_help' => 'Allow user to create and modify records', 'view_all_help' => 'Allow user to view records they didn\'t create', 'edit_all_help' => 'Allow user to modify records they didn\'t create', - + 'view_payment' => 'View Payment', ); From beede39e85275b03a8a8ee66d955aee2f708829b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 16:09:48 +0200 Subject: [PATCH 020/122] Updated to the latest version of Laravel --- composer.json | 2 +- composer.lock | 92 +++++++++---------- readme.md | 2 +- resources/views/accounts/api_tokens.blade.php | 7 +- 4 files changed, 54 insertions(+), 49 deletions(-) diff --git a/composer.json b/composer.json index bc315ba6c2..45e6e18726 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "omnipay/gocardless": "dev-master", "omnipay/stripe": "2.3.0", - "laravel/framework": "5.2.22", + "laravel/framework": "5.2.*", "laravelcollective/html": "5.2.*", "laravelcollective/bus": "5.2.*", "symfony/css-selector": "~3.0", diff --git a/composer.lock b/composer.lock index bd2369f879..6ce01e65ba 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": "a33dce96f4ded3fb269a6d9dcbf24b27", - "content-hash": "f73a83c64422ef3560da4adb988850ae", + "hash": "e5e8524886bd38794a15e406acc3745a", + "content-hash": "6b3f343959ba3f330c425325574dfe28", "packages": [ { "name": "agmscode/omnipay-agms", @@ -123,12 +123,12 @@ "source": { "type": "git", "url": "https://github.com/formers/former.git", - "reference": "e196c4336db77be97131f6a3b3c3b69b3a22b683" + "reference": "d97f907741323b390f43954a90a227921ecc6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/formers/former/zipball/e196c4336db77be97131f6a3b3c3b69b3a22b683", - "reference": "e196c4336db77be97131f6a3b3c3b69b3a22b683", + "url": "https://api.github.com/repos/formers/former/zipball/d97f907741323b390f43954a90a227921ecc6b96", + "reference": "d97f907741323b390f43954a90a227921ecc6b96", "shasum": "" }, "require": { @@ -174,7 +174,7 @@ "foundation", "laravel" ], - "time": "2016-03-02 17:21:21" + "time": "2016-03-16 01:43:45" }, { "name": "anahkiasen/html-object", @@ -880,12 +880,12 @@ "source": { "type": "git", "url": "https://github.com/delatbabel/omnipay-fatzebra.git", - "reference": "7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc" + "reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc", - "reference": "7b3cb869abe8327d4cf6ccc6591a89a95c02bfbc", + "url": "https://api.github.com/repos/delatbabel/omnipay-fatzebra/zipball/d0a56a8704357d91457672741a48a4cb6c7ecd53", + "reference": "d0a56a8704357d91457672741a48a4cb6c7ecd53", "shasum": "" }, "require": { @@ -929,7 +929,7 @@ "payment", "paystream" ], - "time": "2015-02-15 11:27:23" + "time": "2016-03-21 09:21:14" }, { "name": "dercoder/omnipay-ecopayz", @@ -1039,12 +1039,12 @@ "source": { "type": "git", "url": "https://github.com/descubraomundo/omnipay-pagarme.git", - "reference": "528953568929b57189de16fa7431eaab75d61840" + "reference": "8571396139eb1fb1a7011450714a5e8d8d604d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/descubraomundo/omnipay-pagarme/zipball/528953568929b57189de16fa7431eaab75d61840", - "reference": "528953568929b57189de16fa7431eaab75d61840", + "url": "https://api.github.com/repos/descubraomundo/omnipay-pagarme/zipball/8571396139eb1fb1a7011450714a5e8d8d604d8c", + "reference": "8571396139eb1fb1a7011450714a5e8d8d604d8c", "shasum": "" }, "require": { @@ -1081,7 +1081,7 @@ "pay", "payment" ], - "time": "2015-10-27 19:17:20" + "time": "2016-03-18 19:37:37" }, { "name": "dioscouri/omnipay-cybersource", @@ -1938,16 +1938,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.1.1", + "version": "6.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c" + "reference": "d094e337976dff9d8e2424e8485872194e768662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c", - "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662", + "reference": "d094e337976dff9d8e2424e8485872194e768662", "shasum": "" }, "require": { @@ -1963,7 +1963,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "6.2-dev" } }, "autoload": { @@ -1996,7 +1996,7 @@ "rest", "web service" ], - "time": "2015-11-23 00:47:50" + "time": "2016-03-21 20:02:09" }, { "name": "guzzlehttp/promises", @@ -2603,12 +2603,12 @@ "source": { "type": "git", "url": "https://github.com/labs7in0/omnipay-wechat.git", - "reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a" + "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a", - "reference": "4e279ff4535dfa0636a3d6af5c92b8e9dcc4311a", + "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/40c9f86df6573ad98ae1dd0d29712ccbc789a74e", + "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e", "shasum": "" }, "require": { @@ -2644,7 +2644,7 @@ "purchase", "wechat" ], - "time": "2015-11-16 11:04:21" + "time": "2016-03-18 09:59:11" }, { "name": "laracasts/presenter", @@ -2694,16 +2694,16 @@ }, { "name": "laravel/framework", - "version": "v5.2.22", + "version": "v5.2.24", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "aec1b7cb9ec0bac0107361a3730cac9b6f945ef4" + "reference": "396297a5fd3c70c2fc1af68f09ee574a2380175c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/aec1b7cb9ec0bac0107361a3730cac9b6f945ef4", - "reference": "aec1b7cb9ec0bac0107361a3730cac9b6f945ef4", + "url": "https://api.github.com/repos/laravel/framework/zipball/396297a5fd3c70c2fc1af68f09ee574a2380175c", + "reference": "396297a5fd3c70c2fc1af68f09ee574a2380175c", "shasum": "" }, "require": { @@ -2716,7 +2716,7 @@ "monolog/monolog": "~1.11", "mtdowling/cron-expression": "~1.0", "nesbot/carbon": "~1.20", - "paragonie/random_compat": "~1.1", + "paragonie/random_compat": "~1.4", "php": ">=5.5.9", "psy/psysh": "0.7.*", "swiftmailer/swiftmailer": "~5.1", @@ -2818,7 +2818,7 @@ "framework", "laravel" ], - "time": "2016-02-27 22:09:19" + "time": "2016-03-22 13:45:19" }, { "name": "laravel/socialite", @@ -3815,7 +3815,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/31394ce58d5999b6f49b321cb3547747837c1297", + "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/77b316bd08c6b7a1e93721f15d1bfbd21a62ba6b", "reference": "e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "shasum": "" }, @@ -4328,16 +4328,16 @@ }, { "name": "omnipay/eway", - "version": "v2.2.0", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-eway.git", - "reference": "0dcf28596f0382fbfc3ee229e98e60798675ed16" + "reference": "1c953630f7097bfdeed200b17a847015a4df5607" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-eway/zipball/0dcf28596f0382fbfc3ee229e98e60798675ed16", - "reference": "0dcf28596f0382fbfc3ee229e98e60798675ed16", + "url": "https://api.github.com/repos/thephpleague/omnipay-eway/zipball/1c953630f7097bfdeed200b17a847015a4df5607", + "reference": "1c953630f7097bfdeed200b17a847015a4df5607", "shasum": "" }, "require": { @@ -4381,7 +4381,7 @@ "pay", "payment" ], - "time": "2015-03-30 00:28:33" + "time": "2016-03-22 01:11:02" }, { "name": "omnipay/firstdata", @@ -5536,16 +5536,16 @@ }, { "name": "paragonie/random_compat", - "version": "v1.2.2", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "b3313b618f4edd76523572531d5d7e22fe747430" + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/b3313b618f4edd76523572531d5d7e22fe747430", - "reference": "b3313b618f4edd76523572531d5d7e22fe747430", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774", + "reference": "c7e26a21ba357863de030f0b9e701c7d04593774", "shasum": "" }, "require": { @@ -5580,7 +5580,7 @@ "pseudorandom", "random" ], - "time": "2016-03-11 19:54:08" + "time": "2016-03-18 20:34:03" }, { "name": "patricktalmadge/bootstrapper", @@ -8475,16 +8475,16 @@ }, { "name": "phpspec/phpspec", - "version": "2.4.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/phpspec/phpspec.git", - "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed" + "reference": "385ecb015e97c13818074f1517928b24d4a26067" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/phpspec/zipball/5528ce1e93a1efa090c9404aba3395c329b4e6ed", - "reference": "5528ce1e93a1efa090c9404aba3395c329b4e6ed", + "url": "https://api.github.com/repos/phpspec/phpspec/zipball/385ecb015e97c13818074f1517928b24d4a26067", + "reference": "385ecb015e97c13818074f1517928b24d4a26067", "shasum": "" }, "require": { @@ -8549,7 +8549,7 @@ "testing", "tests" ], - "time": "2016-01-01 10:17:54" + "time": "2016-03-20 20:34:32" }, { "name": "phpspec/prophecy", diff --git a/readme.md b/readme.md index fe4cb90bdc..b6a5b63a3d 100644 --- a/readme.md +++ b/readme.md @@ -52,7 +52,7 @@ Note: we've recently updated this branch to Laravel 5.2. If you're upgrading her * [Debian and Nginx](https://www.rosehosting.com/blog/install-invoice-ninja-on-a-debian-7-vps/) * [User Guide](https://www.invoiceninja.com/app-user-guide/) * [Developer Guide](https://www.invoiceninja.com/knowledgebase/developer-guide/) -* [API Documentation](https://www.invoiceninja.com/knowledgebase/api-documentation/) +* [API Documentation](https://www.invoiceninja.com/api-documentation/) * [Support Forum](https://www.invoiceninja.com/forums/forum/support/) * [Feature Roadmap](https://trello.com/b/63BbiVVe/) diff --git a/resources/views/accounts/api_tokens.blade.php b/resources/views/accounts/api_tokens.blade.php index 4612b1cbfe..0b338c8026 100644 --- a/resources/views/accounts/api_tokens.blade.php +++ b/resources/views/accounts/api_tokens.blade.php @@ -5,7 +5,7 @@ @include('accounts.nav', ['selected' => ACCOUNT_API_TOKENS, 'advanced' => true])
- {!! Button::normal(trans('texts.documentation'))->asLinkTo(NINJA_WEB_URL.'/knowledgebase/api-documentation/')->withAttributes(['target' => '_blank'])->appendIcon(Icon::create('info-sign')) !!} + {!! Button::normal(trans('texts.documentation'))->asLinkTo(NINJA_WEB_URL.'/api-documentation/')->withAttributes(['target' => '_blank'])->appendIcon(Icon::create('info-sign')) !!} @if (Utils::isNinja()) {!! Button::normal(trans('texts.zapier'))->asLinkTo(ZAPIER_URL)->withAttributes(['target' => '_blank']) !!} @endif @@ -51,4 +51,9 @@ + @if (Utils::isNinja() && !Utils::isReseller()) +

 

+ + @endif + @stop From 5c1e9900d3b5f015cc5ba3429d0a3a987afaeb11 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 16:37:14 +0200 Subject: [PATCH 021/122] Fix for #779 --- app/Libraries/Utils.php | 6 +++--- resources/lang/en/texts.php | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index cfcc97c001..12ab7dadcc 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -620,8 +620,8 @@ class Utils private static function getMonth($offset) { - $months = [ "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December", ]; + $months = [ "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december", ]; $month = intval(date('n')) - 1; @@ -632,7 +632,7 @@ class Utils $month += 12; } - return $months[$month]; + return trans('texts.' . $months[$month]); } private static function getQuarter($offset) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index c59c22b370..1df2bc2ca4 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1083,6 +1083,19 @@ $LANG = array( 'view_all_help' => 'Allow user to view records they didn\'t create', 'edit_all_help' => 'Allow user to modify records they didn\'t create', 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', ); From b0a4a9b31cb135ca169a2a88ae2c87502b8ca354 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 17:14:40 +0200 Subject: [PATCH 022/122] Cleaning up log files --- app/Http/Controllers/PaymentController.php | 2 +- app/Http/routes.php | 2 +- app/Libraries/Utils.php | 8 ++++++-- app/Ninja/Mailers/Mailer.php | 2 +- bootstrap/app.php | 5 +++++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 1ba681ba57..ebede5711b 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -642,6 +642,6 @@ class PaymentController extends BaseController $message .= $error ?: trans('texts.payment_error'); Session::flash('error', $message); - Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message)); + Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true); } } diff --git a/app/Http/routes.php b/app/Http/routes.php index e014579095..eeafb70f87 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -708,4 +708,4 @@ if (Utils::isNinjaDev()) //ini_set('memory_limit','1024M'); //Auth::loginUsingId(1); } -*/ +*/ \ No newline at end of file diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 12ab7dadcc..439247f337 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -247,7 +247,7 @@ class Utils return "***{$class}*** [{$code}] : {$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}"; } - public static function logError($error, $context = 'PHP') + public static function logError($error, $context = 'PHP', $info = false) { if ($error instanceof Exception) { $error = self::getErrorString($error); @@ -271,7 +271,11 @@ class Utils 'count' => Session::get('error_count', 0), ]; - Log::error($error."\n", $data); + if ($info) { + Log::info($error."\n", $data); + } else { + Log::error($error."\n", $data); + } /* Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message) diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php index c30c9a10d7..7afcc2548c 100644 --- a/app/Ninja/Mailers/Mailer.php +++ b/app/Ninja/Mailers/Mailer.php @@ -81,7 +81,7 @@ class Mailer $emailError = $exception->getMessage(); } - Utils::logError("Email Error: $emailError"); + //Utils::logError("Email Error: $emailError"); if (isset($data['invitation'])) { $invitation = $data['invitation']; diff --git a/bootstrap/app.php b/bootstrap/app.php index 354e5dd905..71f392315f 100755 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -58,4 +58,9 @@ if (strstr($_SERVER['HTTP_USER_AGENT'], 'PhantomJS') && Utils::isNinjaDev()) { } */ +// Write info messages to a separate file +$app->configureMonologUsing(function($monolog) { + $monolog->pushHandler(new Monolog\Handler\StreamHandler(storage_path() . '/logs/laravel-info.log', Monolog\Logger::INFO, false)); +}); + return $app; From 6a55cf56a021de6e91ef00a014fd07b653a77def Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 19:35:57 +0200 Subject: [PATCH 023/122] Updated placeholder for iframe url --- resources/views/accounts/email_settings.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/accounts/email_settings.blade.php b/resources/views/accounts/email_settings.blade.php index e6b7e291a0..4d157ffda3 100644 --- a/resources/views/accounts/email_settings.blade.php +++ b/resources/views/accounts/email_settings.blade.php @@ -50,7 +50,7 @@ ->help(trans('texts.subdomain_help')) !!} {!! Former::text('iframe_url') - ->placeholder('http://www.example.com/invoice') + ->placeholder('https://www.example.com/invoice') ->appendIcon('question-sign') ->addGroupClass('iframe_url') ->label(' ') From abf2fcc09ce755f86327cd98b45bba065c498b09 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 19:38:51 +0200 Subject: [PATCH 024/122] Bumped version number to 2.5.1 --- app/Http/routes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index eeafb70f87..4a773bc676 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -535,7 +535,7 @@ if (!defined('CONTACT_EMAIL')) { define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com'); - define('NINJA_VERSION', '2.5.0.4'); + define('NINJA_VERSION', '2.5.1'); define('NINJA_DATE', '2000-01-01'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); From 933660578806af102a3f576d78353a1a4e1654b8 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 19:50:55 +0200 Subject: [PATCH 025/122] Updated language files --- resources/lang/da/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/de/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/es/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/es_ES/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/fr/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/fr_CA/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/it/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/ja/texts.php | 45 ++++++++++++++++++++++ resources/lang/lt/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/nb_NO/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/nl/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/pt_BR/texts.php | 69 ++++++++++++++++++++++++++++++++++ resources/lang/sv/texts.php | 69 ++++++++++++++++++++++++++++++++++ 13 files changed, 873 insertions(+) diff --git a/resources/lang/da/texts.php b/resources/lang/da/texts.php index e3fe7a0819..ffc74871cd 100644 --- a/resources/lang/da/texts.php +++ b/resources/lang/da/texts.php @@ -1131,4 +1131,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); \ No newline at end of file diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index a3ea1ac658..08ffaf7b8a 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -1132,4 +1132,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); diff --git a/resources/lang/es/texts.php b/resources/lang/es/texts.php index 56fb42534f..75bd1a75da 100644 --- a/resources/lang/es/texts.php +++ b/resources/lang/es/texts.php @@ -1108,4 +1108,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); diff --git a/resources/lang/es_ES/texts.php b/resources/lang/es_ES/texts.php index 4e29075d55..25054072da 100644 --- a/resources/lang/es_ES/texts.php +++ b/resources/lang/es_ES/texts.php @@ -1128,4 +1128,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); diff --git a/resources/lang/fr/texts.php b/resources/lang/fr/texts.php index 2bc358e6a8..6cd75a9f0e 100644 --- a/resources/lang/fr/texts.php +++ b/resources/lang/fr/texts.php @@ -1123,4 +1123,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); diff --git a/resources/lang/fr_CA/texts.php b/resources/lang/fr_CA/texts.php index 946fac0696..c62227e450 100644 --- a/resources/lang/fr_CA/texts.php +++ b/resources/lang/fr_CA/texts.php @@ -1121,4 +1121,73 @@ return array( 'overdue' => 'En souffrance', 'white_label_text' => 'Achetez une licence sans pub d\'un an à $'.WHITE_LABEL_PRICE.' pour retirer le logo de Invoice Ninja du portail client et supporter notre projet.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); \ No newline at end of file diff --git a/resources/lang/it/texts.php b/resources/lang/it/texts.php index acc4cdeeba..0b2a843ccd 100644 --- a/resources/lang/it/texts.php +++ b/resources/lang/it/texts.php @@ -1126,4 +1126,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); \ No newline at end of file diff --git a/resources/lang/ja/texts.php b/resources/lang/ja/texts.php index a6b046e42f..7e673ac11d 100644 --- a/resources/lang/ja/texts.php +++ b/resources/lang/ja/texts.php @@ -1051,6 +1051,51 @@ $LANG = array( 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', 'enable_client_portal' => 'ダッシュボード', 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', ); diff --git a/resources/lang/lt/texts.php b/resources/lang/lt/texts.php index 584f7776b4..252a7f03bd 100644 --- a/resources/lang/lt/texts.php +++ b/resources/lang/lt/texts.php @@ -1133,4 +1133,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); \ No newline at end of file diff --git a/resources/lang/nb_NO/texts.php b/resources/lang/nb_NO/texts.php index afc916a5de..df06d7a325 100644 --- a/resources/lang/nb_NO/texts.php +++ b/resources/lang/nb_NO/texts.php @@ -1131,4 +1131,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); \ No newline at end of file diff --git a/resources/lang/nl/texts.php b/resources/lang/nl/texts.php index db06a981f4..83fef88816 100644 --- a/resources/lang/nl/texts.php +++ b/resources/lang/nl/texts.php @@ -1126,4 +1126,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); \ No newline at end of file diff --git a/resources/lang/pt_BR/texts.php b/resources/lang/pt_BR/texts.php index 731be8bbbd..30fcfbf05c 100644 --- a/resources/lang/pt_BR/texts.php +++ b/resources/lang/pt_BR/texts.php @@ -1123,4 +1123,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); diff --git a/resources/lang/sv/texts.php b/resources/lang/sv/texts.php index 5d526092c4..050076bd6e 100644 --- a/resources/lang/sv/texts.php +++ b/resources/lang/sv/texts.php @@ -1128,4 +1128,73 @@ return array( 'overdue' => 'Overdue', 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', + 'navigation' => 'Navigation', + 'list_invoices' => 'List Invoices', + 'list_clients' => 'List Clients', + 'list_quotes' => 'List Quotes', + 'list_tasks' => 'List Tasks', + 'list_expenses' => 'List Expenses', + 'list_recurring_invoices' => 'List Recurring Invoices', + 'list_payments' => 'List Payments', + 'list_credits' => 'List Credits', + 'tax_name' => 'Tax Name', + 'report_settings' => 'Report Settings', + 'search_hotkey' => 'shortcut is /', + + 'new_user' => 'New User', + 'new_product' => 'New Product', + 'new_tax_rate' => 'New Tax Rate', + 'invoiced_amount' => 'Invoiced Amount', + 'invoice_item_fields' => 'Invoice Item Fields', + 'custom_invoice_item_fields_help' => 'Add a field when creating an invoice item and display the label and value on the PDF.', + 'recurring_invoice_number' => 'Recurring Invoice Number', + 'recurring_invoice_number_prefix_help' => 'Speciy a prefix to be added to the invoice number for recurring invoices. The default value is \'R\'.', + 'enable_client_portal' => 'Dashboard', + 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + + // Client Passwords + 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password_help'=>'Allows you to set a password for each contact. If a password is set, the contact will be required to enter a password before viewing invoices.', + 'send_portal_password'=>'Generate password automatically', + 'send_portal_password_help'=>'If no password is set, one will be generated and sent with the first invoice.', + + 'expired' => 'Expired', + 'invalid_card_number' => 'The credit card number is not valid.', + 'invalid_expiry' => 'The expiration date is not valid.', + 'invalid_cvv' => 'The CVV is not valid.', + 'cost' => 'Cost', + 'create_invoice_for_sample' => 'Note: create your first invoice to see a preview here.', + + // User Permissions + 'owner' => 'Owner', + 'administrator' => 'Administrator', + 'administrator_help' => 'Allow user to manage users, change settings and modify all records', + 'user_create_all' => 'Create clients, invoices, etc.', + 'user_view_all' => 'View all clients, invoices, etc.', + 'user_edit_all' => 'Edit all clients, invoices, etc.', + 'gateway_help_20' => ':link to sign up for Sage Pay.', + 'gateway_help_21' => ':link to sign up for Sage Pay.', + 'partial_due' => 'Partial Due', + 'restore_vendor' => 'Restore Vendor', + 'restored_vendor' => 'Successfully restored vendor', + 'restored_expense' => 'Successfully restored expense', + 'permissions' => 'Permissions', + 'create_all_help' => 'Allow user to create and modify records', + 'view_all_help' => 'Allow user to view records they didn\'t create', + 'edit_all_help' => 'Allow user to modify records they didn\'t create', + 'view_payment' => 'View Payment', + + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + ); From 7576fb94b3317458d1061ffc1748e7ea4473eacc Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 19:52:50 +0200 Subject: [PATCH 026/122] Bumped version number to 2.5.1.1 --- app/Http/routes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index 4a773bc676..1f5289d443 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -535,7 +535,7 @@ if (!defined('CONTACT_EMAIL')) { define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com'); - define('NINJA_VERSION', '2.5.1'); + define('NINJA_VERSION', '2.5.1.1'); define('NINJA_DATE', '2000-01-01'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); From 5640c74f35df1c6e3d6c8679039113a1a2ad7e3b Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 22 Mar 2016 16:19:55 -0400 Subject: [PATCH 027/122] Migrate logos to use Laravel Filesystem --- app/Models/Account.php | 92 +++++++++++++------ app/Ninja/Repositories/AccountRepository.php | 2 +- app/Providers/AppServiceProvider.php | 11 ++- config/filesystems.php | 30 ++++-- .../2016_03_22_168364_add_documents.php | 37 ++++++++ resources/views/accounts/details.blade.php | 4 +- .../emails/partials/account_logo.blade.php | 2 +- resources/views/header.blade.php | 6 +- resources/views/invited/dashboard.blade.php | 2 +- resources/views/invoices/edit.blade.php | 2 +- resources/views/invoices/pdf.blade.php | 2 +- resources/views/user_account.blade.php | 4 +- .../views/users/account_management.blade.php | 4 +- 13 files changed, 143 insertions(+), 55 deletions(-) create mode 100644 database/migrations/2016_03_22_168364_add_documents.php diff --git a/app/Models/Account.php b/app/Models/Account.php index f6a7194bdf..26e1e0f6b7 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -9,6 +9,7 @@ use Cache; use App; use File; use App\Events\UserSettingsChanged; +use Illuminate\Support\Facades\Storage; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; @@ -384,26 +385,64 @@ class Account extends Eloquent public function hasLogo() { - return file_exists($this->getLogoFullPath()); + if($this->logo == ''){ + $this->calculateLogoDetails(); + } + + return !empty($this->logo); + } + + public function getLogoDisk(){ + return Storage::disk(env('LOGO_DISK', 'logos')); + } + + protected function calculateLogoDetails(){ + $disk = $this->getLogoDisk(); + + if($disk->exists($this->account_key.'.png')){ + $this->logo = $this->account_key.'.png'; + } else if($disk->exists($this->account_key.'.jpg')) { + $this->logo = $this->account_key.'.jpg'; + } + + if(!empty($this->logo)){ + $image = imagecreatefromstring($disk->get($this->logo)); + $this->logo_width = imagesx($image); + $this->logo_height = imagesy($image); + $this->logo_size = $disk->size($this->logo); + } else { + $this->logo = null; + } + $this->save(); } - public function getLogoPath() - { - $fileName = 'logo/' . $this->account_key; - - return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg'; + public function getLogoRaw(){ + if(!$this->hasLogo()){ + return null; + } + + $disk = $this->getLogoDisk(); + return $disk->get($this->logo); } - - public function getLogoFullPath() - { - $fileName = public_path() . '/logo/' . $this->account_key; - - return file_exists($fileName.'.png') ? $fileName.'.png' : $fileName.'.jpg'; - } - + public function getLogoURL() { - return SITE_URL . '/' . $this->getLogoPath(); + if(!$this->hasLogo()){ + return null; + } + + $disk = $this->getLogoDisk(); + $adapter = $disk->getAdapter(); + + if($adapter instanceof \League\Flysystem\Adapter\Local) { + // Stored locally + $logo_url = str_replace(public_path(), url('/'), $adapter->applyPathPrefix($this->logo), $count); + if($count == 1){ + return str_replace(DIRECTORY_SEPARATOR, '/', $logo_url); + } + } + + return null; } public function getToken($name) @@ -419,24 +458,20 @@ class Account extends Eloquent public function getLogoWidth() { - $path = $this->getLogoFullPath(); - if (!file_exists($path)) { - return 0; + if(!$this->hasLogo()){ + return null; } - list($width, $height) = getimagesize($path); - return $width; + return $this->logo_width; } public function getLogoHeight() { - $path = $this->getLogoFullPath(); - if (!file_exists($path)) { - return 0; + if(!$this->hasLogo()){ + return null; } - list($width, $height) = getimagesize($path); - return $height; + return $this->logo_height; } public function createInvoice($entityType = ENTITY_INVOICE, $clientId = null) @@ -815,12 +850,11 @@ class Account extends Eloquent public function getLogoSize() { - if (!$this->hasLogo()) { - return 0; + if(!$this->hasLogo()){ + return null; } - $filename = $this->getLogoFullPath(); - return round(File::size($filename) / 1000); + return round($this->logo_size / 1000); } public function isLogoTooLarge() diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index 0e08fbba5d..cd07a37b5d 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -475,7 +475,7 @@ class AccountRepository $item->account_id = $user->account->id; $item->account_name = $user->account->getDisplayName(); $item->pro_plan_paid = $user->account->pro_plan_paid; - $item->logo_path = $user->account->hasLogo() ? $user->account->getLogoPath() : null; + $item->logo_url = $user->account->hasLogo() ? $user->account->getLogoUrl() : null; $data[] = $item; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3cd6910987..c5d597ded2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -22,8 +22,15 @@ class AppServiceProvider extends ServiceProvider { */ public function boot() { - Form::macro('image_data', function($imagePath) { - return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath)); + Form::macro('image_data', function($image, $contents = false) { + if(!$contents){ + $contents = file_get_contents($image); + } + else{ + $contents = $image; + } + + return 'data:image/jpeg;base64,' . base64_encode($contents); }); Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') { diff --git a/config/filesystems.php b/config/filesystems.php index 0221fa70db..d0c683352b 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -47,23 +47,33 @@ return [ 'driver' => 'local', 'root' => storage_path().'/app', ], + + 'logos' => [ + 'driver' => 'local', + 'root' => env('LOGO_PATH', public_path().'/logo'), + ], + + 'documents' => [ + 'driver' => 'local', + 'root' => storage_path().'/documents', + ], 's3' => [ 'driver' => 's3', - 'key' => 'your-key', - 'secret' => 'your-secret', - 'region' => 'your-region', - 'bucket' => 'your-bucket', + 'key' => env('S3_KEY', ''), + 'secret' => env('S3_SECRET', ''), + 'region' => env('S3_REGION', ''), + 'bucket' => env('S3_BUCKET', ''), ], 'rackspace' => [ 'driver' => 'rackspace', - 'username' => 'your-username', - 'key' => 'your-key', - 'container' => 'your-container', - 'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/', - 'region' => 'IAD', - 'url_type' => 'publicURL' + 'username' => env('RACKSPACE_USERNAME', ''), + 'key' => env('RACKSPACE_KEY', ''), + 'container' => env('RACKSPACE_CONTAINER', ''), + 'endpoint' => env('RACKSPACE_ENDPOINT', 'https://identity.api.rackspacecloud.com/v2.0/'), + 'region' => env('RACKSPACE_REGION', 'https://identity.api.rackspacecloud.com/v2.0/'), + 'url_type' => env('RACKSPACE_URL_TYPE', 'publicURL') ], ], diff --git a/database/migrations/2016_03_22_168364_add_documents.php b/database/migrations/2016_03_22_168364_add_documents.php new file mode 100644 index 0000000000..97b0927570 --- /dev/null +++ b/database/migrations/2016_03_22_168364_add_documents.php @@ -0,0 +1,37 @@ +string('logo')->nullable()->default(null); + $table->unsignedInteger('logo_width'); + $table->unsignedInteger('logo_height'); + $table->unsignedInteger('logo_size'); + });*/ + + DB::table('accounts')->update(array('logo' => '')); + } + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function($table) { + $table->dropColumn('logo'); + $table->dropColumn('logo_width'); + $table->dropColumn('logo_height'); + $table->dropColumn('logo_size'); + }); + } +} diff --git a/resources/views/accounts/details.blade.php b/resources/views/accounts/details.blade.php index 56dc8f33e7..7201ed2f00 100644 --- a/resources/views/accounts/details.blade.php +++ b/resources/views/accounts/details.blade.php @@ -51,8 +51,8 @@
diff --git a/resources/views/emails/partials/account_logo.blade.php b/resources/views/emails/partials/account_logo.blade.php index cfe158153f..d79002d6b9 100644 --- a/resources/views/emails/partials/account_logo.blade.php +++ b/resources/views/emails/partials/account_logo.blade.php @@ -3,7 +3,7 @@ @endif - + @if ($account->website) diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 3ce364da64..1191aac2ee 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -448,7 +448,7 @@ 'user_id' => $item->user_id, 'account_name' => $item->account_name, 'user_name' => $item->user_name, - 'logo_path' => isset($item->logo_path) ? $item->logo_path : "", + 'logo_url' => isset($item->logo_url) ? $item->logo_url : "", 'selected' => true, ]) @endif @@ -460,7 +460,7 @@ 'user_id' => $item->user_id, 'account_name' => $item->account_name, 'user_name' => $item->user_name, - 'logo_path' => isset($item->logo_path) ? $item->logo_path : "", + 'logo_url' => isset($item->logo_url) ? $item->logo_url : "", 'selected' => false, ]) @endif @@ -469,7 +469,7 @@ @include('user_account', [ 'account_name' => Auth::user()->account->name ?: trans('texts.untitled'), 'user_name' => Auth::user()->getDisplayName(), - 'logo_path' => Auth::user()->account->getLogoPath(), + 'logo_url' => Auth::user()->account->getLogoURL(), 'selected' => true, ]) @endif diff --git a/resources/views/invited/dashboard.blade.php b/resources/views/invited/dashboard.blade.php index a5b698c46e..4af2cc53c9 100644 --- a/resources/views/invited/dashboard.blade.php +++ b/resources/views/invited/dashboard.blade.php @@ -95,7 +95,7 @@
') !!}
+
+
+
+ +
+
+
+ +
+
@@ -675,10 +686,12 @@ @include('invoices.knockout') diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index 029a81bc0b..dbb8b281e8 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -226,6 +226,7 @@ function InvoiceModel(data) { self.auto_bill = ko.observable(); self.invoice_status_id = ko.observable(0); self.invoice_items = ko.observableArray(); + self.documents = ko.observableArray(); self.amount = ko.observable(0); self.balance = ko.observable(0); self.invoice_design_id = ko.observable(1); @@ -251,6 +252,11 @@ function InvoiceModel(data) { return new ItemModel(options.data); } }, + 'documents': { + create: function(options) { + return new DocumentModel(options.data); + } + }, 'tax': { create: function(options) { return new TaxRateModel(options.data); @@ -267,6 +273,18 @@ function InvoiceModel(data) { applyComboboxListeners(); return itemModel; } + + self.addDocument = function() { + var documentModel = new DocumentModel(); + self.documents.push(documentModel); + return documentModel; + } + + self.removeDocument = function(public_id) { + self.documents.remove(function(document) { + return document.public_id() == public_id; + }); + } if (data) { ko.mapping.fromJS(data, self.mapping, self); @@ -810,6 +828,23 @@ function ItemModel(data) { this.onSelect = function() {} } + +function DocumentModel(data) { + var self = this; + self.public_id = ko.observable(0); + self.size = ko.observable(0); + self.name = ko.observable(''); + self.type = ko.observable(''); + self.url = ko.observable(''); + + self.update = function(data){ + ko.mapping.fromJS(data, {}, this); + } + + if (data) { + self.update(data); + } +} /* Custom binding for product key typeahead */ ko.bindingHandlers.typeahead = { From c57dfb7f0d106eebae1ef3cc5accdfbabc5dde26 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 22 Mar 2016 22:59:20 -0400 Subject: [PATCH 029/122] Restrict documents to pro users --- app/Http/Controllers/DocumentController.php | 4 ++++ resources/views/invoices/edit.blade.php | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 6952899507..03b1ffa917 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -48,6 +48,10 @@ class DocumentController extends BaseController public function postUpload() { + if (!Auth::user()->account->isPro()) { + return; + } + if(!$this->checkCreatePermission($response)){ return $response; } diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index b5f36c1b3d..59c662ccb5 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -270,7 +270,9 @@
  • {{ trans("texts.{$entityType}_terms") }}
  • {{ trans("texts.{$entityType}_footer") }}
  • + @if (Auth::user()->account->isPro())
  • {{ trans("texts.{$entityType}_documents") }}
  • + @endif
    @@ -302,6 +304,7 @@
    ') !!}
    + @if (Auth::user()->account->isPro())
    @@ -312,6 +315,7 @@
    + @endif
    @@ -919,6 +923,7 @@ applyComboboxListeners(); + @if (Auth::user()->account->isPro()) // Initialize document upload dropzone = new Dropzone('#document-upload', { url:{!! json_encode(url('document')) !!}, @@ -955,6 +960,7 @@ } dropzone.files.push(mockFile); } + @endif }); function onFrequencyChange(){ @@ -1313,6 +1319,7 @@ model.invoice().invoice_number(number); } + @if (Auth::user()->account->isPro()) function handleDocumentAdded(file){ if(file.mock)return; file.index = model.invoice().documents().length; @@ -1327,6 +1334,7 @@ file.public_id = response.document.public_id model.invoice().documents()[file.index].update(response.document); } + @endif From aae4f57c097c235673f3662eb0442e559cbfb291 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 23 Mar 2016 15:02:05 +0200 Subject: [PATCH 030/122] Prevent duplicate deletions --- app/Ninja/Repositories/BaseRepository.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/Ninja/Repositories/BaseRepository.php b/app/Ninja/Repositories/BaseRepository.php index bec95fb969..f674c40654 100644 --- a/app/Ninja/Repositories/BaseRepository.php +++ b/app/Ninja/Repositories/BaseRepository.php @@ -20,6 +20,10 @@ class BaseRepository public function archive($entity) { + if ($entity->trashed()) { + return; + } + $entity->delete(); $className = $this->getEventClass($entity, 'Archived'); @@ -31,6 +35,10 @@ class BaseRepository public function restore($entity) { + if ( ! $entity->trashed()) { + return; + } + $fromDeleted = false; $entity->restore(); @@ -49,6 +57,10 @@ class BaseRepository public function delete($entity) { + if ($entity->is_deleted) { + return; + } + $entity->is_deleted = true; $entity->save(); From bdf24f0ce32b29991f01c562122d956d697dd92a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 23 Mar 2016 15:15:18 +0200 Subject: [PATCH 031/122] Tweaking the drop shadows --- public/css/built.css | 6 +++--- public/css/built.public.css | 6 +++--- public/css/public.style.css | 6 +++--- public/css/style.css | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/public/css/built.css b/public/css/built.css index 8e2782ebbf..9488497902 100644 --- a/public/css/built.css +++ b/public/css/built.css @@ -2534,9 +2534,9 @@ font-weight: bold; ul.dropdown-menu, .twitter-typeahead .tt-menu, canvas { - x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); - x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); - box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + x-moz-box-shadow: 0 0 10px 2px rgba(0,0,0,.05); + x-webkit-box-shadow: 0 0 10px 2px rgba(0,0,0,.05); + box-shadow: 0 0 10px 2px rgba(0,0,0,.05); } .navbar .active > a { diff --git a/public/css/built.public.css b/public/css/built.public.css index 3e91dc0ed1..2a02c2db94 100644 --- a/public/css/built.public.css +++ b/public/css/built.public.css @@ -805,9 +805,9 @@ html { } .navbar { - x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); - x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); - box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + x-moz-box-shadow: 0 0 10px 2px rgba(0,0,0,.05); + x-webkit-box-shadow: 0 0 10px 2px rgba(0,0,0,.05); + box-shadow: 0 0 10px 2px rgba(0,0,0,.05); } #footer { diff --git a/public/css/public.style.css b/public/css/public.style.css index 95b158cb9b..b1f91c2573 100644 --- a/public/css/public.style.css +++ b/public/css/public.style.css @@ -22,9 +22,9 @@ html { } .navbar { - x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); - x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); - box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + x-moz-box-shadow: 0 0 10px 2px rgba(0,0,0,.05); + x-webkit-box-shadow: 0 0 10px 2px rgba(0,0,0,.05); + box-shadow: 0 0 10px 2px rgba(0,0,0,.05); } #footer { diff --git a/public/css/style.css b/public/css/style.css index 2cf0cdceca..0b1b53b1e3 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -407,9 +407,9 @@ font-weight: bold; ul.dropdown-menu, .twitter-typeahead .tt-menu, canvas { - x-moz-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); - x-webkit-box-shadow: 0 0 1px 1px rgba(0,0,0,.05); - box-shadow: 0 0 1px 1px rgba(0,0,0,.05); + x-moz-box-shadow: 0 0 10px 2px rgba(0,0,0,.05); + x-webkit-box-shadow: 0 0 10px 2px rgba(0,0,0,.05); + box-shadow: 0 0 10px 2px rgba(0,0,0,.05); } .navbar .active > a { From d810ba7f6bc1adef15b6b4d2e685599c66886577 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Wed, 23 Mar 2016 15:41:05 -0400 Subject: [PATCH 032/122] Improved document support --- app/Http/Controllers/DocumentController.php | 34 +- app/Http/routes.php | 4 +- app/Models/Account.php | 5 +- app/Models/Document.php | 98 +++++- app/Models/EntityModel.php | 11 +- app/Ninja/Repositories/DocumentRepository.php | 78 +++-- app/Ninja/Repositories/InvoiceRepository.php | 5 +- composer.json | 4 +- composer.lock | 319 +++++++++++++++++- config/filesystems.php | 4 +- .../2016_03_22_168362_add_documents.php | 2 +- resources/views/invoices/edit.blade.php | 6 +- 12 files changed, 508 insertions(+), 62 deletions(-) diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 03b1ffa917..4b7718bfd8 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -27,16 +27,15 @@ class DocumentController extends BaseController public function get($publicId) { $document = Document::scope($publicId) - ->withTrashed() ->firstOrFail(); if(!$this->checkViewPermission($document, $response)){ return $response; } - $public_url = $document->getPublicUrl(); - if($public_url){ - return redirect($public_url); + $direct_url = $document->getDirectUrl(); + if($direct_url){ + return redirect($direct_url); } @@ -46,9 +45,34 @@ class DocumentController extends BaseController return $response; } + public function getPreview($publicId) + { + $document = Document::scope($publicId) + ->firstOrFail(); + + if(!$this->checkViewPermission($document, $response)){ + return $response; + } + + if(empty($document->preview)){ + return Response::view('error', array('error'=>'Preview does not exist!'), 404); + } + + $direct_url = $document->getDirectPreviewUrl(); + if($direct_url){ + return redirect($direct_url); + } + + $extension = pathinfo($document->preview, PATHINFO_EXTENSION); + $response = Response::make($document->getRawPreview(), 200); + $response->header('content-type', Document::$extensions[$extension]); + + return $response; + } + public function postUpload() { - if (!Auth::user()->account->isPro()) { + if (!Utils::isPro()) { return; } diff --git a/app/Http/routes.php b/app/Http/routes.php index f9156ffc84..6a4970194c 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -133,6 +133,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::post('recurring_invoices/bulk', 'InvoiceController@bulk'); Route::get('document/{public_id}/{filename?}', 'DocumentController@get'); + Route::get('document/preview/{public_id}/{filename?}', 'DocumentController@getPreview'); Route::post('document', 'DocumentController@postUpload'); Route::get('quotes/create/{client_id?}', 'QuoteController@create'); @@ -428,7 +429,8 @@ if (!defined('CONTACT_EMAIL')) { define('MAX_IFRAME_URL_LENGTH', 250); define('MAX_LOGO_FILE_SIZE', 200); // KB define('MAX_FAILED_LOGINS', 10); - define('DEFAULT_MAX_DOCUMENT_SIZE', 10000);// KB + define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB + define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 150));// pixels define('DEFAULT_FONT_SIZE', 9); define('DEFAULT_HEADER_FONT', 1);// Roboto define('DEFAULT_BODY_FONT', 1);// Roboto diff --git a/app/Models/Account.php b/app/Models/Account.php index 26e1e0f6b7..4f8b6834ba 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -6,6 +6,7 @@ use Session; use DateTime; use Event; use Cache; +use Document; use App; use File; use App\Events\UserSettingsChanged; @@ -393,7 +394,7 @@ class Account extends Eloquent } public function getLogoDisk(){ - return Storage::disk(env('LOGO_DISK', 'logos')); + return Storage::disk(env('LOGO_FILESYSTEM', 'logos')); } protected function calculateLogoDetails(){ @@ -442,7 +443,7 @@ class Account extends Eloquent } } - return null; + Document::getDirectFileUrl($this->logo, $this->getDisk()); } public function getToken($name) diff --git a/app/Models/Document.php b/app/Models/Document.php index 8f1a01448f..f342d1ff08 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -1,7 +1,7 @@ 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', 'pdf' => 'application/pdf', 'gif' => 'image/gif' ); @@ -16,32 +18,27 @@ class Document extends EntityModel public static $types = array( 'image/png' => array( 'extension' => 'png', - 'image' => true, ), 'image/jpeg' => array( 'extension' => 'jpeg', - 'image' => true, + ), + 'image/tiff' => array( + 'extension' => 'tiff', ), 'image/gif' => array( 'extension' => 'gif', - 'image' => true, ), 'application/pdf' => array( 'extension' => 'pdf', ), ); - // Expenses - use SoftDeletes; - - protected $dates = ['deleted_at']; - public function fill(array $attributes) { parent::fill($attributes); if(empty($this->attributes['disk'])){ - $this->attributes['disk'] = env('LOGO_DISK', 'documents'); + $this->attributes['disk'] = env('DOCUMENT_FILESYSTEM', 'documents'); } return $this; @@ -68,17 +65,53 @@ class Document extends EntityModel } public function getDisk(){ - return Storage::disk(!empty($this->disk)?$this->disk:env('LOGO_DISK', 'documents')); + return Storage::disk(!empty($this->disk)?$this->disk:env('DOCUMENT_FILESYSTEM', 'documents')); } public function setDiskAttribute($value) { - $this->attributes['disk'] = $value?$value:env('LOGO_DISK', 'documents'); + $this->attributes['disk'] = $value?$value:env('DOCUMENT_FILESYSTEM', 'documents'); } - public function getPublicUrl(){ - $disk = $this->getDisk(); + public function getDirectUrl(){ + return static::getDirectFileUrl($this->path, $this->getDisk()); + } + + public function getDirectPreviewUrl(){ + return $this->preview?static::getDirectFileUrl($this->preview, $this->getDisk(), true):null; + } + + public static function getDirectFileUrl($path, $disk, $prioritizeSpeed = false){ $adapter = $disk->getAdapter(); + $fullPath = $adapter->applyPathPrefix($path); + + if($adapter instanceof \League\Flysystem\AwsS3v3\AwsS3Adapter) { + $client = $adapter->getClient(); + $command = $client->getCommand('GetObject', [ + 'Bucket' => $adapter->getBucket(), + 'Key' => $fullPath + ]); + + return (string) $client->createPresignedRequest($command, '+10 minutes')->getUri(); + } else if (!$prioritizeSpeed // Rackspace temp URLs are slow, so we don't use them for previews + && $adapter instanceof \League\Flysystem\Rackspace\RackspaceAdapter) { + $secret = env('RACKSPACE_TEMP_URL_SECRET'); + if($secret){ + $object = $adapter->getContainer()->getObject($fullPath); + + if(env('RACKSPACE_TEMP_URL_SECRET_SET')){ + // Go ahead and set the secret too + $object->getService()->getAccount()->setTempUrlSecret($secret); + } + + $url = $object->getUrl(); + $expiry = strtotime('+10 minutes'); + $urlPath = urldecode($url->getPath()); + $body = sprintf("%s\n%d\n%s", 'GET', $expiry, $urlPath); + $hash = hash_hmac('sha1', $body, $secret); + return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry); + } + } return null; } @@ -89,14 +122,49 @@ class Document extends EntityModel return $disk->get($this->path); } + public function getRawPreview(){ + $disk = $this->getDisk(); + + return $disk->get($this->preview); + } + public function getUrl(){ return url('document/'.$this->public_id.'/'.$this->name); } + public function getPreviewUrl(){ + return $this->preview?url('document/preview/'.$this->public_id.'/'.$this->name.'.'.pathinfo($this->preview, PATHINFO_EXTENSION)):null; + } + public function toArray() { $array = parent::toArray(); $array['url'] = $this->getUrl(); + $array['preview_url'] = $this->getPreviewUrl(); return $array; } -} \ No newline at end of file +} + +Document::deleted(function ($document) { + $same_path_count = DB::table('documents') + ->where('documents.account_id', '=', $document->account_id) + ->where('documents.path', '=', $document->path) + ->where('documents.disk', '=', $document->disk) + ->count(); + + if(!$same_path_count){ + $document->getDisk()->delete($document->path); + } + + if($document->preview){ + $same_preview_count = DB::table('documents') + ->where('documents.account_id', '=', $document->account_id) + ->where('documents.preview', '=', $document->preview) + ->where('documents.disk', '=', $document->disk) + ->count(); + if(!$same_preview_count){ + $document->getDisk()->delete($document->preview); + } + } + +}); \ No newline at end of file diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index aa6544e52f..53bb1d0d1a 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -24,9 +24,14 @@ class EntityModel extends Eloquent Utils::fatalError(); } - $lastEntity = $className::withTrashed() - ->scope(false, $entity->account_id) - ->orderBy('public_id', 'DESC') + if(method_exists($className, 'withTrashed')){ + $lastEntity = $className::withTrashed() + ->scope(false, $entity->account_id); + } else { + $lastEntity = $className::scope(false, $entity->account_id); + } + + $lastEntity = $lastEntity->orderBy('public_id', 'DESC') ->first(); if ($lastEntity) { diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index 97bd375007..8697a23262 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -5,7 +5,7 @@ use Utils; use Response; use App\Models\Document; use App\Ninja\Repositories\BaseRepository; -use Intervention\Image\Facades\Image; +use Intervention\Image\ImageManager; use Session; class DocumentRepository extends BaseRepository @@ -20,8 +20,6 @@ class DocumentRepository extends BaseRepository { return Document::scope() ->with('user') - ->withTrashed() - ->where('is_deleted', '=', false) ->get(); } @@ -72,48 +70,80 @@ class DocumentRepository extends BaseRepository $documentType = Document::$extensions[$extension]; $filePath = $uploaded->path(); - $fileContents = null; $name = $uploaded->getClientOriginalName(); - if(filesize($filePath)/1000 > env('MAX_DOCUMENT_SIZE', DEFAULT_MAX_DOCUMENT_SIZE)){ + if(filesize($filePath)/1000 > MAX_DOCUMENT_SIZE){ return Response::json([ 'error' => 'File too large', 'code' => 400 ], 400); } - if($documentType == 'image/gif'){ - // Convert gif to png - $img = Image::make($filePath); - - $fileContents = (string) $img->encode('png'); - $documentType = 'image/png'; - $name = pathinfo($name)['filename'].'.png'; - } - $documentTypeData = Document::$types[$documentType]; - - $hash = $fileContents?sha1($fileContents):sha1_file($filePath); + $hash = sha1_file($filePath); $filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentTypeData['extension']; $document = Document::createNew(); $disk = $document->getDisk(); if(!$disk->exists($filename)){// Have we already stored the same file - $disk->put($filename, $fileContents?$fileContents:file_get_contents($filePath)); + $disk->put($filename, file_get_contents($filePath)); + } + + // This is an image; check if we need to create a preview + if(in_array($documentType, array('image/jpeg','image/png','image/gif','image/bmp','image/tiff'))){ + $makePreview = false; + $imageSize = getimagesize($filePath); + $imgManagerConfig = array(); + if(in_array($documentType, array('image/gif','image/bmp','image/tiff'))){ + // Needs to be converted + $makePreview = true; + } else { + if($imageSize[0] > DOCUMENT_PREVIEW_SIZE || $imageSize[1] > DOCUMENT_PREVIEW_SIZE){ + $makePreview = true; + } + } + + if($documentType == 'image/bmp' || $documentType == 'image/tiff'){ + if(!class_exists('Imagick')){ + // Cant't read this + $makePreview = false; + } else { + $imgManagerConfig['driver'] = 'imagick'; + } + } + + if($makePreview){ + $previewType = 'jpg'; + if(in_array($documentType, array('image/png','image/gif','image/bmp','image/tiff'))){ + // Has transparency + $previewType = 'png'; + } + + $document->preview = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentTypeData['extension'].'.x'.DOCUMENT_PREVIEW_SIZE.'.'.$previewType; + if(!$disk->exists($document->preview)){ + // We haven't created a preview yet + $imgManager = new ImageManager($imgManagerConfig); + + $img = $imgManager->make($filePath); + $img->fit(DOCUMENT_PREVIEW_SIZE, DOCUMENT_PREVIEW_SIZE, function ($constraint) { + $constraint->upsize(); + }); + + $previewContent = (string) $img->encode($previewType); + $disk->put($document->preview, $previewContent); + } + } } $document->path = $filename; $document->type = $documentType; - $document->size = $fileContents?strlen($fileContents):filesize($filePath); + $document->size = filesize($filePath); $document->name = substr($name, -255); - if(!empty($documentTypeData['image'])){ - $imageSize = getimagesize($filePath); - if($imageSize){ - $document->width = $imageSize[0]; - $document->height = $imageSize[1]; - } + if(!empty($imageSize)){ + $document->width = $imageSize[0]; + $document->height = $imageSize[1]; } $document->save(); diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index a2b94af80e..d69df1e2ae 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -399,7 +399,8 @@ class InvoiceRepository extends BaseRepository $invoice->invoice_items()->forceDelete(); } - foreach ($data['documents'] as $document_id){ + $document_ids = !empty($data['documents'])?array_map('intval', $data['documents']):array();; + foreach ($document_ids as $document_id){ $document = Document::scope($document_id)->first(); if($document && !$checkSubPermissions || $document->canEdit()){ $document->invoice_id = $invoice->id; @@ -408,7 +409,7 @@ class InvoiceRepository extends BaseRepository } foreach ($invoice->documents as $document){ - if(!in_array($document->id, $data['documents'])){ + if(!in_array($document->public_id, $document_ids)){ // Removed if(!$checkSubPermissions || $document->canEdit()){ $document->delete(); diff --git a/composer.json b/composer.json index 45e6e18726..9a52431d45 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,9 @@ "maatwebsite/excel": "~2.0", "ezyang/htmlpurifier": "~v4.7", "cerdic/css-tidy": "~v1.5", - "asgrim/ofxparser": "^1.1" + "asgrim/ofxparser": "^1.1", + "league/flysystem-aws-s3-v3": "~1.0", + "league/flysystem-rackspace": "~1.0" }, "require-dev": { "phpunit/phpunit": "~4.0", diff --git a/composer.lock b/composer.lock index 6ce01e65ba..74b6ceefec 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": "e5e8524886bd38794a15e406acc3745a", - "content-hash": "6b3f343959ba3f330c425325574dfe28", + "hash": "aa7451471a31552d5d6dce491ec20272", + "content-hash": "d70446a01a12bb41eece33b979adc975", "packages": [ { "name": "agmscode/omnipay-agms", @@ -321,6 +321,86 @@ ], "time": "2015-12-11 11:08:57" }, + { + "name": "aws/aws-sdk-php", + "version": "3.17.1", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "f8c0cc9357e10896a5c57104f2c79d1b727d97d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f8c0cc9357e10896a5c57104f2c79d1b727d97d0", + "reference": "f8c0cc9357e10896a5c57104f2c79d1b727d97d0", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "~5.3|~6.0.1|~6.1", + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "~1.0", + "mtdowling/jmespath.php": "~2.2", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-json": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "nette/neon": "^2.3", + "phpunit/phpunit": "~4.0|~5.0", + "psr/cache": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2016-03-22 19:19:22" + }, { "name": "barryvdh/laravel-debugbar", "version": "v2.2.0", @@ -579,7 +659,7 @@ "laravel" ], "abandoned": "OpenSkill/Datatable", - "time": "2015-04-29 07:00:36" + "time": "2015-11-23 21:33:41" }, { "name": "classpreloader/classpreloader", @@ -3056,6 +3136,100 @@ ], "time": "2016-03-14 21:54:11" }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/595e24678bf78f8107ebc9355d8376ae0eb712c6", + "reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.0.0", + "league/flysystem": "~1.0", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "time": "2015-11-19 08:44:16" + }, + { + "name": "league/flysystem-rackspace", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-rackspace.git", + "reference": "ba877e837f5dce60e78a0555de37eb9bfc7dd6b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-rackspace/zipball/ba877e837f5dce60e78a0555de37eb9bfc7dd6b9", + "reference": "ba877e837f5dce60e78a0555de37eb9bfc7dd6b9", + "shasum": "" + }, + "require": { + "league/flysystem": "~1.0", + "php": ">=5.4.0", + "rackspace/php-opencloud": "~1.16" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\Rackspace\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for Rackspace", + "time": "2016-03-11 12:13:42" + }, { "name": "league/fractal", "version": "0.13.0", @@ -3585,6 +3759,33 @@ ], "time": "2015-07-14 19:53:54" }, + { + "name": "mikemccabe/json-patch-php", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/mikemccabe/json-patch-php.git", + "reference": "b3af30a6aec7f6467c773cd49b2d974a70f7c0d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikemccabe/json-patch-php/zipball/b3af30a6aec7f6467c773cd49b2d974a70f7c0d4", + "reference": "b3af30a6aec7f6467c773cd49b2d974a70f7c0d4", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "mikemccabe\\JsonPatch\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "description": "Produce and apply json-patch objects", + "time": "2015-01-05 21:19:54" + }, { "name": "monolog/monolog", "version": "1.18.1", @@ -3707,6 +3908,61 @@ ], "time": "2016-01-26 21:23:30" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "192f93e43c2c97acde7694993ab171b3de284093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/192f93e43c2c97acde7694993ab171b3de284093", + "reference": "192f93e43c2c97acde7694993ab171b3de284093", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2016-01-05 18:25:05" + }, { "name": "nesbot/carbon", "version": "1.21.0", @@ -5906,6 +6162,63 @@ ], "time": "2016-03-09 05:03:14" }, + { + "name": "rackspace/php-opencloud", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/rackspace/php-opencloud.git", + "reference": "d6b71feed7f9e7a4b52e0240a79f06473ba69c8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rackspace/php-opencloud/zipball/d6b71feed7f9e7a4b52e0240a79f06473ba69c8c", + "reference": "d6b71feed7f9e7a4b52e0240a79f06473ba69c8c", + "shasum": "" + }, + "require": { + "guzzle/guzzle": "~3.8", + "mikemccabe/json-patch-php": "~0.1", + "php": ">=5.4", + "psr/log": "~1.0" + }, + "require-dev": { + "apigen/apigen": "~4.0", + "fabpot/php-cs-fixer": "1.0.*@dev", + "jakub-onderka/php-parallel-lint": "0.*", + "phpspec/prophecy": "~1.4", + "phpunit/phpunit": "4.3.*", + "satooshi/php-coveralls": "0.6.*@dev" + }, + "type": "library", + "autoload": { + "psr-0": { + "OpenCloud": [ + "lib/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Jamie Hannaford", + "email": "jamie.hannaford@rackspace.com", + "homepage": "https://github.com/jamiehannaford" + } + ], + "description": "PHP SDK for Rackspace/OpenStack APIs", + "keywords": [ + "Openstack", + "nova", + "opencloud", + "rackspace", + "swift" + ], + "time": "2016-01-29 10:34:57" + }, { "name": "samvaughton/omnipay-barclays-epdq", "version": "2.2.0", diff --git a/config/filesystems.php b/config/filesystems.php index d0c683352b..da16e0e167 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -62,7 +62,7 @@ return [ 'driver' => 's3', 'key' => env('S3_KEY', ''), 'secret' => env('S3_SECRET', ''), - 'region' => env('S3_REGION', ''), + 'region' => env('S3_REGION', 'us-east-1'), 'bucket' => env('S3_BUCKET', ''), ], @@ -72,7 +72,7 @@ return [ 'key' => env('RACKSPACE_KEY', ''), 'container' => env('RACKSPACE_CONTAINER', ''), 'endpoint' => env('RACKSPACE_ENDPOINT', 'https://identity.api.rackspacecloud.com/v2.0/'), - 'region' => env('RACKSPACE_REGION', 'https://identity.api.rackspacecloud.com/v2.0/'), + 'region' => env('RACKSPACE_REGION', 'IAD'), 'url_type' => env('RACKSPACE_URL_TYPE', 'publicURL') ], diff --git a/database/migrations/2016_03_22_168362_add_documents.php b/database/migrations/2016_03_22_168362_add_documents.php index 811fb3a1a5..f37ab0e859 100644 --- a/database/migrations/2016_03_22_168362_add_documents.php +++ b/database/migrations/2016_03_22_168362_add_documents.php @@ -28,6 +28,7 @@ class AddDocuments extends Migration { $t->unsignedInteger('invoice_id')->nullable(); $t->unsignedInteger('expense_id')->nullable(); $t->string('path'); + $t->string('preview'); $t->string('name'); $t->string('type'); $t->string('disk'); @@ -36,7 +37,6 @@ class AddDocuments extends Migration { $t->unsignedInteger('height')->nullable(); $t->timestamps(); - $t->softDeletes(); $t->foreign('account_id')->references('id')->on('accounts'); $t->foreign('user_id')->references('id')->on('users'); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 59c662ccb5..0a9810d416 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -932,7 +932,7 @@ }, acceptedFiles:{!! json_encode(implode(',',array_keys(\App\Models\Document::$types))) !!}, addRemoveLinks:true, - maxFileSize:{{floatval(env('MAX_DOCUMENT_SIZE', DEFAULT_MAX_DOCUMENT_SIZE)/1000)}}, + maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, dictDefaultMessage:{!! json_encode(trans('texts.document_upload_message')) !!} }); dropzone.on("addedfile",handleDocumentAdded); @@ -948,7 +948,7 @@ public_id:document.public_id(), status:Dropzone.SUCCESS, accepted:true, - url:document.url(), + url:document.preview_url()||document.url(), mock:true, index:i }; @@ -956,7 +956,7 @@ dropzone.emit('addedfile', mockFile); dropzone.emit('complete', mockFile); if(document.type().match(/image.*/)){ - dropzone.emit('thumbnail', mockFile, document.url()); + dropzone.emit('thumbnail', mockFile, document.preview_url()||document.url()); } dropzone.files.push(mockFile); } From 942f543bbb6b3d73319dc107cd65fda518fbfc13 Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Wed, 23 Mar 2016 18:40:42 -0400 Subject: [PATCH 033/122] Display documents in invoice PDF --- Gruntfile.js | 2 +- app/Http/Controllers/AccountController.php | 5 +++ app/Http/Controllers/DocumentController.php | 25 +++++++++++ app/Http/Controllers/InvoiceController.php | 2 +- .../Controllers/PublicClientController.php | 44 ++++++++++++++++++- app/Http/routes.php | 4 +- app/Models/Document.php | 8 ++++ app/Ninja/Repositories/DocumentRepository.php | 35 ++++++++++++--- app/Ninja/Repositories/InvoiceRepository.php | 2 +- .../2016_03_22_168362_add_documents.php | 6 ++- public/built.js | 35 +++++++++++++-- public/js/pdf.pdfmake.js | 35 +++++++++++++-- public/js/{vfs_fonts.js => vfs.js} | 7 ++- public/pdf.built.js | 7 ++- resources/lang/en/texts.php | 5 ++- .../views/accounts/invoice_design.blade.php | 2 + resources/views/invoices/edit.blade.php | 13 ++++-- resources/views/invoices/history.blade.php | 7 ++- resources/views/invoices/pdf.blade.php | 2 +- resources/views/invoices/view.blade.php | 9 ++-- 20 files changed, 222 insertions(+), 33 deletions(-) rename public/js/{vfs_fonts.js => vfs.js} (52%) diff --git a/Gruntfile.js b/Gruntfile.js index 0b004f4ba5..ce22ba6de5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -171,7 +171,7 @@ module.exports = function(grunt) { 'public/js/pdf_viewer.js', 'public/js/compatibility.js', 'public/js/pdfmake.min.js', - 'public/js/vfs_fonts.js', + 'public/js/vfs.js', ], dest: 'public/pdf.built.js', nonull: true diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 912ade56f9..6d06a26110 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -347,6 +347,7 @@ class AccountController extends BaseController $client = new stdClass(); $contact = new stdClass(); $invoiceItem = new stdClass(); + $document = new stdClass(); $client->name = 'Sample Client'; $client->address1 = trans('texts.address1'); @@ -372,8 +373,11 @@ class AccountController extends BaseController $invoiceItem->notes = 'Notes'; $invoiceItem->product_key = 'Item'; + $document->base64 = 'data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAyAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNBCUAAAAAABAAAAAAAAAAAAAAAAAAAAAA/+4AIUFkb2JlAGTAAAAAAQMAEAMDBgkAAAW8AAALrQAAEWf/2wCEAAgGBgYGBggGBggMCAcIDA4KCAgKDhANDQ4NDRARDA4NDQ4MEQ8SExQTEg8YGBoaGBgjIiIiIycnJycnJycnJycBCQgICQoJCwkJCw4LDQsOEQ4ODg4REw0NDg0NExgRDw8PDxEYFhcUFBQXFhoaGBgaGiEhICEhJycnJycnJycnJ//CABEIAGQAlgMBIgACEQEDEQH/xADtAAABBQEBAAAAAAAAAAAAAAAAAQIDBAUGBwEBAAMBAQEAAAAAAAAAAAAAAAIDBAUBBhAAAQQCAQMDBQEAAAAAAAAAAgABAwQRBRIQIBMwIQYxIiMUFUARAAIBAgMFAwgHBwUBAAAAAAECAwARIRIEMUFRYROhIkIgcYGRsdFSIzDBMpKyFAVA4WJyM0MkUPGiU3OTEgABAgQBCQYEBwAAAAAAAAABEQIAITESAyBBUWFxkaGxIhAwgdEyE8HxYnLw4UJSgiMUEwEAAgIBAwQCAwEBAAAAAAABABEhMVFBYXEQgZGhILEwwdHw8f/aAAwDAQACEQMRAAAA9ScqiDlGjgRUUcqSCOVfTEeETZI/TABQBHCxAiDmcvz1O3rM7i7HG29J1nGW6c/ZO4i1ry9ZZwJOzk2Gc11N8YVe6FsZKEQqwR8v0vnEpz4isza7FaovCjNThxulztSxiz6597PwkfQ99R6vxT0S7N2yuXJpQceKrkIq3L9kK/OuR9F8rpjCsmdZXLUN+H0Obp9Hp8azkdPd1q58T21bV6XK6dcjW2UPGl0amXp5VdnIV3c5n6t508/srbbd+3Hbl2Ib8GXV2E59tXOvLwNmfv5sueVzWhPqsNggNdcKwOifnXlS4iDvkho4bP8ASEeyPrpZktFYLMbCPudZsNzzcsTdVc5CemqECqHoAEQBABXAOABAGtD0AH//2gAIAQIAAQUB9TkSnkPEFiKNhvcnhfysQuPbJwZijLkNUGZicWCZ3X1DsIRdZZlnKmPMnOImhsWBQSifR/o7sy+5fb0OIuU8EblCBxtFGQv14ssdjQxMXqf/2gAIAQMAAQUB9Qa5LwxipBck8bMjIY0BsXYJ4Q2QT2BdFK7uMGW/QJmKIo5OrimGZ0MDm4xjEw+PMhDibBi7Y6DjkIkT/iZn8uEzoSLBYdE7dcrzGmkFn68nx6n/2gAIAQEAAQUB9HCwsLHq5XJkxC/+ByZmsbSpCi2JG3GOM68rcOZOuU7IJuRJ+uFjsd8K1tCE55wIYpBYqrzHIAQlKdmty5KG6POC2RSTXwjUGxm8ywsLHX6KMJLrXNdLXCarQd4jeY5ZrHmLYwk0Vo5k85FJZlPjTOxYDySNa2H4wpTNYrLHZKQxhHJsHGzYsRFHe17KbYHI5tVZeGlxI67yOZmTx2wYbDpmsSu9iKCL49M/DtswNZrjb2GvjtW9XsY/EKliOSQXAXnaubRQ2JWoNJWvXbu1G0FmS0MOur+L+VPKNGs0FzvvaSjZUma8xwX5isVyhUFOWwUGg2LtV+OiSOnLAMNeig1tJ1Jr5RNor9Zq91pHz12N0dfTCtvbkcl7f6xr/wAjjvUKW3LgWv2VlRaXVg8NWnHG1aBNBaFmmtiQVDIJIJIyCyYEF1ibDSms9NlUa/THY7vXtb2tSzshj+JbBF8TeI/2vklNVvkVOeV61ck9SB1+qQLx3UVa9C47HDhHDJKEQw2eS5LKz0wzqbX1LCsfF6Mqajv6S/s7eurtmbeRg/EeS5LKyjCORnpCzxxNGsrksrKysrKysrKysrKysrKysrPXK917r3Xuvde/rf/aAAgBAgIGPwHvOlq6z0t3wbnNAFWg1+mS84LiQC6drJgfCJYTrf3UHlxhWA1T8GJ5KEF1aRb7YaD6cNovcmcn5xPDnXq6o9QaIQ9Z1S/OC3OyfgckXL/FxaeESBHjAkvARd7RxGNVtLgNJatYH+XG9p6+k9LdgFF2Q9uJhh7gJoUcQaEKoO8QUUJUGRG3slFSDrhQVifHsuY8jV6m7s3hDi9rsIn9Y6mH7tEe5h4oQuDNN2YIDDnPdc5yUCBBSU8jRsiuReGNu0pPvf/aAAgBAwIGPwHvFdLnEq6awBXWUhC8LojqcIlkETU6NEI5xJGq3eYJYiCpJQecJ7hI0Ycod/SVdS4pxcnKFb0pWrifhxgPUFuJ0+I05CgpEgHbacYAMytEoBXq+cG1zcMlM1x5+UTMzUhGkmEtKZ86iGNCMa1yyElHLtF1FnsijXN+kDdmi1zS3OLgUWJIn0JyHYhA5GJG7VQwhGZdkIM2Qh6vunzi4MC7Sm7IRe9//9oACAEBAQY/Af2u18eH7Bjsq2bO3wpjQUrldsRED3wvxGlkGpbvYAtgQeOHDzVYTdf+I7f+N/ZXcYX4Gx/CQeysYwfM1vxCspRkPP3j6MxQAYYGR9noG+i+q1Dtw8CUrRfNP2sO6gA8TE7qkeRMkUpvfHPMeWw5aMussuXBIr7uYW/qoJFpgzHYcAMOdXkyIN1+9b0sbVkXW7d+FhblsrLJKGTaGAC+uu4Q5pV1GQxObBk8J3X+g6rgvcmwZssY5ALiaZxNg7fZC4JzBONXn62olH/YTl7KJy5kG24GUEbBYbbbhXXDBpVwyKLqF3hicMaPX06cdpAvzzHGm6EkcEY4WUdgzH0CssbjUMONx3ud8ppRPpelN4Zdg9GXbSZFjY+IsQT90mo5XcRMD0mVAtrfFaszsGK3ubANy+ztxqOXiMfP5TPJgqgsTyFGXTuNPBISVVw5w43AIpfzMqzq++KS34lwodXSl5PCSc/Ze1dOJQFawyLhbje9hQSR3aTeLgKvIZb+2nZ5cbd1AM3o3UhddgtfxYbMBWWOMkbl/wBsTV54nEe0KFbtNArkj4bj7GolXTL8Ze1z671G6SNK4/qxnvxm+BymwtUulP8AbN18x8qSC9uopW/npYtVozLHGMomgN8Bh9miA/SnA7okGUE8G3dtG36fKrn+7G90B4gi+FWnMmYWsxxJvwzWvsoxh2yri4Pd5bi9Hpl5bDFU7q+ktc9lHoBQvEkAe+o1lkUByEkZTsW/xCpAJzB02ISFLgADZev8zRpqD8QBVv8A6Jann0yNplkFssq9RVIO0MmK7N4oMZBKhPe6FmHZa3qqPKdkdpBwPD6Bpf6L4szqbDmTfCsn6fqGmO54wV9m2upqcyse6WlNvRdhXSzJlOLMDm9GFZNMjytwQfXWX8uYv59nrx9lP+aPUbYFUlFHp2mguqTqxKLJK+LKP/VMfWKvKrsu5y5ZfWmFdTRytAx8UbYdtxQMpDFjhqYflSA7s4XBquttRz2NaunIpR+DeRJqiuYrgq8WOAoaiXVPEzYqkZCKOVt9X1DJPFsvKMp+8hqTStE0Er2xBDobG5FxY40kGi02nifZfMSSfNtr/OlcRHwxKO0A3q8smduDfL/FXTiQCPbbKHHrF6+WbH+B3TsufZRyTSfyu1/usR7ayPKM3wulj2VnAVGOJTZjxBGNZiuVvi+w331wPprLIbkbn7resd013hbz4fupbDYb38iTTE2z7DzGIoJrNN+ZjXDOO61h5rg0mp1Wmkk0yplEDG2Vt5wwNWH+NIdxJj9t1pZ/0/V5WQhk6gvzGI91fP0sesUeKI5W9X7qXTauJ9JM2AWYd0nhermNb+a3srxfeP118qdhyYBhWEkf81jf1Vnim658QfA+giulqUyNwbC/1GiLfLOOU7jypek3d8Q3Vw8r5sKt6PdV4i0Z5Yjtq2k1YmQbI5cfxe+ra39OLD44fd3qXSQaJ0uwJnlFsluFBSb2Fr+TldQw518pynLaO2rli7cT9Q/0r//aAAgBAgMBPxD8BHIj4/gUu+n/AKDL7Eqh2LDnpJp36uxcBVJSQBqzju2/1Mo/rVB3tkuO1ZHHZYne4pQ3+A1jS9SIA5pdrL6FN29E1HHIwAiNNrOl06RtUaBbO7u6gApbHBXuAv3EB7MGADleztFGRKsm7wY7RPX6jyyGlEcPVK65Tfd263KMLBdl5vh/uDZC0O5wdmKVo4YKKAOVMbNnutFAI9eEuQ4e6ahKuKj2+B/en0tbqrHmAfYICaGFNJdQyMh/5uV4l03drL4SfIR6aL1b1BlPXXmNhFlAM7NwL0U7zACUS0VtC3J6+u9zqhb2fqLSlI+JcuIO5SQ4R9ofyf/aAAgBAwMBPxD+RAWF0BeXwHuzQV9CbX26fUGyI3Q+OsxIrVsvtv6l5UovefjcHV637+PwAhSpEW03npcCcYFf6CUJoVSLxaKfBDaWsSw47vyTCEodeVls2/8AUQ7CBsMHauvOIZ9gwKrOdefH4MthVWOO9y9BzaCnDeJ8kzpIwbaLNkqtAQS0QFwTYlN+IQGULuC0pXHSWlpFWocCQV3A4dhwVblrrFrfXSZH08asO7MfiaKWfA2PeN7MUMgK5fu4Urrgge+T6jfLDqw7/wBkMAgG2DxzG9uzsd1xQBRbbbn1ENij2hXaE6AkMCOSsjnKOW/Qai9iTi/5f//aAAgBAQMBPxAIEqVKlSpUCEHoUiRjGX6BAlSpUqIIaIhUI6G34hXMIeiRjE9OkqB63HygG1aCOt3TKzCFkCino59iplOlzY8tvCMIxuwf0/mBqJ40DUb89L4/sgg43QRGuFT0ESVfo0gRlyha0dVlpKlKrm6raQySjYol1lVfgj8C3g6iJbHNxPeAW9yDaQdgrpMZAK1eq2o7Q7EFEVS8X6HaIQYrdr7U0YQobDxRja4mPhsgnSp/cLbjYA4K51OOKoU0zRiegjSEq4oFegvxGpy4QRr5JcRHqajXulVBqlghaxQnLR092G41E0g3djqcHWMXuExr0VmhZdW7FsLT+gynKYpXXjGV7wreJppoapXL7oQD0sBYvCAX4tIpESrHmFyooWQqCbMCN1vpBgtacBgtAYVZcF7afsYf9lQisQlRdvDkWyqGZBthXx7RPvKkUrlb5Q/CrdFT5neoWdIZSWgR/VBQwZ0nUGPeBAJdZvWE38qghbIlumjVcdMzdAL5o/BAVDYFa5xT2qVhDQIAA5pB+5aemryoxhX0jk3pALPvUXhzAK5y/XUnskCEqEqMLSHNUwwLAQBRotLMeIdlDn5FpRZUUm5R2ZJ7EpNZRMobAO5K5hOAUuBYHYG+8SddNHz0+EKEOCcKzlT1BZYb4uB90OpYUAVM2rcL3vCknNK+bjWGKs6bZa9oVhmRdpg/YWAAlUVJkcjdXD11Lgke0VcU2MbHfygaFKWEnTL5GJZzMyGuGMPMbSQlbPagPOZaKOHjusEyaLtXgeW3iK4+oDc4bNYnwcKiQaks/Caxh5wK7kdeZvb3LEJhAMqbKrhAqim522Qv5gPgqp9FxlL7mnZpXi3MxIMgDkG/ug65qHbsEF8zXvjwBFAU4jmwArRmKjV6XLdNd1TvoiF1X5vX/fMHBChWDvd+4paeJz4FDgzLjs70CdhHznQBjzv7Sxo8bd2NfcZmYNWs8RxQGYGe1+olGV9n7Z+0UPFyYwlYvmDNJctGQPGwnyQAWPv0haPhQ4abtsUxZfaFBalqvypK8pGizJpYO+aShBw+h2xgHf3CNeSAXzRnTRxS/szKo3P+IMAszsGE7iUiOwZy99tXZg3BCqz2L+qH0gU09RzxfaMDrstvwgKoDsPRrCLj7jcKSy6oH5pLZC0I+L/UPAvRNDQUa9oMU7aNedH3NWIKBWuO+m4lsAS60VfopKsCajNR6AT7l8D418EaQCisod0YIUK9U/PBh6loQegqKly/QfkBmNzMzM/i+jOk/9k='; + $invoice->client = $client; $invoice->invoice_items = [$invoiceItem]; + $invoice->documents = $account->isPro()?[$document,$document,$document,$document]:[]; $data['account'] = $account; $data['invoice'] = $invoice; @@ -749,6 +753,7 @@ class AccountController extends BaseController $account->hide_paid_to_date = Input::get('hide_paid_to_date') ? true : false; $account->all_pages_header = Input::get('all_pages_header') ? true : false; $account->all_pages_footer = Input::get('all_pages_footer') ? true : false; + $account->invoice_embed_documents = Input::get('invoice_embed_documents') ? true : false; $account->header_font_id = Input::get('header_font_id'); $account->body_font_id = Input::get('body_font_id'); $account->primary_color = Input::get('primary_color'); diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 4b7718bfd8..d19909bae0 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -70,6 +70,31 @@ class DocumentController extends BaseController return $response; } + public function getVFSJS($publicId, $name){ + $document = Document::scope($publicId) + ->firstOrFail(); + + if(substr($name, -3)=='.js'){ + $name = substr($name, 0, -3); + } + + if(!$this->checkViewPermission($document, $response)){ + return $response; + } + + if(substr($document->type, 0, 6) != 'image/'){ + return Response::view('error', array('error'=>'Image does not exist!'), 404); + } + + $content = $document->preview?$document->getRawPreview():$document->getRaw(); + $content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")'; + $response = Response::make($content, 200); + $response->header('content-type', 'text/javascript'); + $response->header('cache-control', 'max-age=31536000'); + + return $response; + } + public function postUpload() { if (!Utils::isPro()) { diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 138909a9e1..8cd6049d0b 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -536,7 +536,7 @@ class InvoiceController extends BaseController public function invoiceHistory($publicId) { $invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail(); - $invoice->load('user', 'invoice_items', 'account.country', 'client.contacts', 'client.country'); + $invoice->load('user', 'invoice_items', 'documents', 'account.country', 'client.contacts', 'client.country'); $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date); $invoice->due_date = Utils::fromSqlDate($invoice->due_date); $invoice->is_pro = Auth::user()->isPro(); diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index d067ff38a7..c854189ac2 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -7,10 +7,12 @@ use URL; use Input; use Utils; use Request; +use Response; use Session; use Datatable; use App\Models\Gateway; use App\Models\Invitation; +use App\Models\Document; use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\ActivityRepository; @@ -22,8 +24,9 @@ class PublicClientController extends BaseController { private $invoiceRepo; private $paymentRepo; + private $documentRepo; - public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, PaymentService $paymentService) + public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, PaymentService $paymentService) { $this->invoiceRepo = $invoiceRepo; $this->paymentRepo = $paymentRepo; @@ -372,5 +375,44 @@ class PublicClientController extends BaseController return $invitation; } + + + + public function getDocumentVFSJS($publicId, $name){ + if (!$invitation = $this->getInvitation()) { + return $this->returnError(); + } + + $clientId = $invitation->invoice->client_id; + $document = Document::scope($publicId, $invitation->account_id)->first(); + + + if(!$document || substr($document->type, 0, 6) != 'image/'){ + return Response::view('error', array('error'=>'Image does not exist!'), 404); + } + + $authorized = false; + if($document->expense && $document->expense->client_id == $invitation->invoice->client_id){ + $authorized = true; + } else if($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id){ + $authorized = true; + } + + if(!$authorized){ + return Response::view('error', array('error'=>'Not authorized'), 403); + } + + if(substr($name, -3)=='.js'){ + $name = substr($name, 0, -3); + } + + $content = $document->preview?$document->getRawPreview():$document->getRaw(); + $content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")'; + $response = Response::make($content, 200); + $response->header('content-type', 'text/javascript'); + $response->header('cache-control', 'max-age=31536000'); + + return $response; + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 6a4970194c..e70444847a 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -47,6 +47,7 @@ Route::group(['middleware' => 'auth:client'], function() { Route::get('client/invoices', 'PublicClientController@invoiceIndex'); Route::get('client/payments', 'PublicClientController@paymentIndex'); Route::get('client/dashboard', 'PublicClientController@dashboard'); + Route::get('client/document/js/{public_id}/{filename}', 'PublicClientController@getDocumentVFSJS'); }); Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable')); @@ -133,6 +134,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::post('recurring_invoices/bulk', 'InvoiceController@bulk'); Route::get('document/{public_id}/{filename?}', 'DocumentController@get'); + Route::get('document/js/{public_id}/{filename}', 'DocumentController@getVFSJS'); Route::get('document/preview/{public_id}/{filename?}', 'DocumentController@getPreview'); Route::post('document', 'DocumentController@postUpload'); @@ -430,7 +432,7 @@ if (!defined('CONTACT_EMAIL')) { define('MAX_LOGO_FILE_SIZE', 200); // KB define('MAX_FAILED_LOGINS', 10); define('MAX_DOCUMENT_SIZE', env('MAX_DOCUMENT_SIZE', 10000));// KB - define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 150));// pixels + define('DOCUMENT_PREVIEW_SIZE', env('DOCUMENT_PREVIEW_SIZE', 300));// pixels define('DEFAULT_FONT_SIZE', 9); define('DEFAULT_HEADER_FONT', 1);// Roboto define('DEFAULT_BODY_FONT', 1);// Roboto diff --git a/app/Models/Document.php b/app/Models/Document.php index f342d1ff08..2d61cb7e3b 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -132,6 +132,14 @@ class Document extends EntityModel return url('document/'.$this->public_id.'/'.$this->name); } + public function getVFSJSUrl(){ + return url('document/js/'.$this->public_id.'/'.$this->name.'.js'); + } + + public function getClientVFSJSUrl(){ + return url('client/document/js/'.$this->public_id.'/'.$this->name.'.js'); + } + public function getPreviewUrl(){ return $this->preview?url('document/preview/'.$this->public_id.'/'.$this->name.'.'.pathinfo($this->preview, PATHINFO_EXTENSION)):null; } diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index 8697a23262..08af5bb577 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -94,12 +94,14 @@ class DocumentRepository extends BaseRepository if(in_array($documentType, array('image/jpeg','image/png','image/gif','image/bmp','image/tiff'))){ $makePreview = false; $imageSize = getimagesize($filePath); + $width = $imageSize[0]; + $height = $imageSize[1]; $imgManagerConfig = array(); if(in_array($documentType, array('image/gif','image/bmp','image/tiff'))){ // Needs to be converted $makePreview = true; } else { - if($imageSize[0] > DOCUMENT_PREVIEW_SIZE || $imageSize[1] > DOCUMENT_PREVIEW_SIZE){ + if($width > DOCUMENT_PREVIEW_SIZE || $height > DOCUMENT_PREVIEW_SIZE){ $makePreview = true; } } @@ -126,14 +128,30 @@ class DocumentRepository extends BaseRepository $imgManager = new ImageManager($imgManagerConfig); $img = $imgManager->make($filePath); - $img->fit(DOCUMENT_PREVIEW_SIZE, DOCUMENT_PREVIEW_SIZE, function ($constraint) { - $constraint->upsize(); - }); + + if($width <= DOCUMENT_PREVIEW_SIZE && $height <= DOCUMENT_PREVIEW_SIZE){ + $previewWidth = $width; + $previewHeight = $height; + } else if($width > $height) { + $previewWidth = DOCUMENT_PREVIEW_SIZE; + $previewHeight = $height * DOCUMENT_PREVIEW_SIZE / $width; + } else { + $previewHeight = DOCUMENT_PREVIEW_SIZE; + $previewWidth = $width * DOCUMENT_PREVIEW_SIZE / $height; + } + + $img->resize($previewWidth, $previewHeight); $previewContent = (string) $img->encode($previewType); $disk->put($document->preview, $previewContent); + $base64 = base64_encode($previewContent); } - } + else{ + $base64 = base64_encode($disk->get($document->preview)); + } + }else{ + $base64 = base64_encode(file_get_contents($filePath)); + } } $document->path = $filename; @@ -147,11 +165,16 @@ class DocumentRepository extends BaseRepository } $document->save(); + $doc_array = $document->toArray(); + if(!empty($base64)){ + $mime = !empty($previewType)?Document::$extensions[$previewType]:$documentType; + $doc_array['base64'] = 'data:'.$mime.';base64,'.$base64; + } return Response::json([ 'error' => false, - 'document' => $document, + 'document' => $doc_array, 'code' => 200 ], 200); } diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index d69df1e2ae..bf9cc59c39 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -600,7 +600,7 @@ class InvoiceRepository extends BaseRepository return false; } - $invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country'); + $invoice->load('user', 'invoice_items', 'documents', 'invoice_design', 'account.country', 'client.contacts', 'client.country'); $client = $invoice->client; if (!$client || $client->is_deleted) { diff --git a/database/migrations/2016_03_22_168362_add_documents.php b/database/migrations/2016_03_22_168362_add_documents.php index f37ab0e859..1fcaea3790 100644 --- a/database/migrations/2016_03_22_168362_add_documents.php +++ b/database/migrations/2016_03_22_168362_add_documents.php @@ -10,14 +10,15 @@ class AddDocuments extends Migration { */ public function up() { - /*Schema::table('accounts', function($table) { + Schema::table('accounts', function($table) { $table->string('logo')->nullable()->default(null); $table->unsignedInteger('logo_width'); $table->unsignedInteger('logo_height'); $table->unsignedInteger('logo_size'); + $table->boolean('invoice_embed_documents')->default(1); }); - DB::table('accounts')->update(array('logo' => ''));*/ + DB::table('accounts')->update(array('logo' => '')); Schema::dropIfExists('documents'); Schema::create('documents', function($t) { @@ -59,6 +60,7 @@ class AddDocuments extends Migration { $table->dropColumn('logo_width'); $table->dropColumn('logo_height'); $table->dropColumn('logo_size'); + $table->dropColumn('invoice_embed_documents'); }); Schema::dropIfExists('documents'); diff --git a/public/built.js b/public/built.js index 8b12b9693f..6a2954d3a7 100644 --- a/public/built.js +++ b/public/built.js @@ -31101,11 +31101,12 @@ function GetPdfMake(invoice, javascript, callback) { function addFont(font){ if(window.ninjaFontVfs[font.folder]){ + folder = 'fonts/'+font.folder; pdfMake.fonts[font.name] = { - normal: font.folder+'/'+font.normal, - italics: font.folder+'/'+font.italics, - bold: font.folder+'/'+font.bold, - bolditalics: font.folder+'/'+font.bolditalics + normal: folder+'/'+font.normal, + italics: folder+'/'+font.italics, + bold: folder+'/'+font.bold, + bolditalics: folder+'/'+font.bolditalics } } } @@ -31136,6 +31137,7 @@ NINJA.decodeJavascript = function(invoice, javascript) 'invoiceDetailsHeight': (NINJA.invoiceDetails(invoice).length * 16) + 16, 'invoiceLineItems': NINJA.invoiceLines(invoice), 'invoiceLineItemColumns': NINJA.invoiceColumns(invoice), + 'invoiceDocuments' : NINJA.invoiceDocuments(invoice), 'quantityWidth': NINJA.quantityWidth(invoice), 'taxWidth': NINJA.taxWidth(invoice), 'clientDetails': NINJA.clientDetails(invoice), @@ -31393,6 +31395,31 @@ NINJA.invoiceLines = function(invoice) { return NINJA.prepareDataTable(grid, 'invoiceItems'); } +NINJA.invoiceDocuments = function(invoice) { + if(!invoice.documents || !invoice.account.invoice_embed_documents)return[]; + var stack = []; + var stackItem = null; + + var j = 0; + for (var i = 0; i < invoice.documents.length; i++) { + var document = invoice.documents[i]; + var path = document.base64; + if(!path && (document.preview_url || document.type == 'image/png' || document.type == 'image/jpeg')){ + path = 'docs/'+document.public_id+'/'+document.name; + } + if(path && (window.pdfMake.vfs[path] || document.base64)){ + if(j%3==0){ + stackItem = {columns:[]}; + stack.push(stackItem); + } + stackItem.columns.push({stack:[{image:path,style:'invoiceDocument',fit:[150,150]}], width:175}) + j++; + } + } + + return {stack:stack}; +} + NINJA.subtotals = function(invoice, hideBalance) { if (!invoice) { diff --git a/public/js/pdf.pdfmake.js b/public/js/pdf.pdfmake.js index f8f30f4b54..6989916a88 100644 --- a/public/js/pdf.pdfmake.js +++ b/public/js/pdf.pdfmake.js @@ -109,11 +109,12 @@ function GetPdfMake(invoice, javascript, callback) { function addFont(font){ if(window.ninjaFontVfs[font.folder]){ + folder = 'fonts/'+font.folder; pdfMake.fonts[font.name] = { - normal: font.folder+'/'+font.normal, - italics: font.folder+'/'+font.italics, - bold: font.folder+'/'+font.bold, - bolditalics: font.folder+'/'+font.bolditalics + normal: folder+'/'+font.normal, + italics: folder+'/'+font.italics, + bold: folder+'/'+font.bold, + bolditalics: folder+'/'+font.bolditalics } } } @@ -144,6 +145,7 @@ NINJA.decodeJavascript = function(invoice, javascript) 'invoiceDetailsHeight': (NINJA.invoiceDetails(invoice).length * 16) + 16, 'invoiceLineItems': NINJA.invoiceLines(invoice), 'invoiceLineItemColumns': NINJA.invoiceColumns(invoice), + 'invoiceDocuments' : NINJA.invoiceDocuments(invoice), 'quantityWidth': NINJA.quantityWidth(invoice), 'taxWidth': NINJA.taxWidth(invoice), 'clientDetails': NINJA.clientDetails(invoice), @@ -401,6 +403,31 @@ NINJA.invoiceLines = function(invoice) { return NINJA.prepareDataTable(grid, 'invoiceItems'); } +NINJA.invoiceDocuments = function(invoice) { + if(!invoice.documents || !invoice.account.invoice_embed_documents)return[]; + var stack = []; + var stackItem = null; + + var j = 0; + for (var i = 0; i < invoice.documents.length; i++) { + var document = invoice.documents[i]; + var path = document.base64; + if(!path && (document.preview_url || document.type == 'image/png' || document.type == 'image/jpeg')){ + path = 'docs/'+document.public_id+'/'+document.name; + } + if(path && (window.pdfMake.vfs[path] || document.base64)){ + if(j%3==0){ + stackItem = {columns:[]}; + stack.push(stackItem); + } + stackItem.columns.push({stack:[{image:path,style:'invoiceDocument',fit:[150,150]}], width:175}) + j++; + } + } + + return {stack:stack}; +} + NINJA.subtotals = function(invoice, hideBalance) { if (!invoice) { diff --git a/public/js/vfs_fonts.js b/public/js/vfs.js similarity index 52% rename from public/js/vfs_fonts.js rename to public/js/vfs.js index 76e2d87ee4..fcdb3d2061 100644 --- a/public/js/vfs_fonts.js +++ b/public/js/vfs.js @@ -3,7 +3,12 @@ if(window.ninjaFontVfs)ninjaLoadFontVfs(); function ninjaLoadFontVfs(){ jQuery.each(window.ninjaFontVfs, function(font, files){ jQuery.each(files, function(filename, file){ - window.pdfMake.vfs[font+'/'+filename] = file; + window.pdfMake.vfs['fonts/'+font+'/'+filename] = file; }); }) +} +function ninjaAddVFSDoc(name,content){ + window.pdfMake.vfs['docs/'+name] = content; + if(window.refreshPDF)refreshPDF(true); + jQuery(document).trigger('ninjaVFSDocAdded'); } \ No newline at end of file diff --git a/public/pdf.built.js b/public/pdf.built.js index 9b663502b7..892149f88b 100644 --- a/public/pdf.built.js +++ b/public/pdf.built.js @@ -7926,7 +7926,12 @@ if(window.ninjaFontVfs)ninjaLoadFontVfs(); function ninjaLoadFontVfs(){ jQuery.each(window.ninjaFontVfs, function(font, files){ jQuery.each(files, function(filename, file){ - window.pdfMake.vfs[font+'/'+filename] = file; + window.pdfMake.vfs['fonts/'+font+'/'+filename] = file; }); }) +} +function ninjaAddVFSDoc(name,content){ + window.pdfMake.vfs['docs/'+name] = content; + if(window.refreshPDF)refreshPDF(true); + jQuery(document).trigger('ninjaVFSDocAdded'); } \ No newline at end of file diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index b855b65c9a..284ad739d8 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1099,8 +1099,9 @@ $LANG = array( // Documents 'invoice_documents' => 'Attached Documents', - 'document_upload_message' => 'Drop files here or click to upload.' - + 'document_upload_message' => 'Drop files here or click to upload.', + 'invoice_embed_documents' => 'Embed Documents', + 'invoice_embed_documents_help' => 'Include attached images in the invoice.', ); return $LANG; diff --git a/resources/views/accounts/invoice_design.blade.php b/resources/views/accounts/invoice_design.blade.php index 4af173818d..9576afbf1d 100644 --- a/resources/views/accounts/invoice_design.blade.php +++ b/resources/views/accounts/invoice_design.blade.php @@ -48,6 +48,7 @@ function getPDFString(cb) { invoice.is_pro = {!! Auth::user()->isPro() ? 'true' : 'false' !!}; invoice.account.hide_quantity = $('#hide_quantity').is(":checked"); + invoice.account.invoice_embed_documents = $('#invoice_embed_documents').is(":checked"); invoice.account.hide_paid_to_date = $('#hide_paid_to_date').is(":checked"); invoice.invoice_design_id = $('#invoice_design_id').val(); @@ -207,6 +208,7 @@ {!! Former::checkbox('hide_quantity')->text(trans('texts.hide_quantity_help')) !!} {!! Former::checkbox('hide_paid_to_date')->text(trans('texts.hide_paid_to_date_help')) !!} + {!! Former::checkbox('invoice_embed_documents')->text(trans('texts.invoice_embed_documents_help')) !!} diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 0a9810d416..bf2ce391c6 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -270,7 +270,7 @@
  • {{ trans("texts.{$entityType}_terms") }}
  • {{ trans("texts.{$entityType}_footer") }}
  • - @if (Auth::user()->account->isPro()) + @if ($account->isPro())
  • {{ trans("texts.{$entityType}_documents") }}
  • @endif @@ -304,7 +304,7 @@ ') !!} - @if (Auth::user()->account->isPro()) + @if ($account->isPro())
    @@ -1319,7 +1319,7 @@ model.invoice().invoice_number(number); } - @if (Auth::user()->account->isPro()) + @if ($account->isPro()) function handleDocumentAdded(file){ if(file.mock)return; file.index = model.invoice().documents().length; @@ -1328,14 +1328,21 @@ function handleDocumentRemoved(file){ model.invoice().removeDocument(file.public_id); + refreshPDF(true); } function handleDocumentUploaded(file, response){ file.public_id = response.document.public_id model.invoice().documents()[file.index].update(response.document); + refreshPDF(true); } @endif + @if ($account->isPro() && $account->invoice_embed_documents) + @foreach ($invoice->documents as $document) + + @endforeach + @endif @stop diff --git a/resources/views/invoices/history.blade.php b/resources/views/invoices/history.blade.php index 3a9fd36e33..19d7eeff52 100644 --- a/resources/views/invoices/history.blade.php +++ b/resources/views/invoices/history.blade.php @@ -56,5 +56,10 @@
     
    @include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800]) - + + @if (Auth::user()->account->isPro() && Auth::user()->account->invoice_embed_documents) + @foreach ($invoice->documents as $document) + + @endforeach + @endif @stop \ No newline at end of file diff --git a/resources/views/invoices/pdf.blade.php b/resources/views/invoices/pdf.blade.php index 2c4d97cf97..1c8389affe 100644 --- a/resources/views/invoices/pdf.blade.php +++ b/resources/views/invoices/pdf.blade.php @@ -123,7 +123,7 @@ $('#theFrame').attr('src', string).show(); } else { if (isRefreshing) { - //needsRefresh = true; + needsRefresh = true; return; } isRefreshing = true; diff --git a/resources/views/invoices/view.blade.php b/resources/views/invoices/view.blade.php index c1ba382289..2ad564f9fd 100644 --- a/resources/views/invoices/view.blade.php +++ b/resources/views/invoices/view.blade.php @@ -45,7 +45,11 @@ @endif

     

    - + @if ($account->isPro() && $account->invoice_embed_documents) + @foreach ($invoice->documents as $document) + + @endforeach + @endif From e3c59101ba31485be1daa968cdcf66c517898433 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 24 Mar 2016 09:14:27 +0200 Subject: [PATCH 039/122] Fixed invitation iframe URL --- app/Models/Invitation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index 2cc7102957..7abd074a08 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -40,7 +40,7 @@ class Invitation extends EntityModel if ($this->account->isPro()) { if ($iframe_url) { - return "{$iframe_url}/?{$this->invitation_key}"; + return "{$iframe_url}?{$this->invitation_key}"; } elseif ($this->account->subdomain) { $url = Utils::replaceSubdomain($url, $this->account->subdomain); } From 1c5c45a1e19e6fa87af231afe1fc4046c3ff987a Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Thu, 24 Mar 2016 11:33:28 -0400 Subject: [PATCH 040/122] Improved handling of various document types; better documents zip --- app/Http/Controllers/DocumentController.php | 10 +- .../Controllers/PublicClientController.php | 128 +++++++++--------- app/Models/Document.php | 79 ++++++++--- app/Ninja/Repositories/DocumentRepository.php | 32 +++-- app/Providers/AppServiceProvider.php | 6 + public/built.js | 6 +- public/js/pdf.pdfmake.js | 6 +- resources/lang/en/texts.php | 2 +- resources/views/invoices/edit.blade.php | 19 ++- resources/views/invoices/history.blade.php | 4 +- resources/views/invoices/view.blade.php | 8 +- 11 files changed, 183 insertions(+), 117 deletions(-) diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 6c824dab61..593ab595f3 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -46,7 +46,7 @@ class DocumentController extends BaseController if($stream){ $headers = [ - 'Content-Type' => $document->type, + 'Content-Type' => Document::$types[$document->type]['mime'], 'Content-Length' => $document->size, ]; @@ -56,7 +56,7 @@ class DocumentController extends BaseController } else{ $response = Response::make($document->getRaw(), 200); - $response->header('content-type', $document->type); + $response->header('content-type', Document::$types[$document->type]['mime']); } return $response; @@ -80,9 +80,9 @@ class DocumentController extends BaseController return redirect($direct_url); } - $extension = pathinfo($document->preview, PATHINFO_EXTENSION); + $previewType = pathinfo($document->preview, PATHINFO_EXTENSION); $response = Response::make($document->getRawPreview(), 200); - $response->header('content-type', Document::$extensions[$extension]); + $response->header('content-type', Document::$types[$previewType]['mime']); return $response; } @@ -99,7 +99,7 @@ class DocumentController extends BaseController return $response; } - if(substr($document->type, 0, 6) != 'image/'){ + if(!$document->isPDFEmbeddable()){ return Response::view('error', array('error'=>'Image does not exist!'), 404); } diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index 6c2ad8cd49..7aca779532 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -19,8 +19,7 @@ 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; +use Barracuda\ArchiveStream\ZipArchive; class PublicClientController extends BaseController { @@ -138,8 +137,13 @@ class PublicClientController extends BaseController 'phantomjs' => Input::has('phantomjs'), ); - if($account->isPro() && $this->canCreateInvoiceDocsZip($invoice)){ - $data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}"); + if($account->isPro() && $this->canCreateZip()){ + $zipDocs = $this->getInvoiceZipDocuments($invoice, $size); + + if(count($zipDocs) > 1){ + $data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}"); + $data['documentsZipSize'] = $size; + } } return View::make('invoices.view', $data); @@ -172,6 +176,12 @@ class PublicClientController extends BaseController return $paymentTypes; } + + protected function humanFilesize($bytes, $decimals = 2) { + $size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB'); + $factor = floor((strlen($bytes) - 1) / 3); + return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$size[$factor]; + } public function download($invitationKey) { @@ -391,7 +401,7 @@ class PublicClientController extends BaseController $document = Document::scope($publicId, $invitation->account_id)->first(); - if(!$document || substr($document->type, 0, 6) != 'image/'){ + if(!$document->isPDFEmbeddable()){ return Response::view('error', array('error'=>'Image does not exist!'), 404); } @@ -423,19 +433,43 @@ class PublicClientController extends BaseController return function_exists('gmp_init'); } - protected function canCreateInvoiceDocsZip($invoice){ - if(!$this->canCreateZip())return false; - if(count($invoice->documents) == 1)return false; - + protected function getInvoiceZipDocuments($invoice, &$size=0){ + $documents = $invoice->documents->sortBy('size'); + + $size = 0; $maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000; - $i = 0; - foreach($invoice->documents as $document){ - if($document->size <= $maxSize)$i++; - if($i > 1){ - return true; + $toZip = array(); + foreach($documents as $document){ + if($size + $document->size > $maxSize)break; + + if(!empty($toZip[$document->name])){ + // This name is taken + if($toZip[$document->name]->hash != $document->hash){ + // 2 different files with the same name + $nameInfo = pathinfo($document->name); + + for($i = 1;; $i++){ + $name = $nameInfo['filename'].' ('.$i.').'.$nameInfo['extension']; + + if(empty($toZip[$name])){ + $toZip[$name] = $document; + $size += $document->size; + break; + } else if ($toZip[$name]->hash == $document->hash){ + // We're not adding this after all + break; + } + } + + } + } + else{ + $toZip[$document->name] = $document; + $size += $document->size; } } - return false; + + return $toZip; } public function getInvoiceDocumentsZip($invitationKey){ @@ -451,64 +485,24 @@ class PublicClientController extends BaseController 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); - } - } + $toZip = $this->getInvoiceZipDocuments($invoice); 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'); + $zip = new 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()); - } + foreach($toZip as $name=>$document){ + $fileStream = $document->getStream(); + if($fileStream){ + $zip->init_file_stream_transfer($name, $document->size, array('time'=>$document->created_at->timestamp)); + while ($buffer = fread($fileStream, 256000))$zip->stream_file_part($buffer); + fclose($fileStream); + $zip->complete_file_stream(); + } + else{ + $zip->add_file($name, $document->getRaw()); } } $zip->finish(); diff --git a/app/Models/Document.php b/app/Models/Document.php index 92d3e79439..e1f01b5842 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -5,31 +5,68 @@ use DB; class Document extends EntityModel { - public static $extensions = array( - 'png' => 'image/png', - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'pdf' => 'application/pdf', - 'gif' => 'image/gif' + public static $extraExtensions = array( + 'jpg' => 'jpeg', + 'tif' => 'tiff', + ); + + public static $allowedMimes = array(// Used by Dropzone.js; does not affect what the server accepts + 'image/png', 'image/jpeg', 'image/tiff', 'application/pdf', 'image/gif', 'image/vnd.adobe.photoshop', 'text/plain', + 'application/zip', 'application/msword', + 'application/excel', 'application/vnd.ms-excel', 'application/x-excel', 'application/x-msexcel', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','application/postscript', 'image/svg+xml', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.ms-powerpoint', ); public static $types = array( - 'image/png' => array( - 'extension' => 'png', + 'png' => array( + 'mime' => 'image/png', ), - 'image/jpeg' => array( - 'extension' => 'jpeg', + 'ai' => array( + 'mime' => 'application/postscript', ), - 'image/tiff' => array( - 'extension' => 'tiff', + 'svg' => array( + 'mime' => 'image/svg+xml', ), - 'image/gif' => array( - 'extension' => 'gif', + 'jpeg' => array( + 'mime' => 'image/jpeg', ), - 'application/pdf' => array( - 'extension' => 'pdf', + 'tiff' => array( + 'mime' => 'image/tiff', + ), + 'pdf' => array( + 'mime' => 'application/pdf', + ), + 'gif' => array( + 'mime' => 'image/gif', + ), + 'psd' => array( + 'mime' => 'image/vnd.adobe.photoshop', + ), + 'txt' => array( + 'mime' => 'text/plain', + ), + 'zip' => array( + 'mime' => 'application/zip', + ), + 'doc' => array( + 'mime' => 'application/msword', + ), + 'xls' => array( + 'mime' => 'application/vnd.ms-excel', + ), + 'ppt' => array( + 'mime' => 'application/vnd.ms-powerpoint', + ), + 'xlsx' => array( + 'mime' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ), + 'docx' => array( + 'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ), + 'pptx' => array( + 'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', ), ); @@ -142,11 +179,17 @@ class Document extends EntityModel return url('client/document/'.$invitation->invitation_key.'/'.$this->public_id.'/'.$this->name); } + public function isPDFEmbeddable(){ + return $this->type == 'jpeg' || $this->type == 'png' || $this->preview; + } + public function getVFSJSUrl(){ + if(!$this->isPDFEmbeddable())return null; return url('document/js/'.$this->public_id.'/'.$this->name.'.js'); } public function getClientVFSJSUrl(){ + if(!$this->isPDFEmbeddable())return null; return url('client/document/js/'.$this->public_id.'/'.$this->name.'.js'); } diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index 2ce58720de..ce4c7fd9d8 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -61,14 +61,22 @@ class DocumentRepository extends BaseRepository $uploaded = $input['file']; $extension = strtolower($uploaded->extension()); - if(empty(Document::$extensions[$extension])){ + if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){ + $documentType = Document::$extraExtensions[$extension]; + } + else{ + $documentType = $extension; + } + + if(empty(Document::$types[$documentType])){ return Response::json([ 'error' => 'Unsupported extension', 'code' => 400 ], 400); } - - $documentType = Document::$extensions[$extension]; + + $documentTypeData = Document::$types[$documentType]; + $filePath = $uploaded->path(); $name = $uploaded->getClientOriginalName(); $size = filesize($filePath); @@ -80,10 +88,10 @@ class DocumentRepository extends BaseRepository ], 400); } - $documentTypeData = Document::$types[$documentType]; + $hash = sha1_file($filePath); - $filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentTypeData['extension']; + $filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType; $document = Document::createNew(); $disk = $document->getDisk(); @@ -94,20 +102,20 @@ class DocumentRepository extends BaseRepository } // This is an image; check if we need to create a preview - if(in_array($documentType, array('image/jpeg','image/png','image/gif','image/bmp','image/tiff'))){ + if(in_array($documentType, array('jpeg','png','gif','bmp','tiff','psd'))){ $makePreview = false; $imageSize = getimagesize($filePath); $width = $imageSize[0]; $height = $imageSize[1]; $imgManagerConfig = array(); - if(in_array($documentType, array('image/gif','image/bmp','image/tiff'))){ + if(in_array($documentType, array('gif','bmp','tiff','psd'))){ // Needs to be converted $makePreview = true; } else if($width > DOCUMENT_PREVIEW_SIZE || $height > DOCUMENT_PREVIEW_SIZE){ $makePreview = true; } - if($documentType == 'image/bmp' || $documentType == 'image/tiff'){ + if(in_array($documentType,array('bmp','tiff','psd'))){ if(!class_exists('Imagick')){ // Cant't read this $makePreview = false; @@ -117,13 +125,13 @@ class DocumentRepository extends BaseRepository } if($makePreview){ - $previewType = 'jpg'; - if(in_array($documentType, array('image/png','image/gif','image/bmp','image/tiff'))){ + $previewType = 'jpeg'; + if(in_array($documentType, array('png','gif','tiff','psd'))){ // Has transparency $previewType = 'png'; } - $document->preview = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentTypeData['extension'].'.x'.DOCUMENT_PREVIEW_SIZE.'.'.$previewType; + $document->preview = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType.'.x'.DOCUMENT_PREVIEW_SIZE.'.'.$previewType; if(!$disk->exists($document->preview)){ // We haven't created a preview yet $imgManager = new ImageManager($imgManagerConfig); @@ -170,7 +178,7 @@ class DocumentRepository extends BaseRepository $doc_array = $document->toArray(); if(!empty($base64)){ - $mime = !empty($previewType)?Document::$extensions[$previewType]:$documentType; + $mime = Document::$types[!empty($previewType)?$previewType:$documentType]['mime']; $doc_array['base64'] = 'data:'.$mime.';base64,'.$base64; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c5d597ded2..8a6760718a 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -159,6 +159,12 @@ class AppServiceProvider extends ServiceProvider { return $str . ''; }); + Form::macro('human_filesize', function($bytes, $decimals = 1) { + $size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB'); + $factor = floor((strlen($bytes) - 1) / 3); + return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor]; + }); + Validator::extend('positive', function($attribute, $value, $parameters) { return Utils::parseFloat($value) >= 0; }); diff --git a/public/built.js b/public/built.js index 6a2954d3a7..0b46c5659d 100644 --- a/public/built.js +++ b/public/built.js @@ -31404,10 +31404,10 @@ NINJA.invoiceDocuments = function(invoice) { for (var i = 0; i < invoice.documents.length; i++) { var document = invoice.documents[i]; var path = document.base64; - if(!path && (document.preview_url || document.type == 'image/png' || document.type == 'image/jpeg')){ - path = 'docs/'+document.public_id+'/'+document.name; - } + + if(!path)path = 'docs/'+document.public_id+'/'+document.name; if(path && (window.pdfMake.vfs[path] || document.base64)){ + // Only embed if we actually have an image for it if(j%3==0){ stackItem = {columns:[]}; stack.push(stackItem); diff --git a/public/js/pdf.pdfmake.js b/public/js/pdf.pdfmake.js index 6989916a88..29da491112 100644 --- a/public/js/pdf.pdfmake.js +++ b/public/js/pdf.pdfmake.js @@ -412,10 +412,10 @@ NINJA.invoiceDocuments = function(invoice) { for (var i = 0; i < invoice.documents.length; i++) { var document = invoice.documents[i]; var path = document.base64; - if(!path && (document.preview_url || document.type == 'image/png' || document.type == 'image/jpeg')){ - path = 'docs/'+document.public_id+'/'+document.name; - } + + if(!path)path = 'docs/'+document.public_id+'/'+document.name; if(path && (window.pdfMake.vfs[path] || document.base64)){ + // Only embed if we actually have an image for it if(j%3==0){ stackItem = {columns:[]}; stack.push(stackItem); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 7eec8138a5..ec8ba2a2c4 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1107,7 +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', + 'download_documents' => 'Download Documents (:size)', ); return $LANG; diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index bf2ce391c6..134826e44a 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -930,7 +930,7 @@ params:{ _token:"{{ Session::getToken() }}" }, - acceptedFiles:{!! json_encode(implode(',',array_keys(\App\Models\Document::$types))) !!}, + acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!}, addRemoveLinks:true, maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, dictDefaultMessage:{!! json_encode(trans('texts.document_upload_message')) !!} @@ -955,9 +955,12 @@ dropzone.emit('addedfile', mockFile); dropzone.emit('complete', mockFile); - if(document.type().match(/image.*/)){ + if(document.preview_url()){ dropzone.emit('thumbnail', mockFile, document.preview_url()||document.url()); } + else if(document.type()=='jpeg' || document.type()=='png' || document.type()=='svg'){ + dropzone.emit('thumbnail', mockFile, document.url()); + } dropzone.files.push(mockFile); } @endif @@ -1005,7 +1008,9 @@ } function createInvoiceModel() { - var invoice = ko.toJS(window.model).invoice; + var model = ko.toJS(window.model); + if(!model)return; + var invoice = model.invoice; invoice.is_pro = {{ Auth::user()->isPro() ? 'true' : 'false' }}; invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }}; invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true}); @@ -1335,13 +1340,19 @@ file.public_id = response.document.public_id model.invoice().documents()[file.index].update(response.document); refreshPDF(true); + + if(response.document.preview_url){ + dropzone.emit('thumbnail', file, response.document.preview_url); + } } @endif @if ($account->isPro() && $account->invoice_embed_documents) @foreach ($invoice->documents as $document) - + @if($document->isPDFEmbeddable()) + + @endif @endforeach @endif diff --git a/resources/views/invoices/history.blade.php b/resources/views/invoices/history.blade.php index 19d7eeff52..256f541f02 100644 --- a/resources/views/invoices/history.blade.php +++ b/resources/views/invoices/history.blade.php @@ -59,7 +59,9 @@ @if (Auth::user()->account->isPro() && Auth::user()->account->invoice_embed_documents) @foreach ($invoice->documents as $document) - + @if($document->isPDFEmbeddable()) + + @endif @endforeach @endif @stop \ No newline at end of file diff --git a/resources/views/invoices/view.blade.php b/resources/views/invoices/view.blade.php index 16479df684..bad07e9e73 100644 --- a/resources/views/invoices/view.blade.php +++ b/resources/views/invoices/view.blade.php @@ -43,7 +43,7 @@
    @if(!empty($documentsZipURL)) - {!! Button::normal(trans('texts.download_documents'))->asLinkTo($documentsZipURL)->large() !!} + {!! Button::normal(trans('texts.download_documents', array('size'=>Form::human_filesize($documentsZipSize))))->asLinkTo($documentsZipURL)->large() !!} @endif
    @endif @@ -54,7 +54,7 @@

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

    @@ -62,7 +62,9 @@ @if ($account->isPro() && $account->invoice_embed_documents) @foreach ($invoice->documents as $document) - + @if($document->isPDFEmbeddable()) + + @endif @endforeach @endif @stop \ No newline at end of file diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index a383a1b0cb..c6aaa72303 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -38,9 +38,8 @@ {!! Former::open($url) ->method($method) - ->addClass('warn-on-exit') + ->addClass('warn-on-exit main-form') ->autocomplete('off') - ->attributes(array('enctype'=>'multipart/form-data')) ->onsubmit('return onFormSubmit(event)') ->rules(array( 'client' => 'required', @@ -307,17 +306,27 @@
    @if ($account->isPro())
    -
    -
    - -
    -
    -
    - - - +
    +
    +
    + +
    +
    +
    + + + +
    + @if ($invoice->hasExpenseDocuments()) +

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

    + @foreach($invoice->expenses as $expense) + @foreach($expense->documents as $document) +
    {{$document->name}}
    + @endforeach + @endforeach + @endif
    @endif @@ -778,24 +787,24 @@ model.invoice().has_tasks(true); @endif - @if (isset($expenses) && $expenses) + if(model.invoice().expenses() && !model.invoice().public_id()){ model.expense_currency_id({{ $expenseCurrencyId }}); // move the blank invoice line item to the end var blank = model.invoice().invoice_items.pop(); - var expenses = {!! $expenses !!}; + var expenses = model.invoice().expenses(); for (var i=0; iaccount->isPro()) + $('.main-form').submit(function(){ + if($('#document-upload .dropzone .fallback input').val())$(this).attr('enctype', 'multipart/form-data') + else $(this).removeAttr('enctype') + }) + // Initialize document upload - dropzone = new Dropzone('#document-upload', { + dropzone = new Dropzone('#document-upload .dropzone', { url:{!! json_encode(url('document')) !!}, params:{ _token:"{{ Session::getToken() }}" @@ -1360,6 +1374,13 @@ @endif @endforeach + @foreach ($invoice->expenses as $expense) + @foreach ($expense->documents as $document) + @if($document->isPDFEmbeddable()) + + @endif + @endforeach + @endforeach @endif @stop diff --git a/resources/views/invoices/history.blade.php b/resources/views/invoices/history.blade.php index 256f541f02..baa5c925aa 100644 --- a/resources/views/invoices/history.blade.php +++ b/resources/views/invoices/history.blade.php @@ -57,11 +57,18 @@ @include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800]) - @if (Auth::user()->account->isPro() && Auth::user()->account->invoice_embed_documents) + @if (Utils::isPro() && $invoice->account->invoice_embed_documents) @foreach ($invoice->documents as $document) @if($document->isPDFEmbeddable()) @endif @endforeach + @foreach ($invoice->expenses as $expense) + @foreach ($expense->documents as $document) + @if($document->isPDFEmbeddable()) + + @endif + @endforeach + @endforeach @endif @stop \ No newline at end of file diff --git a/resources/views/invoices/knockout.blade.php b/resources/views/invoices/knockout.blade.php index 886eb98551..9db13abe19 100644 --- a/resources/views/invoices/knockout.blade.php +++ b/resources/views/invoices/knockout.blade.php @@ -227,6 +227,7 @@ function InvoiceModel(data) { self.invoice_status_id = ko.observable(0); self.invoice_items = ko.observableArray(); self.documents = ko.observableArray(); + self.expenses = ko.observableArray(); self.amount = ko.observable(0); self.balance = ko.observable(0); self.invoice_design_id = ko.observable(1); @@ -257,6 +258,11 @@ function InvoiceModel(data) { return new DocumentModel(options.data); } }, + 'expenses': { + create: function(options) { + return new ExpenseModel(options.data); + } + }, 'tax': { create: function(options) { return new TaxRateModel(options.data); @@ -846,6 +852,28 @@ function DocumentModel(data) { self.update(data); } } + +var ExpenseModel = function(data) { + var self = this; + + self.mapping = { + 'documents': { + create: function(options) { + return new DocumentModel(options.data); + } + } + } + + self.description = ko.observable(''); + self.qty = ko.observable(0); + self.public_id = ko.observable(0); + self.amount = ko.observable(); + self.converted_amount = ko.observable(); + + if (data) { + ko.mapping.fromJS(data, self.mapping, this); + } +}; /* Custom binding for product key typeahead */ ko.bindingHandlers.typeahead = { diff --git a/resources/views/invoices/view.blade.php b/resources/views/invoices/view.blade.php index bad07e9e73..08f5cd9827 100644 --- a/resources/views/invoices/view.blade.php +++ b/resources/views/invoices/view.blade.php @@ -49,13 +49,18 @@ @endif

     

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

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

    @endif @@ -63,9 +68,16 @@ @if ($account->isPro() && $account->invoice_embed_documents) @foreach ($invoice->documents as $document) @if($document->isPDFEmbeddable()) - + @endif @endforeach + @foreach ($invoice->expenses as $expense) + @foreach ($expense->documents as $document) + @if($document->isPDFEmbeddable()) + + @endif + @endforeach + @endforeach @endif