From e2f09021c65141fe64b66e5314a4f41f5de847bc Mon Sep 17 00:00:00 2001 From: samoilis Date: Wed, 9 Mar 2016 02:00:19 +0200 Subject: [PATCH 001/111] Update ContactMailer.php a small fix to payment links of email templates this links is relating to payments and not to invoice view --- app/Ninja/Mailers/ContactMailer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index dc513c3e8822..fd51dd5b6a71 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -259,7 +259,7 @@ class ContactMailer extends Mailer foreach (Gateway::$paymentTypes as $type) { $camelType = Gateway::getPaymentTypeName($type); $type = Utils::toSnakeCase($camelType); - $variables["\${$camelType}Link"] = $invitation->getLink() . "/{$type}"; + $variables["\${$camelType}Link"] = $invitation->getLink('payment') . "/{$type}"; $variables["\${$camelType}Button"] = HTML::emailPaymentButton($invitation->getLink('payment') . "/{$type}"); } From 62d0840d02d32811538f9a8373065e62cb1dd223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=BCske?= Date: Wed, 9 Mar 2016 17:34:41 +0100 Subject: [PATCH 002/111] Fix typo in de-lang --- resources/lang/de/texts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index 5fea0bf82b63..ccec754558d0 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -1122,7 +1122,7 @@ return array( 'all_pages_header' => 'Show header on', 'all_pages_footer' => 'Show footer on', 'invoice_currency' => 'Rechnungs-Währung', - 'enable_https' => 'Wir empfehlen dringend HTTPS zu verwendne, um Kreditkarten online zu akzeptieren.', + 'enable_https' => 'Wir empfehlen dringend HTTPS zu verwenden, um Kreditkarten online zu akzeptieren.', 'quote_issued_to' => 'Quote issued to', 'show_currency_code' => 'Währungscode', 'trial_message' => 'Your account will receive a free two week trial of our pro plan.', From 4eae70400e22ad821da6e4b3d46f52af1353d72a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 22 Mar 2016 20:00:41 +0200 Subject: [PATCH 003/111] Updated travis branch --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index b6a5b63a3df4..c8fc462f01f4 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ # Invoice Ninja ### [https://www.invoiceninja.com](https://www.invoiceninja.com) -[![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=develop)](https://travis-ci.org/invoiceninja/invoiceninja) +[![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=master)](https://travis-ci.org/invoiceninja/invoiceninja) [![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Note: we've recently updated this branch to Laravel 5.2. If you're upgrading here are some things to note From 39f426e3522448135ad5c025d271579fcdb097d1 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 23 Mar 2016 15:02:53 +0200 Subject: [PATCH 004/111] 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 bec95fb96921..f674c406549e 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 90baeb5018f44528793489a0efc36df72ed4ee9f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 24 Mar 2016 09:15:02 +0200 Subject: [PATCH 005/111] 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 2cc71029578d..7abd074a08b4 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 6a6c932bf634bed7a013fd968ffd559a5ba0ac7a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 31 Mar 2016 17:14:34 +0300 Subject: [PATCH 006/111] Merge fix #787 for spaces in .env file --- app/Http/Controllers/AppController.php | 43 ++++++++++++++++---------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index 394323d7420a..23757f2e0297 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -83,23 +83,29 @@ class AppController extends BaseController return Redirect::to('/'); } - $config = "APP_ENV=production\n". - "APP_DEBUG={$app['debug']}\n". - "APP_URL={$app['url']}\n". - "APP_KEY={$app['key']}\n\n". - "DB_TYPE={$dbType}\n". - "DB_HOST={$database['type']['host']}\n". - "DB_DATABASE={$database['type']['database']}\n". - "DB_USERNAME={$database['type']['username']}\n". - "DB_PASSWORD={$database['type']['password']}\n\n". - "MAIL_DRIVER={$mail['driver']}\n". - "MAIL_PORT={$mail['port']}\n". - "MAIL_ENCRYPTION={$mail['encryption']}\n". - "MAIL_HOST={$mail['host']}\n". - "MAIL_USERNAME={$mail['username']}\n". - "MAIL_FROM_NAME={$mail['from']['name']}\n". - "MAIL_PASSWORD={$mail['password']}\n\n". - "PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'"; + $_ENV['APP_ENV']='production'; + $_ENV['APP_DEBUG']=$app['debug']; + $_ENV['APP_URL']=$app['url']; + $_ENV['APP_KEY']=$app['key']; + $_ENV['DB_TYPE']=$dbType; + $_ENV['DB_HOST']=$database['type']['host']; + $_ENV['DB_DATABASE']=$database['type']['database']; + $_ENV['DB_USERNAME']=$database['type']['username']; + $_ENV['DB_PASSWORD']=$database['type']['password']; + $_ENV['MAIL_DRIVER']=$mail['driver']; + $_ENV['MAIL_PORT']=$mail['port']; + $_ENV['MAIL_ENCRYPTION']=$mail['encryption']; + $_ENV['MAIL_HOST']=$mail['host']; + $_ENV['MAIL_USERNAME']=$mail['username'];; + + $config = ''; + foreach ($_ENV as $key => $val) { + if (preg_match('/\s/',$val)) { + $val = "'{$val}'"; + } + $config .= "{$key}={$val}\n"; + } + // Write Config Settings $fp = fopen(base_path()."/.env", 'w'); @@ -166,6 +172,9 @@ class AppController extends BaseController $config = ''; foreach ($_ENV as $key => $val) { + if (preg_match('/\s/',$val)) { + $val = "'{$val}'"; + } $config .= "{$key}={$val}\n"; } From 0c4cbd69bd091341343bc8ffcba5b410905082aa Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 31 Mar 2016 17:27:18 +0300 Subject: [PATCH 007/111] Merge bug fixes from develop branch --- app/Http/Controllers/ReportController.php | 9 +++++++-- app/Models/TaxRate.php | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index f1feb65b4bd9..cfb9caaddef6 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -158,8 +158,10 @@ class ReportController extends BaseController } $records = DB::table($entityType.'s') - ->select(DB::raw('sum(amount) as total, '.$timeframe.' as '.$groupBy)) - ->where('account_id', '=', Auth::user()->account_id) + ->select(DB::raw('sum('.$entityType.'s.amount) as total, '.$timeframe.' as '.$groupBy)) + ->join('clients', 'clients.id', '=', $entityType.'s.client_id') + ->where('clients.is_deleted', '=', false) + ->where($entityType.'s.account_id', '=', Auth::user()->account_id) ->where($entityType.'s.is_deleted', '=', false) ->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d')) ->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d')) @@ -168,6 +170,9 @@ class ReportController extends BaseController if ($entityType == ENTITY_INVOICE) { $records->where('is_quote', '=', false) ->where('is_recurring', '=', false); + } elseif ($entityType == ENTITY_PAYMENT) { + $records->join('invoices', 'invoices.id', '=', 'payments.invoice_id') + ->where('invoices.is_deleted', '=', false); } $totals = $records->lists('total'); diff --git a/app/Models/TaxRate.php b/app/Models/TaxRate.php index 15c39a7572a0..cf0a576a8f0d 100644 --- a/app/Models/TaxRate.php +++ b/app/Models/TaxRate.php @@ -1,5 +1,6 @@ Date: Thu, 31 Mar 2016 17:28:04 +0300 Subject: [PATCH 008/111] Updated version number --- 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 1f5289d44326..a47f75ac1dea 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.1'); + define('NINJA_VERSION', '2.5.1.2'); define('NINJA_DATE', '2000-01-01'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); From be9163a926a83d9af22e9cd7de257b55ed8c6c58 Mon Sep 17 00:00:00 2001 From: Jeroen Date: Thu, 31 Mar 2016 23:08:10 +0200 Subject: [PATCH 009/111] #793: Reference pro_plan images with templatetag Updated references to the pro_plan before- and after-images with the asset-templatetag. They were referenced by `ninja.dev`, which doesn't resolve on a lot of computers --- resources/views/header.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 3ce364da6441..4c21c277fde0 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -784,11 +784,11 @@

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

- before + before

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

- after + after
@@ -811,4 +811,4 @@

 

-@stop \ No newline at end of file +@stop From eed5074cabeef26e37927fd60d98c34ebbe86ce3 Mon Sep 17 00:00:00 2001 From: Stefan Welsch Date: Sat, 2 Apr 2016 12:39:18 +0200 Subject: [PATCH 010/111] Update texts.php Add and correct some german translations --- resources/lang/de/texts.php | 178 ++++++++++++++++++------------------ 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index 08ffaf7b8a0b..c6000eb44f88 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -865,7 +865,7 @@ return array( 'activity_15' => ':user aktualisierte :credit Guthaben', 'activity_16' => ':user archivierte :credit Guthaben', 'activity_17' => ':user löschte :credit Guthaben', - 'activity_18' => ':user löschte Angebot :quote', + 'activity_18' => ':user erstellte Angebot :quote', 'activity_19' => ':user aktualisierte Angebot :quote', 'activity_20' => ':user mailte Angebot :quote an :contact', 'activity_21' => ':contact schaute Angebot :quote an', @@ -916,50 +916,50 @@ return array( 'country' => 'Land', 'include' => 'Hinzufügen', - 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', - 'import_freshbooks' => 'Import From FreshBooks', - 'import_data' => 'Import Data', - 'source' => 'Source', + 'logo_too_large' => 'Ihr Logo ist nur :size. Um eine bessere Darstellung im PDF Dokument zu erhalten, empfehlen wir ein Bild größer als 200KB', + 'import_freshbooks' => 'Importiere von FreshBooks', + 'import_data' => 'Importiere Daten', + 'source' => 'Quelle', 'csv' => 'CSV', - 'client_file' => 'Client File', - 'invoice_file' => 'Invoice File', + 'client_file' => 'Kunden Datei', + 'invoice_file' => 'Rechnungs Datei', 'task_file' => 'Task File', - 'no_mapper' => 'No valid mapping for file', - 'invalid_csv_header' => 'Invalid CSV Header', + 'no_mapper' => 'Kein gültiges Mapping für die Datei', + 'invalid_csv_header' => 'Ungültiger CSV Header', 'email_errors' => [ - 'inactive_client' => 'Emails can not be sent to inactive clients', - 'inactive_contact' => 'Emails can not be sent to inactive contacts', - 'inactive_invoice' => 'Emails can not be sent to inactive invoices', - 'user_unregistered' => 'Please register your account to send emails', - 'user_unconfirmed' => 'Please confirm your account to send emails', - 'invalid_contact_email' => 'Invalid contact email', + 'inactive_client' => 'Emails können nicht zu inaktiven Kunden gesendet werden', + 'inactive_contact' => 'Emails können nicht zu inaktiven Kontakten gesendet werden', + 'inactive_invoice' => 'Emails können nicht zu inaktiven Rechnungen gesendet werden', + 'user_unregistered' => 'Bitte registrieren Sie sich um Emails zu versenden', + 'user_unconfirmed' => 'Bitte bestätigen Sie Ihr Konto um Emails zu senden', + 'invalid_contact_email' => 'Ungültige Kontakt Email Adresse', ], - 'client_portal' => 'Client Portal', + 'client_portal' => 'Kunden-Portal', 'admin' => 'Admin', 'disabled' => 'Disabled', - 'show_archived_users' => 'Show archived users', + 'show_archived_users' => 'Zeige archivierte Benutzer', 'notes' => 'Notes', - 'invoice_will_create' => 'client will be created', - 'invoices_will_create' => 'invoices will be created', - 'failed_to_import' => 'The following records failed to import, they either already exist or are missing required fields.', + 'invoice_will_create' => 'Kunde wird erstellt', + 'invoices_will_create' => 'Rechnung wird erstellt', + 'failed_to_import' => 'Die folgenden Daten konnten nicht importiert werden. Entweder sind diese bereits vorhanden oder es fehlen benötigte Felder.', - 'publishable_key' => 'Publishable Key', - 'secret_key' => 'Secret Key', + 'publishable_key' => 'Öffentlicher Schlüssel', + 'secret_key' => 'Geheimer Schlüssel', 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', 'email_design' => 'Email Design', 'due_by' => 'Due by :date', 'enable_email_markup' => 'Enable Markup', 'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.', - 'template_help_title' => 'Templates Help', - 'template_help_1' => 'Available variables:', + 'template_help_title' => 'Templates Hilfe', + 'template_help_1' => 'Verfügbare Variablen:', 'email_design_id' => 'Email Style', - 'email_design_help' => 'Make your emails look more professional with HTML layouts', - 'plain' => 'Plain', - 'light' => 'Light', - 'dark' => 'Dark', + 'email_design_help' => 'Lassen Sie Ihre Email professioneller mit HTML Layouts aussehen', + 'plain' => 'Einfach', + 'light' => 'Hell', + 'dark' => 'Dunkel', 'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.', 'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', @@ -972,19 +972,19 @@ return array( 'color_help' => 'Note: the primary color is also used in the client portal and custom email designs.', 'token_expired' => 'Validation token was expired. Please try again.', - 'invoice_link' => 'Invoice Link', - 'button_confirmation_message' => 'Click to confirm your email address.', - 'confirm' => 'Confirm', - 'email_preferences' => 'Email Preferences', - 'created_invoices' => 'Successfully created :count invoice(s)', - 'next_invoice_number' => 'The next invoice number is :number.', - 'next_quote_number' => 'The next quote number is :number.', + 'invoice_link' => 'Link zur Rechnung', + 'button_confirmation_message' => 'Bitte klicken um Ihre Email-Adresse zu bestätigen.', + 'confirm' => 'Bestätigen', + 'email_preferences' => 'Email Einstellungen', + 'created_invoices' => ':count Rechnung(en) erfolgreich erstellt', + 'next_invoice_number' => 'Die nächste Rechnungsnummer ist :number.', + 'next_quote_number' => 'Die nächste Angebotsnummer ist :number.', - 'days_before' => 'days before', - 'days_after' => 'days after', - 'field_due_date' => 'due date', - 'field_invoice_date' => 'invoice date', - 'schedule' => 'Schedule', + 'days_before' => 'Tage vorher', + 'days_after' => 'Tage danach', + 'field_due_date' => 'Fälligkeitsdatum', + 'field_invoice_date' => 'Rechnungsdatum', + 'schedule' => 'Zeitgesteuert', 'email_designs' => 'Email Designs', 'assigned_when_sent' => 'Assigned when sent', @@ -1036,7 +1036,7 @@ return array( 'convert_currency' => 'Währung umrechnen', // Payment terms - 'num_days' => 'Number of days', + 'num_days' => 'Anzahl Tage', 'create_payment_term' => 'Create Payment Term', 'edit_payment_terms' => 'Edit Payment Term', 'edit_payment_term' => 'Edit Payment Term', @@ -1118,8 +1118,8 @@ return array( 'first_page' => 'Erste Seite', 'all_pages' => 'Alle Seiten', 'last_page' => 'Letzte Seite', - 'all_pages_header' => 'Show header on', - 'all_pages_footer' => 'Show footer on', + 'all_pages_header' => 'Zeige Header auf', + 'all_pages_footer' => 'Zeige Footer auf', 'invoice_currency' => 'Rechnungs-Währung', 'enable_https' => 'Wir empfehlen dringend HTTPS zu verwenden, um Kreditkarten online zu akzeptieren.', 'quote_issued_to' => 'Quote issued to', @@ -1128,77 +1128,77 @@ return array( 'trial_footer' => 'Your free trial lasts :count more days, :link to upgrade now.', 'trial_footer_last_day' => 'Heute ist der letzte Tag Ihrer kostenlosen Probezeit, :link um upzugraden.', 'trial_call_to_action' => 'Kostenlose Probezeit starten', - 'trial_success' => 'Successfully enabled two week free pro plan trial', - '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.', + 'trial_success' => 'Erfolgreich eine 2-Wochen Testversion aktiviert', + 'overdue' => 'Überfällig', + 'white_label_text' => 'Kaufen Sie eine 1 Jahres Whitelabel Lizenz zum Preis von $'.WHITE_LABEL_PRICE.' um das Invoice Ninja Branding vom Kundenportal zu entfernen und unser Projekt zu unterstützen.', '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 /', + 'list_invoices' => 'Liste Rechnungen', + 'list_clients' => 'Liste Kunden', + 'list_quotes' => 'Liste Angebote', + 'list_tasks' => 'Liste Aufgaben', + 'list_expenses' => 'Liste Ausgaben', + 'list_recurring_invoices' => 'Liste wiederkehrende Rechnungen', + 'list_payments' => 'Liste Zahlungen', + 'list_credits' => 'Liste Guthaben', + 'tax_name' => 'Steuersatz Name', + 'report_settings' => 'Report Einstellungen', + 'search_hotkey' => 'Kürzel ist /', - 'new_user' => 'New User', - 'new_product' => 'New Product', - 'new_tax_rate' => 'New Tax Rate', - 'invoiced_amount' => 'Invoiced Amount', - 'invoice_item_fields' => 'Invoice Item Fields', + 'new_user' => 'Neuer Benutzer', + 'new_product' => 'Neues Produkt', + 'new_tax_rate' => 'Neuer Steuersatz', + 'invoiced_amount' => 'Rechnungsbetrag', + 'invoice_item_fields' => 'Rechnungspositions Feld', '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\'.', + 'recurring_invoice_number' => 'Wiederkehrende Rechnungsnummer', + 'recurring_invoice_number_prefix_help' => 'Geben Sie einen Präfix für wiederkehrende Rechnungen an. Standard ist \'R\'.', 'enable_client_portal' => 'Dashboard', - 'enable_client_portal_help' => 'Show/hide the dashboard page in the client portal.', + 'enable_client_portal_help' => 'Zeige das Dashboard im Kundenportal.', // 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.', + 'enable_portal_password'=>'Passwortgeschützte Rechnungen', + 'enable_portal_password_help'=>'Erlaubt Ihnen ein Passwort für jeden Kontakt zu erstellen. Wenn ein Passwort erstellt wurde, muss der Kunde dieses eingeben, bevor er eine Rechnung ansehen darf.', + 'send_portal_password'=>'Erstelle das Passwort automatisch', + 'send_portal_password_help'=>'Wenn kein Passwort gesetzt wurde, wird eins generiert und mit der ersten Rechnung verschickt.', - '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.', + 'expired' => 'Abgelaufen', + 'invalid_card_number' => 'Die Kreditkartennummer ist nicht gültig.', + 'invalid_expiry' => 'Das Ablaufdatum ist nicht gültig.', + 'invalid_cvv' => 'Der CVV Code ist nicht gültig.', + 'cost' => 'Kosten', + 'create_invoice_for_sample' => 'Hinweis: Erstellen Sie Ihre erste Rechnung um hier eine Vorschau zu sehen.', // User Permissions - 'owner' => 'Owner', + 'owner' => 'Eigentümer', '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.', + 'user_create_all' => 'Erstelle Kunden, Rechnungen, usw.', + 'user_view_all' => 'Alle Kunden, Rechnungen, usw. ansehen', + 'user_edit_all' => 'Alle Kunden, Rechnungen, usw. bearbeiten', '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', + 'permissions' => 'Berechtigungen', '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', + 'view_payment' => 'Zahlung zeigen', - 'january' => 'January', - 'february' => 'February', - 'march' => 'March', + 'january' => 'Januar', + 'february' => 'Februar', + 'march' => 'März', 'april' => 'April', - 'may' => 'May', - 'june' => 'June', - 'july' => 'July', + 'may' => 'Mai', + 'june' => 'Juni', + 'july' => 'Juli', 'august' => 'August', 'september' => 'September', - 'october' => 'October', + 'october' => 'Oktober', 'november' => 'November', - 'december' => 'December', + 'december' => 'Dezember', ); From 26130680e344614703a67e12132140ed24ad670b Mon Sep 17 00:00:00 2001 From: Stefan Welsch Date: Sun, 3 Apr 2016 10:16:05 +0200 Subject: [PATCH 011/111] Update texts.php update due date translation --- resources/lang/de/texts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index c6000eb44f88..4bb50425823d 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -950,7 +950,7 @@ return array( 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', 'email_design' => 'Email Design', - 'due_by' => 'Due by :date', + 'due_by' => 'Fällig am :date', 'enable_email_markup' => 'Enable Markup', 'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.', 'template_help_title' => 'Templates Hilfe', From 4fe388746809466568b22ceb6429524cded78805 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Fri, 8 Apr 2016 10:42:14 +0300 Subject: [PATCH 012/111] Merge fix for #800 --- app/Models/Account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Account.php b/app/Models/Account.php index f6a7194bdf6f..834152fc15e1 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -948,7 +948,7 @@ class Account extends Eloquent // Add line breaks if HTML isn't already being used return strip_tags($this->email_footer) == $this->email_footer ? nl2br($this->email_footer) : $this->email_footer; } else { - return "

" . trans('texts.email_signature') . "\n
\$account"; + return "

" . trans('texts.email_signature') . "\n
\$account

"; } } From 263fe35923f51fdb2075f56d2817907ceac5dd9c Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Fri, 8 Apr 2016 12:14:09 +0300 Subject: [PATCH 013/111] Updated postmark driver for Laravel 5.2 --- composer.json | 2 +- composer.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 45e6e1872640..3d349c74fd9e 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "alfaproject/omnipay-skrill": "dev-master", "omnipay/bitpay": "dev-master", "guzzlehttp/guzzle": "~6.0", - "wildbit/laravel-postmark-provider": "2.0", + "wildbit/laravel-postmark-provider": "3.0", "Dwolla/omnipay-dwolla": "dev-master", "laravel/socialite": "~2.0", "simshaun/recurr": "dev-master", diff --git a/composer.lock b/composer.lock index 6ce01e65ba67..f47bad86a156 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": "ce6df956642de67a38a4a22775eaeff3", + "content-hash": "fcd326b2ee271a65f719ba87f57a6c14", "packages": [ { "name": "agmscode/omnipay-agms", @@ -3815,7 +3815,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/77b316bd08c6b7a1e93721f15d1bfbd21a62ba6b", + "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/b27d2823d052f5c227eeb29324bb564cfdb8f9af", "reference": "e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "shasum": "" }, @@ -7585,20 +7585,20 @@ }, { "name": "wildbit/laravel-postmark-provider", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/wildbit/laravel-postmark-provider.git", - "reference": "79a7e8bde66b2bd6f314829b00ee08616847ebc5" + "reference": "b80815602f618abe24030ea6d3f117da49a72885" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wildbit/laravel-postmark-provider/zipball/79a7e8bde66b2bd6f314829b00ee08616847ebc5", - "reference": "79a7e8bde66b2bd6f314829b00ee08616847ebc5", + "url": "https://api.github.com/repos/wildbit/laravel-postmark-provider/zipball/b80815602f618abe24030ea6d3f117da49a72885", + "reference": "b80815602f618abe24030ea6d3f117da49a72885", "shasum": "" }, "require": { - "illuminate/mail": "~5.0", + "illuminate/mail": "~5.2", "wildbit/swiftmailer-postmark": "~2.0" }, "type": "library", @@ -7612,7 +7612,7 @@ "MIT" ], "description": "An officially supported mail provider to send mail from Laravel through Postmark, see instructions for integrating it here: https://github.com/wildbit/laravel-postmark-provider/blob/master/README.md", - "time": "2015-11-10 14:43:06" + "time": "2016-02-10 14:15:58" }, { "name": "wildbit/swiftmailer-postmark", From 65164cbaf327116eae06a2fd54ad0090a7cab381 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 9 Apr 2016 23:00:55 +0300 Subject: [PATCH 014/111] Fixes #798 Setup error --- app/Http/Controllers/AppController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index 23757f2e0297..6a78290e0634 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -100,7 +100,10 @@ class AppController extends BaseController $config = ''; foreach ($_ENV as $key => $val) { - if (preg_match('/\s/',$val)) { + if (is_array($val)) { + continue; + } + if (preg_match('/\s/', $val)) { $val = "'{$val}'"; } $config .= "{$key}={$val}\n"; From 80d0dda1e0748a477493a4065862866ec7468655 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 9 Apr 2016 23:03:51 +0300 Subject: [PATCH 015/111] Bumped version to 2.5.1.3 --- 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 a47f75ac1dea..59d22caf3e94 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.2'); + define('NINJA_VERSION', '2.5.1.3'); define('NINJA_DATE', '2000-01-01'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); From 1ece4f5534954b9ee1a78e75ab035bd086c42c85 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 10 Apr 2016 10:09:46 +0300 Subject: [PATCH 016/111] Git update --- app/Http/routes.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/routes.php b/app/Http/routes.php index 59d22caf3e94..33c8db1c5f5c 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,5 +1,6 @@ Date: Sun, 10 Apr 2016 22:54:48 +0300 Subject: [PATCH 017/111] Fixed payment list in client portal --- app/Http/Controllers/PublicClientController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index d067ff38a740..39b65b02e001 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -302,7 +302,7 @@ class PublicClientController extends BaseController $payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch')); return Datatable::query($payments) - ->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })->toHtml() + ->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number)->toHtml() : $model->invoice_number; }) ->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : 'Manual entry'; }) ->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? 'Online payment' : ''); }) ->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); }) From f31aacbab8d69d0c91aaab09f90279a4da29384d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 10 Apr 2016 23:05:41 +0300 Subject: [PATCH 018/111] Separated out log messages --- bootstrap/app.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bootstrap/app.php b/bootstrap/app.php index 71f392315f59..600e0fb5e9c8 100755 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -61,6 +61,8 @@ 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)); + $monolog->pushHandler(new Monolog\Handler\StreamHandler(storage_path() . '/logs/laravel-warning.log', Monolog\Logger::WARNING, false)); + $monolog->pushHandler(new Monolog\Handler\StreamHandler(storage_path() . '/logs/laravel-error.log', Monolog\Logger::ERROR, false)); }); return $app; From 13f5d80ee6434b91bad1e8896b072a353626c6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=C3=AFssa?= Date: Tue, 26 Apr 2016 00:31:48 +0200 Subject: [PATCH 019/111] Added some french translations --- resources/lang/fr/texts.php | 378 ++++++++++++++++++------------------ 1 file changed, 189 insertions(+), 189 deletions(-) diff --git a/resources/lang/fr/texts.php b/resources/lang/fr/texts.php index 6cd75a9f0efa..89f323a6f6e7 100644 --- a/resources/lang/fr/texts.php +++ b/resources/lang/fr/texts.php @@ -120,7 +120,7 @@ return array( 'active_clients' => 'clients actifs', 'invoices_past_due' => 'Date limite de paiement dépassée', 'upcoming_invoices' => 'Factures à venir', - 'average_invoice' => 'Moyenne de facturation', + 'average_invoice' => 'Facture moyenne', // list pages 'archive' => 'Archiver', @@ -437,8 +437,8 @@ return array( 'invalid_counter' => 'Pour éviter un éventuel conflit, merci de définir un préfixe pour le numéro de facture ou pour le numéro de devis', 'mark_sent' => 'Marquer comme envoyé', - 'gateway_help_1' => ':link to sign up for Authorize.net.', - 'gateway_help_2' => ':link to sign up for Authorize.net.', + 'gateway_help_1' => ':link pour vous inscrire à Authorize.net.', + 'gateway_help_2' => ':link pour vous inscrire à Authorize.net.', 'gateway_help_17' => ':link pour obtenir votre signature PayPal API.', 'gateway_help_27' => ':link pour vous enregistrer sur TwoCheckout.', @@ -503,10 +503,10 @@ return array( 'approve' => 'Accepter', 'token_billing_type_id' => 'Jeton de paiement', - 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_help' => 'Permet de stocker des cartes de crédit avec votre passerelle, et de déclancher le paiement à une date ultérieure.', 'token_billing_1' => 'Désactiver', - 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', - 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_2' => 'Opt-in - Case à cocher affichée mais non sélectionnée', + 'token_billing_3' => 'Opt-out - Case à cocher affichée et sélectionné', 'token_billing_4' => 'Toujours', 'token_billing_checkbox' => 'Enregistrer les informations de paiement', 'view_in_stripe' => 'Voir sur Stripe', @@ -522,8 +522,8 @@ return array( 'billing_address' => 'Adresse de facturation', 'billing_method' => 'Méthode de facturation', 'order_overview' => 'Résumé de la commande', - 'match_address' => '*Address must match address associated with credit card.', - 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', + 'match_address' => '*L\'adresse doit correspondre à l\'adresse associée à la carte de crédit.', + 'click_once' => '*S\'il vous plaît cliquer sur "PAYER MAINTENANT" une seule fois - la transaction peut prendre jusqu\'à 1 minute.', 'default_invoice_footer' => 'Définir par défaut', 'invoice_footer' => 'Pied de facture', @@ -575,7 +575,7 @@ return array( 'resend_confirmation' => 'Renvoyer l\'email de confirmation', 'confirmation_resent' => 'L\'email de confirmation a été renvoyé', - 'gateway_help_42' => ':link to sign up for BitPay.
Note: use a Legacy API Key, not an API token.', + 'gateway_help_42' => ':link pour vous inscrire à BitPay
Remarque:. utiliser une clé API "Legacy", pas un jeton.', 'payment_type_credit_card' => 'Carte de crédit', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', @@ -588,7 +588,7 @@ return array( 'client_name' => 'Nom du client', 'pdf_settings' => 'Réglages PDF', 'product_settings' => 'Réglages du produit', - 'auto_wrap' => 'Auto Line Wrap', + 'auto_wrap' => 'Retour à la ligne automatique', 'duplicate_post' => 'Attention: la page précédente a été soumise deux fois. La deuxième soumission a été ignorée.', 'view_documentation' => 'Voir documentation', 'app_title' => 'Outil de facturation gratuit & Open-Source', @@ -755,11 +755,11 @@ return array( 'show_line_item_tax' => 'Display line item taxes inline', 'iframe_url' => 'Site internet', - 'iframe_url_help1' => 'Copy the following code to a page on your site.', - 'iframe_url_help2' => 'You can test the feature by clicking \'View as recipient\' for an invoice.', + 'iframe_url_help1' => 'Copiez le code suivant sur une page de votre site.', + 'iframe_url_help2' => 'Vous pouvez tester la fonctionnalité en cliquant sur \'Voir en tant que destinataire\' pour une facture.', 'auto_bill' => 'Facturation automatique', - 'military_time' => '24 Hour Time', + 'military_time' => '24H', 'last_sent' => 'Dernier envoi', 'reminder_emails' => 'Emails de rappel', @@ -769,21 +769,21 @@ return array( 'first_reminder' => 'Premier rappel', 'second_reminder' => 'Second rappel', 'third_reminder' => 'Troisième rappel', - 'num_days_reminder' => 'Days after due date', - 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'num_days_reminder' => 'Jours après la date d\'échéance', + 'reminder_subject' => 'Rappel: Facture :invoice de :account', 'reset' => 'Remettre à zéro', 'invoice_not_found' => 'La facture demandée n\'est pas disponible', - 'referral_program' => 'Referral Program', - 'referral_code' => 'Referral Code', - 'last_sent_on' => 'Last sent on :date', + 'referral_program' => 'Programme de parrainage', + 'referral_code' => 'Code de Parrainage', + 'last_sent_on' => 'Dernier envoi le :date', - 'page_expire' => 'This page will expire soon, :click_here to keep working', + 'page_expire' => 'Cette page va bientôt expirer, :click_here pour continuer à travailler', 'upcoming_quotes' => 'Devis à venir', 'expired_quotes' => 'Devis expirés', 'sign_up_using' => 'Connexion avec', - 'invalid_credentials' => 'These credentials do not match our records', + 'invalid_credentials' => 'Ces informations de connexion sont invalides', 'show_all_options' => 'Voir toutes les options', 'user_details' => 'Utilisateur', 'oneclick_login' => 'Connexion en 1 clic', @@ -801,19 +801,19 @@ return array( 'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.', 'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice', - 'custom_invoice_link' => 'Custom Invoice Link', - 'total_invoiced' => 'Total Invoiced', + 'custom_invoice_link' => 'Personnaliser le lien de la facture', + 'total_invoiced' => 'Total facturé', 'open_balance' => 'Open Balance', 'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.', - 'basic_settings' => 'Basic Settings', + 'basic_settings' => 'Paramètres généraux', 'pro' => 'Pro', 'gateways' => 'Passerelles de paiement', - 'recurring_too_soon' => 'Il est trop tôt pour créer la prochaine facture récurrente, it\'s scheduled for :date', + 'recurring_too_soon' => 'Il est trop tôt pour créer la prochaine facture récurrente, prévue pour le :date', - 'next_send_on' => 'Send Next: :date', + 'next_send_on' => 'Envoi suivant: :date', 'no_longer_running' => 'This invoice is not scheduled to run', 'general_settings' => 'Réglages généraux', - 'customize' => 'Customiser', + 'customize' => 'Personnaliser', 'oneclick_login_help' => 'Connectez un compte pour vous connecter sans votre mot de passe', 'referral_code_help' => 'Gagnez de l\'argent en partagent notre outil en ligne', @@ -831,12 +831,12 @@ return array( 'recurring_hour' => 'Recurring Hour', 'pattern' => 'Pattern', 'pattern_help_title' => 'Pattern Help', - 'pattern_help_1' => 'Create custom invoice and quote numbers by specifying a pattern', - 'pattern_help_2' => 'Available variables:', - 'pattern_help_3' => 'For example, :example would be converted to :value', + 'pattern_help_1' => 'Créer des numéros de facture et devis personnalisés selon un modèle', + 'pattern_help_2' => 'Variables disponibles:', + 'pattern_help_3' => 'Par exemple, :example sera converti en :value', 'see_options' => 'Voir les options', - 'invoice_counter' => 'Invoice Counter', - 'quote_counter' => 'Quote Counter', + 'invoice_counter' => 'Compteur de factures', + 'quote_counter' => 'Compteur de devis', 'type' => 'Type', 'activity_1' => ':user a crée le client :client', @@ -869,7 +869,7 @@ return array( 'activity_28' => ':user a restauré le crédit :credit', 'activity_29' => ':contact a approuvé le devis :quote', - 'payment' => 'Paiment', + 'payment' => 'Paiement', 'system' => 'Système', 'signature' => 'Signature email', 'default_messages' => 'Messages par défaut', @@ -908,51 +908,51 @@ return array( 'include' => 'Inclure', 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', - 'import_freshbooks' => 'Import From FreshBooks', - 'import_data' => 'Import Data', + 'import_freshbooks' => 'Importer depuis FreshBooks', + 'import_data' => 'Importer des données', 'source' => 'Source', 'csv' => 'CSV', - 'client_file' => 'Client File', - 'invoice_file' => 'Invoice File', - 'task_file' => 'Task File', - 'no_mapper' => 'No valid mapping for file', - 'invalid_csv_header' => 'Invalid CSV Header', + 'client_file' => 'Fichier de clients', + 'invoice_file' => 'Fichier de factures', + 'task_file' => 'Fichier de tâches', + 'no_mapper' => 'Mappage invalide pour ce fichier', + 'invalid_csv_header' => 'En-tête du fichier CSV invalide', 'email_errors' => [ - 'inactive_client' => 'Emails can not be sent to inactive clients', - 'inactive_contact' => 'Emails can not be sent to inactive contacts', - 'inactive_invoice' => 'Emails can not be sent to inactive invoices', - 'user_unregistered' => 'Please register your account to send emails', - 'user_unconfirmed' => 'Please confirm your account to send emails', - 'invalid_contact_email' => 'Invalid contact email', + 'inactive_client' => 'Les mails ne peuvent être envoyés aux clients inactifs', + 'inactive_contact' => 'Les mails ne peuvent être envoyés aux contacts inactifs', + 'inactive_invoice' => 'Les mails ne peuvent être envoyés aux factures inactives', + 'user_unregistered' => 'Veuillez vous inscrire afin d\'envoyer des mails', + 'user_unconfirmed' => 'Veuillez confirmer votre compte afin de permettre l\'envoi de mail', + 'invalid_contact_email' => 'Adresse mail du contact invalide', ], - 'client_portal' => 'Client Portal', + 'client_portal' => 'Portail client', 'admin' => 'Admin', - 'disabled' => 'Disabled', - 'show_archived_users' => 'Show archived users', + 'disabled' => 'Désactivé', + 'show_archived_users' => 'Afficher les utilisateurs archivés', 'notes' => 'Notes', 'invoice_will_create' => 'client will be created', 'invoices_will_create' => 'invoices will be created', 'failed_to_import' => 'The following records failed to import, they either already exist or are missing required fields.', - 'publishable_key' => 'Publishable Key', - 'secret_key' => 'Secret Key', - 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + 'publishable_key' => 'Clé publique', + 'secret_key' => 'Clé secrète', + 'missing_publishable_key' => 'Saisissez votre clé publique Stripe pour un processus de commande amélioré', 'email_design' => 'Email Design', - 'due_by' => 'Due by :date', + 'due_by' => 'A échéanche du :date', 'enable_email_markup' => 'Enable Markup', 'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.', 'template_help_title' => 'Templates Help', - 'template_help_1' => 'Available variables:', - 'email_design_id' => 'Email Style', + 'template_help_1' => 'Variable disponibles :', + 'email_design_id' => 'Style de mail', 'email_design_help' => 'Make your emails look more professional with HTML layouts', - 'plain' => 'Plain', - 'light' => 'Light', - 'dark' => 'Dark', + 'plain' => 'Brut', + 'light' => 'Clair', + 'dark' => 'Sombre', - 'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.', + 'industry_help' => 'Utilisé dans le but de fournir des statistiques la taille et le secteur de l\'entreprise.', 'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', 'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.', 'quote_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the quote number.', @@ -962,36 +962,36 @@ return array( 'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.', 'color_help' => 'Note: the primary color is also used in the client portal and custom email designs.', - 'token_expired' => 'Validation token was expired. Please try again.', - 'invoice_link' => 'Invoice Link', - 'button_confirmation_message' => 'Click to confirm your email address.', - 'confirm' => 'Confirm', - 'email_preferences' => 'Email Preferences', - 'created_invoices' => 'Successfully created :count invoice(s)', - 'next_invoice_number' => 'The next invoice number is :number.', - 'next_quote_number' => 'The next quote number is :number.', + 'token_expired' => 'Validation jeton expiré. Veuillez réessayer.', + 'invoice_link' => 'Lien vers la facture', + 'button_confirmation_message' => 'Cliquez pour confirmer votre adresse e-mail.', + 'confirm' => 'Confirmer', + 'email_preferences' => 'Préférences de mail', + 'created_invoices' => ':count factures(s) créées avec succès', + 'next_invoice_number' => 'Le prochain numéro de facture est :number.', + 'next_quote_number' => 'Le prochain numéro de devis est :number.', 'days_before' => 'days before', 'days_after' => 'days after', - 'field_due_date' => 'due date', - 'field_invoice_date' => 'invoice date', + 'field_due_date' => 'date d\'échéance', + 'field_invoice_date' => 'Date de la facture', 'schedule' => 'Schedule', 'email_designs' => 'Email Designs', 'assigned_when_sent' => 'Assigned when sent', 'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.', - 'white_label_purchase_link' => 'Purchase a white label license', + 'white_label_purchase_link' => 'Acheter une licence marque blanche', // Expense / vendor - 'expense' => 'Expense', - 'expenses' => 'Expenses', + 'expense' => 'Dépense', + 'expenses' => 'Dépenses', 'new_expense' => 'Enter Expense', 'enter_expense' => 'Enter Expense', - 'vendors' => 'Vendors', - 'new_vendor' => 'New Vendor', + 'vendors' => 'Fournisseurs', + 'new_vendor' => 'Nouveau fournisseur', 'payment_terms_net' => 'Net', 'vendor' => 'Vendor', - 'edit_vendor' => 'Edit Vendor', + 'edit_vendor' => 'Editer le fournisseur', 'archive_vendor' => 'Archive Vendor', 'delete_vendor' => 'Delete Vendor', 'view_vendor' => 'View Vendor', @@ -1001,37 +1001,37 @@ return array( 'archived_expenses' => 'Successfully archived expenses', // Expenses - 'expense_amount' => 'Expense Amount', + 'expense_amount' => 'Montant de la dépense', 'expense_balance' => 'Expense Balance', - 'expense_date' => 'Expense Date', + 'expense_date' => 'Date de la dépense', 'expense_should_be_invoiced' => 'Should this expense be invoiced?', 'public_notes' => 'Public Notes', - 'invoice_amount' => 'Invoice Amount', - 'exchange_rate' => 'Exchange Rate', - 'yes' => 'Yes', - 'no' => 'No', - 'should_be_invoiced' => 'Should be invoiced', - 'view_expense' => 'View expense # :expense', - 'edit_expense' => 'Edit Expense', - 'archive_expense' => 'Archive Expense', - 'delete_expense' => 'Delete Expense', - 'view_expense_num' => 'Expense # :expense', + 'invoice_amount' => 'Montant de la facture', + 'exchange_rate' => 'Taux de change', + 'yes' => 'Oui', + 'no' => 'Non', + 'should_be_invoiced' => 'Devrait être facturé', + 'view_expense' => 'Voir la dépense # :expense', + 'edit_expense' => 'Editer la dépensee', + 'archive_expense' => 'Archiver le dépense', + 'delete_expense' => 'supprimer la dépense', + 'view_expense_num' => 'Dépense # :expense', 'updated_expense' => 'Successfully updated expense', 'created_expense' => 'Successfully created expense', - 'enter_expense' => 'Enter Expense', - 'view' => 'View', - 'restore_expense' => 'Restore Expense', + 'enter_expense' => 'Entrer la dépense', + 'view' => 'Voir', + 'restore_expense' => 'Restorer la dépense', 'invoice_expense' => 'Invoice Expense', 'expense_error_multiple_clients' => 'The expenses can\'t belong to different clients', 'expense_error_invoiced' => 'Expense has already been invoiced', 'convert_currency' => 'Convert currency', // Payment terms - 'num_days' => 'Number of days', - 'create_payment_term' => 'Create Payment Term', - 'edit_payment_terms' => 'Edit Payment Term', - 'edit_payment_term' => 'Edit Payment Term', - 'archive_payment_term' => 'Archive Payment Term', + 'num_days' => 'Nombre de jours', + 'create_payment_term' => 'Créer une condition de paiement', + 'edit_payment_terms' => 'Editer condition de paiement', + 'edit_payment_term' => 'Editer la condition de paiement', + 'archive_payment_term' => 'Archiver la condition de paiement', // recurring due dates 'recurring_due_dates' => 'Recurring Invoice Due Dates', @@ -1048,56 +1048,56 @@ return array(
  • Today is the Friday, due date is the 1st Friday after. The due date will be next Friday, not today.
  • ', - 'due' => 'Due', - 'next_due_on' => 'Due Next: :date', - 'use_client_terms' => 'Use client terms', - 'day_of_month' => ':ordinal day of month', - 'last_day_of_month' => 'Last day of month', + 'due' => 'Dû', + 'next_due_on' => 'Prochaine échéance: :date', + 'use_client_terms' => 'Utiliser les conditions de paiement du client', + 'day_of_month' => ':ordinal jour du mois', + 'last_day_of_month' => 'Dernier jour du mois', 'day_of_week_after' => ':ordinal :day after', - 'sunday' => 'Sunday', - 'monday' => 'Monday', - 'tuesday' => 'Tuesday', - 'wednesday' => 'Wednesday', - 'thursday' => 'Thursday', - 'friday' => 'Friday', - 'saturday' => 'Saturday', + 'sunday' => 'Dimanche', + 'monday' => 'Lundi', + 'tuesday' => 'Mardi', + 'wednesday' => 'Mercredi', + 'thursday' => 'Jeudi', + 'friday' => 'Vendredi', + 'saturday' => 'Samedi', // Fonts 'header_font_id' => 'Header Font', 'body_font_id' => 'Body Font', 'color_font_help' => 'Note: the primary color and fonts are also used in the client portal and custom email designs.', - 'live_preview' => 'Live Preview', - 'invalid_mail_config' => 'Unable to send email, please check that the mail settings are correct.', + 'live_preview' => 'Aperçu', + 'invalid_mail_config' => 'Impossible d\'envoyer le mail, veuillez vérifier que les paramètres de messagerie sont corrects.', - 'invoice_message_button' => 'To view your invoice for :amount, click the button below.', - 'quote_message_button' => 'To view your quote for :amount, click the button below.', - 'payment_message_button' => 'Thank you for your payment of :amount.', - 'payment_type_direct_debit' => 'Direct Debit', - 'bank_accounts' => 'Bank Accounts', - 'add_bank_account' => 'Add Bank Account', - 'setup_account' => 'Setup Account', - 'import_expenses' => 'Import Expenses', - 'bank_id' => 'bank', + 'invoice_message_button' => 'Pour visionner votre facture de :amount, cliquer sur le lien ci-dessous', + 'quote_message_button' => 'Pour visionner votre devis de :amount, cliquer sur le lien ci-dessous', + 'payment_message_button' => 'Merci pour votre paiement de :amount.', + 'payment_type_direct_debit' => 'Prélèvement', + 'bank_accounts' => 'Comptes bancaires', + 'add_bank_account' => 'Ajouter un compte bancaire', + 'setup_account' => 'Paraméter le compte', + 'import_expenses' => 'Importer les dépenses', + 'bank_id' => 'banque', 'integration_type' => 'Integration Type', - 'updated_bank_account' => 'Successfully updated bank account', - 'edit_bank_account' => 'Edit Bank Account', - 'archive_bank_account' => 'Archive Bank Account', - 'archived_bank_account' => 'Successfully archived bank account', - 'created_bank_account' => 'Successfully created bank account', - 'validate_bank_account' => 'Validate Bank Account', + 'updated_bank_account' => 'Compte bancaire mis à jour', + 'edit_bank_account' => 'Editer le compte bancaire', + 'archive_bank_account' => 'Archiver le compte bancaire', + 'archived_bank_account' => 'Compte bancaire archivé', + 'created_bank_account' => 'Compte bancaire créé', + 'validate_bank_account' => 'Valider le compte bancaire', 'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and 400+ US banks.', 'bank_password_help' => 'Note: your password is transmitted securely and never stored on our servers.', - 'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.', - 'username' => 'Username', - 'account_number' => 'Account Number', - 'account_name' => 'Account Name', + 'bank_password_warning' => 'Attention: votre mot de passe peut être transmis en clair, pensez à activer HTTPS.', + 'username' => 'Nom d\'utilisateur', + 'account_number' => 'N° de compte', + 'account_name' => 'Nom du compte', 'bank_account_error' => 'Failed to retreive account details, please check your credentials.', - 'status_approved' => 'Approved', - 'quote_settings' => 'Quote Settings', - 'auto_convert_quote' => 'Auto convert quote', - 'auto_convert_quote_help' => 'Automatically convert a quote to an invoice when approved by a client.', - 'validate' => 'Validate', + 'status_approved' => 'Approuvé', + 'quote_settings' => 'Paramètres des devis', + 'auto_convert_quote' => 'Convertir automatiquement les devis', + 'auto_convert_quote_help' => 'Convertissez automatiquement un devis en facture dès qu\'il est approuvé par le client.', + 'validate' => 'Valider', 'info' => 'Info', 'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)', @@ -1105,91 +1105,91 @@ return array( 'expense_error_multiple_currencies' => 'The expenses can\'t have different currencies.', 'expense_error_mismatch_currencies' => 'The client\'s currency does not match the expense currency.', 'trello_roadmap' => 'Trello Roadmap', - 'header_footer' => 'Header/Footer', - 'first_page' => 'first page', - 'all_pages' => 'all pages', - 'last_page' => 'last page', + 'header_footer' => 'En-tête/Pied de page', + 'first_page' => 'première page', + 'all_pages' => 'toutes les pages', + 'last_page' => 'dernière page', 'all_pages_header' => 'Show header on', 'all_pages_footer' => 'Show footer on', - 'invoice_currency' => 'Invoice Currency', - 'enable_https' => 'We strongly recommend using HTTPS to accept credit card details online.', - 'quote_issued_to' => 'Quote issued to', - 'show_currency_code' => 'Currency Code', + 'invoice_currency' => 'Devise de la facture', + 'enable_https' => 'Nous vous recommandons fortement d\'activer le HTTPS si vous acceptez les paiements en ligne.', + 'quote_issued_to' => 'Devis à l\'attention de', + 'show_currency_code' => 'Code de la devise', 'trial_message' => 'Your account will receive a free two week trial of our pro plan.', 'trial_footer' => 'Your free trial lasts :count more days, :link to upgrade now.', - 'trial_footer_last_day' => 'This is the last day of your free trial, :link to upgrade now.', - 'trial_call_to_action' => 'Start Free Trial', + 'trial_footer_last_day' => 'Ceci est le dernier jour de votre essai gratuit, :link pour mettre à niveau maintenant.', + 'trial_call_to_action' => 'Commencer l\'essai gratuit', 'trial_success' => 'Successfully enabled two week free pro plan trial', - 'overdue' => 'Overdue', + 'overdue' => 'Impayé', '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_invoices' => 'Liste des factures', + 'list_clients' => 'Liste des clients', + 'list_quotes' => 'Liste des devis', + 'list_tasks' => 'Liste des tâches', + 'list_expenses' => 'Liste des dépenses', + 'list_recurring_invoices' => 'Liste des factures récurrentes', + 'list_payments' => 'Liste des paiements', 'list_credits' => 'List Credits', - 'tax_name' => 'Tax Name', + 'tax_name' => 'Nom de la taxe', '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', + 'new_user' => 'Nouvel utilisateur', + 'new_product' => 'Nouvel article', + 'new_tax_rate' => 'Nouveau taux de taxe', + 'invoiced_amount' => 'Montant de la facture', '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.', + 'enable_client_portal' => 'Tableau de bord', + 'enable_client_portal_help' => 'Afficher / masquer le tableau de bord sur le portail client.', // Client Passwords - 'enable_portal_password'=>'Password protect invoices', + 'enable_portal_password'=>'Protéger les factures avec un mot de passe', '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'=>'Générer un mot de passe automatiquement', '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', + 'expired' => 'Expiré', + 'invalid_card_number' => 'Le numéro de carte bancaire est invalide.', + 'invalid_expiry' => 'La date d\'expiration est invalide.', + 'invalid_cvv' => 'Le code de sécurité est incorrect.', + 'cost' => 'Coût', '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.', + 'owner' => 'Propriétaire', + 'administrator' => 'Administrateur', + 'administrator_help' => 'Permettre à l\'utilisateur de gérer les utilisateurs, modifier les paramètres et de modifier tous les enregistrements', + 'user_create_all' => 'Créer des clients, des factures, etc.', + 'user_view_all' => 'Voir tous les clients, les factures, etc.', + 'user_edit_all' => 'Modifier tous les clients, les factures, etc.', + 'gateway_help_20' => ':link pour vous inscrire à Sage Pay.', + 'gateway_help_21' => ':link pour vous inscrire à Sage Pay.', 'partial_due' => 'Partial Due', - 'restore_vendor' => 'Restore Vendor', - 'restored_vendor' => 'Successfully restored vendor', - 'restored_expense' => 'Successfully restored expense', + 'restore_vendor' => 'Restorer le fournisseur', + 'restored_vendor' => 'Fournisseur restoré', + 'restored_expense' => 'Dépense restorée', 'permissions' => 'Permissions', - 'create_all_help' => 'Allow user to create and modify records', + 'create_all_help' => 'Autoriser l\'utilisateur à créer et éditer tous les enregistrements', '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', + 'view_payment' => 'Voir le paiement', - 'january' => 'January', - 'february' => 'February', - 'march' => 'March', - 'april' => 'April', - 'may' => 'May', - 'june' => 'June', - 'july' => 'July', - 'august' => 'August', - 'september' => 'September', - 'october' => 'October', - 'november' => 'November', - 'december' => 'December', + 'january' => 'Janvier', + 'february' => 'Février', + 'march' => 'Mars', + 'april' => 'Avril', + 'may' => 'Mai', + 'june' => 'Juin', + 'july' => 'Juillet', + 'august' => 'Août', + 'september' => 'Septembre', + 'october' => 'Octobre', + 'november' => 'Novembre', + 'december' => 'Décembre', ); From 89cf0509d5f87c8cd7195d72f4f085409c96d14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Gonz=C3=A1lez?= Date: Fri, 29 Apr 2016 06:04:04 +0200 Subject: [PATCH 020/111] Typo correction --- resources/lang/de/validation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/de/validation.php b/resources/lang/de/validation.php index e8be6d98b63c..11051273e2f5 100644 --- a/resources/lang/de/validation.php +++ b/resources/lang/de/validation.php @@ -76,7 +76,7 @@ return array( "notmasked" => "Die Werte sind maskiert", "less_than" => ':attribute muss weniger als :value sein', "has_counter" => 'Der Wert muss {$counter} beinhalten', - "valid_contacts" => "Alle Kontake müssen entweder einen Namen oder eine E-Mail Adresse haben", + "valid_contacts" => "Alle Kontakte müssen entweder einen Namen oder eine E-Mail Adresse haben", "valid_invoice_items" => "Die Rechnung übersteigt den maximalen Betrag", /* From 4bc95c4b98cc8230a48059d7d4bb96e0e760a597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Gonz=C3=A1lez?= Date: Fri, 29 Apr 2016 06:45:30 +0200 Subject: [PATCH 021/111] German translations --- resources/lang/de/texts.php | 70 ++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index 08ffaf7b8a0b..16396181c8d0 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -936,30 +936,30 @@ return array( 'invalid_contact_email' => 'Invalid contact email', ], - 'client_portal' => 'Client Portal', + 'client_portal' => 'Klientenportal', 'admin' => 'Admin', - 'disabled' => 'Disabled', - 'show_archived_users' => 'Show archived users', - 'notes' => 'Notes', - 'invoice_will_create' => 'client will be created', - 'invoices_will_create' => 'invoices will be created', - 'failed_to_import' => 'The following records failed to import, they either already exist or are missing required fields.', + 'disabled' => 'Deaktiviert', + 'show_archived_users' => 'Archivierte Benutzer anzeigen', + 'notes' => 'Notizen', + 'invoice_will_create' => 'Klient wird erstellt', + 'invoices_will_create' => 'Rechnungen werden erstellt', + 'failed_to_import' => 'Folgende Einträge konnten nicht importiert werden, weil sie entweder schon existierten, oder ein Pflichtfeld nicht beinhalten.', - 'publishable_key' => 'Publishable Key', - 'secret_key' => 'Secret Key', - 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', + 'publishable_key' => 'Öffentlicher Schlüssel', + 'secret_key' => 'Geheimer Schlüssel', + 'missing_publishable_key' => 'Benutze deinen öffentlichen Stripe-Schlüssel für einen verbesserten Kassiervorgang', - 'email_design' => 'Email Design', - 'due_by' => 'Due by :date', - 'enable_email_markup' => 'Enable Markup', - 'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.', - 'template_help_title' => 'Templates Help', - 'template_help_1' => 'Available variables:', - 'email_design_id' => 'Email Style', - 'email_design_help' => 'Make your emails look more professional with HTML layouts', - 'plain' => 'Plain', - 'light' => 'Light', - 'dark' => 'Dark', + 'email_design' => 'Email-Design', + 'due_by' => 'Fällig bis :date', + 'enable_email_markup' => 'Textauszeichnung aktivieren', + 'enable_email_markup_help' => 'Mache es Klienten einfacher zu bezahlen, indem du schema.org Textauszeichnung zu deinen Emails hinzufügst.', + 'template_help_title' => 'Vorlagen-Hilfe', + 'template_help_1' => 'Verfügbare Variablen:', + 'email_design_id' => 'Email-Stil', + 'email_design_help' => 'Lass deine Mails proffessioneller aussehen mit HTML-Layouts', + 'plain' => 'Einfach', + 'light' => 'Hell', + 'dark' => 'Dunkel', 'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.', 'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', @@ -971,21 +971,21 @@ return array( 'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.', 'color_help' => 'Note: the primary color is also used in the client portal and custom email designs.', - 'token_expired' => 'Validation token was expired. Please try again.', - 'invoice_link' => 'Invoice Link', - 'button_confirmation_message' => 'Click to confirm your email address.', - 'confirm' => 'Confirm', - 'email_preferences' => 'Email Preferences', - 'created_invoices' => 'Successfully created :count invoice(s)', - 'next_invoice_number' => 'The next invoice number is :number.', - 'next_quote_number' => 'The next quote number is :number.', + 'token_expired' => 'Bestätigungss-Token ist abgelaufen. Bitte nochmals probieren.', + 'invoice_link' => 'Rechnungs-Link', + 'button_confirmation_message' => 'Klicken, um Ihre Email-Adresse zu bestätigen.', + 'confirm' => 'Bestätigen', + 'email_preferences' => 'Email-Einstellungen', + 'created_invoices' => ':count Rechnung(en) erfolgreich erstellt', + 'next_invoice_number' => 'Die nächste Rechnungsnummer ist :number.', + 'next_quote_number' => 'Die nächste Angebotsnummer ist :number.', - 'days_before' => 'days before', - 'days_after' => 'days after', - 'field_due_date' => 'due date', - 'field_invoice_date' => 'invoice date', - 'schedule' => 'Schedule', - 'email_designs' => 'Email Designs', + 'days_before' => 'Tage vor', + 'days_after' => 'Tage nach', + 'field_due_date' => 'Fälligkeit', + 'field_invoice_date' => 'Rechnungsdatum', + 'schedule' => 'Zeitplan', + 'email_designs' => 'Email-Designs', 'assigned_when_sent' => 'Assigned when sent', 'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.', From 5fde7a520790d5d34a8084564f5a96064840a658 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 30 Apr 2016 21:02:17 +0300 Subject: [PATCH 022/111] Revert "German translations" --- resources/lang/de/texts.php | 70 ++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index 16396181c8d0..08ffaf7b8a0b 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -936,30 +936,30 @@ return array( 'invalid_contact_email' => 'Invalid contact email', ], - 'client_portal' => 'Klientenportal', + 'client_portal' => 'Client Portal', 'admin' => 'Admin', - 'disabled' => 'Deaktiviert', - 'show_archived_users' => 'Archivierte Benutzer anzeigen', - 'notes' => 'Notizen', - 'invoice_will_create' => 'Klient wird erstellt', - 'invoices_will_create' => 'Rechnungen werden erstellt', - 'failed_to_import' => 'Folgende Einträge konnten nicht importiert werden, weil sie entweder schon existierten, oder ein Pflichtfeld nicht beinhalten.', + 'disabled' => 'Disabled', + 'show_archived_users' => 'Show archived users', + 'notes' => 'Notes', + 'invoice_will_create' => 'client will be created', + 'invoices_will_create' => 'invoices will be created', + 'failed_to_import' => 'The following records failed to import, they either already exist or are missing required fields.', - 'publishable_key' => 'Öffentlicher Schlüssel', - 'secret_key' => 'Geheimer Schlüssel', - 'missing_publishable_key' => 'Benutze deinen öffentlichen Stripe-Schlüssel für einen verbesserten Kassiervorgang', + 'publishable_key' => 'Publishable Key', + 'secret_key' => 'Secret Key', + 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', - 'email_design' => 'Email-Design', - 'due_by' => 'Fällig bis :date', - 'enable_email_markup' => 'Textauszeichnung aktivieren', - 'enable_email_markup_help' => 'Mache es Klienten einfacher zu bezahlen, indem du schema.org Textauszeichnung zu deinen Emails hinzufügst.', - 'template_help_title' => 'Vorlagen-Hilfe', - 'template_help_1' => 'Verfügbare Variablen:', - 'email_design_id' => 'Email-Stil', - 'email_design_help' => 'Lass deine Mails proffessioneller aussehen mit HTML-Layouts', - 'plain' => 'Einfach', - 'light' => 'Hell', - 'dark' => 'Dunkel', + 'email_design' => 'Email Design', + 'due_by' => 'Due by :date', + 'enable_email_markup' => 'Enable Markup', + 'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.', + 'template_help_title' => 'Templates Help', + 'template_help_1' => 'Available variables:', + 'email_design_id' => 'Email Style', + 'email_design_help' => 'Make your emails look more professional with HTML layouts', + 'plain' => 'Plain', + 'light' => 'Light', + 'dark' => 'Dark', 'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.', 'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', @@ -971,21 +971,21 @@ return array( 'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.', 'color_help' => 'Note: the primary color is also used in the client portal and custom email designs.', - 'token_expired' => 'Bestätigungss-Token ist abgelaufen. Bitte nochmals probieren.', - 'invoice_link' => 'Rechnungs-Link', - 'button_confirmation_message' => 'Klicken, um Ihre Email-Adresse zu bestätigen.', - 'confirm' => 'Bestätigen', - 'email_preferences' => 'Email-Einstellungen', - 'created_invoices' => ':count Rechnung(en) erfolgreich erstellt', - 'next_invoice_number' => 'Die nächste Rechnungsnummer ist :number.', - 'next_quote_number' => 'Die nächste Angebotsnummer ist :number.', + 'token_expired' => 'Validation token was expired. Please try again.', + 'invoice_link' => 'Invoice Link', + 'button_confirmation_message' => 'Click to confirm your email address.', + 'confirm' => 'Confirm', + 'email_preferences' => 'Email Preferences', + 'created_invoices' => 'Successfully created :count invoice(s)', + 'next_invoice_number' => 'The next invoice number is :number.', + 'next_quote_number' => 'The next quote number is :number.', - 'days_before' => 'Tage vor', - 'days_after' => 'Tage nach', - 'field_due_date' => 'Fälligkeit', - 'field_invoice_date' => 'Rechnungsdatum', - 'schedule' => 'Zeitplan', - 'email_designs' => 'Email-Designs', + 'days_before' => 'days before', + 'days_after' => 'days after', + 'field_due_date' => 'due date', + 'field_invoice_date' => 'invoice date', + 'schedule' => 'Schedule', + 'email_designs' => 'Email Designs', 'assigned_when_sent' => 'Assigned when sent', 'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.', From 6a86764dee9c3f323d549314d03bddf4ac9e42e3 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 12 May 2016 12:45:36 +0300 Subject: [PATCH 023/111] Fix for #859 --- app/Http/Controllers/ReportController.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index a63b16d9fcc4..4f162f260d37 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -248,11 +248,13 @@ class ReportController extends BaseController ->withArchived() ->with('contacts') ->with(['invoices' => function($query) use ($startDate, $endDate, $dateField) { - $query->withArchived(); - if ($dateField == FILTER_PAYMENT_DATE) { + $query->with('invoice_items')->withArchived(); + if ($dateField == FILTER_INVOICE_DATE) { $query->where('invoice_date', '>=', $startDate) ->where('invoice_date', '<=', $endDate) - ->whereHas('payments', function($query) use ($startDate, $endDate) { + ->with('payments'); + } else { + $query->whereHas('payments', function($query) use ($startDate, $endDate) { $query->where('payment_date', '>=', $startDate) ->where('payment_date', '<=', $endDate) ->withArchived(); @@ -260,9 +262,8 @@ class ReportController extends BaseController ->with(['payments' => function($query) use ($startDate, $endDate) { $query->where('payment_date', '>=', $startDate) ->where('payment_date', '<=', $endDate) - ->withArchived() - ->with('payment_type', 'account_gateway.gateway'); - }, 'invoice_items']); + ->withArchived(); + }]); } }]); From d72b54bb6104de3d945897fb5dda16785e36ef2b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 12 May 2016 12:46:04 +0300 Subject: [PATCH 024/111] Fix problem with migration --- database/migrations/2016_03_22_168362_add_documents.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2016_03_22_168362_add_documents.php b/database/migrations/2016_03_22_168362_add_documents.php index 3d0a44f20c25..233f420c198b 100644 --- a/database/migrations/2016_03_22_168362_add_documents.php +++ b/database/migrations/2016_03_22_168362_add_documents.php @@ -19,7 +19,7 @@ class AddDocuments extends Migration { $table->boolean('document_email_attachment')->default(1); }); - DB::table('accounts')->update(array('logo' => '')); + \DB::table('accounts')->update(array('logo' => '')); Schema::dropIfExists('documents'); Schema::create('documents', function($t) { From 2933ec97caa825a58bdb62697d30157e690c78d7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 12 May 2016 20:29:33 +0300 Subject: [PATCH 025/111] Fix for saving client portal setting in self host --- app/Http/Controllers/AccountController.php | 105 +++++++++++---------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index b3bd13fad409..e67c0908f003 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -117,21 +117,21 @@ class AccountController extends BaseController if (Auth::user()->isPro() && ! Auth::user()->isTrial()) { return false; } - + $invitation = $this->accountRepo->enablePlan(); return $invitation->invitation_key; } - + public function changePlan() { $user = Auth::user(); $account = $user->account; - + $plan = Input::get('plan'); $term = Input::get('plan_term'); - + $planDetails = $account->getPlanDetails(false, false); - + $credit = 0; if ($planDetails) { if ($planDetails['plan'] == PLAN_PRO && $plan == PLAN_ENTERPRISE) { @@ -141,7 +141,7 @@ class AccountController extends BaseController $pending_monthly = true; $term = PLAN_TERM_YEARLY; } - + $new_plan = array( 'plan' => PLAN_ENTERPRISE, 'term' => $term, @@ -169,7 +169,7 @@ class AccountController extends BaseController // Downgrade $refund_deadline = clone $planDetails['started']; $refund_deadline->modify('+30 days'); - + if ($plan == PLAN_FREE && $refund_deadline >= date_create()) { // Refund $account->company->plan = null; @@ -179,10 +179,10 @@ class AccountController extends BaseController $account->company->plan_paid = null; $account->company->pending_plan = null; $account->company->pending_term = null; - + if ($account->company->payment) { $payment = $account->company->payment; - + $gateway = $this->paymentService->createGateway($payment->account_gateway); $refund = $gateway->refund(array( 'transactionReference' => $payment->transaction_reference, @@ -195,9 +195,9 @@ class AccountController extends BaseController } else { Session::flash('message', trans('texts.updated_plan')); } - + $account->company->save(); - + } else { $pending_change = array( 'plan' => $plan, @@ -205,18 +205,18 @@ class AccountController extends BaseController ); } } - + if (!empty($new_plan)) { $time_used = $planDetails['paid']->diff(date_create()); $days_used = $time_used->days; - + if ($time_used->invert) { // They paid in advance $days_used *= -1; } - + $days_total = $planDetails['paid']->diff($planDetails['expires'])->days; - + $percent_used = $days_used / $days_total; $old_plan_price = Account::$plan_prices[$planDetails['plan']][$planDetails['term']]; $credit = $old_plan_price * (1 - $percent_used); @@ -227,20 +227,20 @@ class AccountController extends BaseController 'term' => $term, ); } - + if (!empty($pending_change) && empty($new_plan)) { $account->company->pending_plan = $pending_change['plan']; $account->company->pending_term = $pending_change['term']; $account->company->save(); - + Session::flash('message', trans('texts.updated_plan')); } - + if (!empty($new_plan)) { $invitation = $this->accountRepo->enablePlan($new_plan['plan'], $new_plan['term'], $credit, !empty($pending_monthly)); return Redirect::to('view/'.$invitation->invitation_key); } - + return Redirect::to('/settings/'.ACCOUNT_MANAGEMENT, 301); } @@ -499,7 +499,7 @@ class AccountController extends BaseController $client->postal_code = trans('texts.postal_code'); $client->work_phone = trans('texts.work_phone'); $client->work_email = trans('texts.work_id'); - + $invoice->invoice_number = '0000'; $invoice->invoice_date = Utils::fromSqlDate(date('Y-m-d')); $invoice->account = json_decode($account->toJson()); @@ -517,7 +517,7 @@ class AccountController extends BaseController $invoiceItem->product_key = 'Item'; $document->base64 = 'data:image/jpeg;base64,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'; - + $invoice->client = $client; $invoice->invoice_items = [$invoiceItem]; //$invoice->documents = $account->hasFeature(FEATURE_DOCUMENTS) ? [$document] : []; @@ -530,7 +530,7 @@ class AccountController extends BaseController $data['invoiceDesigns'] = InvoiceDesign::getDesigns(); $data['invoiceFonts'] = Cache::get('fonts'); $data['section'] = $section; - + $pageSizes = [ 'A0', 'A1', @@ -708,6 +708,13 @@ class AccountController extends BaseController private function saveClientPortal() { + $account = Auth::user()->account; + + $account->enable_client_portal = !!Input::get('enable_client_portal'); + $account->enable_client_portal_dashboard = !!Input::get('enable_client_portal_dashboard'); + $account->enable_portal_password = !!Input::get('enable_portal_password'); + $account->send_portal_password = !!Input::get('send_portal_password'); + // Only allowed for pro Invoice Ninja users or white labeled self-hosted users if (Auth::user()->account->hasFeature(FEATURE_CLIENT_PORTAL_CSS)) { $input_css = Input::get('client_view_css'); @@ -742,22 +749,16 @@ class AccountController extends BaseController $sanitized_css = $input_css; } - $account = Auth::user()->account; $account->client_view_css = $sanitized_css; - - $account->enable_client_portal = !!Input::get('enable_client_portal'); - $account->enable_client_portal_dashboard = !!Input::get('enable_client_portal_dashboard'); - $account->enable_portal_password = !!Input::get('enable_portal_password'); - $account->send_portal_password = !!Input::get('send_portal_password'); - - $account->save(); - - Session::flash('message', trans('texts.updated_settings')); } + $account->save(); + + Session::flash('message', trans('texts.updated_settings')); + return Redirect::to('settings/'.ACCOUNT_CLIENT_PORTAL); } - + private function saveEmailTemplates() { if (Auth::user()->account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) { @@ -1010,15 +1011,15 @@ class AccountController extends BaseController /* Logo image file */ if ($uploaded = Input::file('logo')) { $path = Input::file('logo')->getRealPath(); - + $disk = $account->getLogoDisk(); if ($account->hasLogo()) { $disk->delete($account->logo); } - + $extension = strtolower($uploaded->getClientOriginalExtension()); if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){ - $documentType = Document::$extraExtensions[$extension]; + $documentType = Document::$extraExtensions[$extension]; } else{ $documentType = $extension; @@ -1037,19 +1038,19 @@ class AccountController extends BaseController } else { if ($documentType != 'gif') { $account->logo = $account->account_key.'.'.$documentType; - + $imageSize = getimagesize($filePath); $account->logo_width = $imageSize[0]; $account->logo_height = $imageSize[1]; $account->logo_size = $size; - + // make sure image isn't interlaced if (extension_loaded('fileinfo')) { $image = Image::make($path); $image->interlace(false); $imageStr = (string) $image->encode($documentType); $disk->put($account->logo, $imageStr); - + $account->logo_size = strlen($imageStr); } else { $stream = fopen($filePath, 'r'); @@ -1062,12 +1063,12 @@ class AccountController extends BaseController $image->resize(200, 120, function ($constraint) { $constraint->aspectRatio(); }); - + $account->logo = $account->account_key.'.png'; $image = Image::canvas($image->width(), $image->height(), '#FFFFFF')->insert($image); $imageStr = (string) $image->encode('png'); $disk->put($account->logo, $imageStr); - + $account->logo_size = strlen($imageStr); $account->logo_width = $image->width(); $account->logo_height = $image->height(); @@ -1077,7 +1078,7 @@ class AccountController extends BaseController } } } - + $account->save(); } @@ -1147,7 +1148,7 @@ class AccountController extends BaseController $account = Auth::user()->account; if ($account->hasLogo()) { $account->getLogoDisk()->delete($account->logo); - + $account->logo = null; $account->logo_size = null; $account->logo_width = null; @@ -1252,7 +1253,7 @@ class AccountController extends BaseController $this->accountRepo->unlinkAccount($account); if ($account->company->accounts->count() == 1) { - $account->company->forceDelete(); + $account->company->forceDelete(); } $account->forceDelete(); @@ -1300,7 +1301,7 @@ class AccountController extends BaseController return Redirect::to("/settings/$section/", 301); } - + public function previewEmail(\App\Services\TemplateService $templateService) { $template = Input::get('template'); @@ -1308,29 +1309,29 @@ class AccountController extends BaseController ->invoices() ->withTrashed() ->first(); - + if ( ! $invoice) { return trans('texts.create_invoice_for_sample'); } - + $account = Auth::user()->account; - + // replace the variables with sample data $data = [ 'account' => $account, 'invoice' => $invoice, 'invitation' => $invoice->invitations->first(), 'client' => $invoice->client, - 'amount' => $invoice->amount + 'amount' => $invoice->amount ]; - - // create the email view + + // create the email view $view = 'emails.' . $account->getTemplateView(ENTITY_INVOICE) . '_html'; $data = array_merge($data, [ 'body' => $templateService->processVariables($template, $data), 'entityType' => ENTITY_INVOICE, ]); - + return Response::view($view, $data); } } From 6b203fcd5e2d3e7bc423b79586e0df8a29b02274 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 15 May 2016 14:07:04 +0300 Subject: [PATCH 026/111] Fix for creating vendors when importing OFX transactions --- app/Services/BankAccountService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/BankAccountService.php b/app/Services/BankAccountService.php index 72aada6e3ff9..4a3551c782fa 100644 --- a/app/Services/BankAccountService.php +++ b/app/Services/BankAccountService.php @@ -178,7 +178,7 @@ class BankAccountService extends BaseService $field => $info, 'name' => $vendorName, 'transaction_name' => $transaction['vendor_orig'], - 'vendorcontact' => [], + 'vendor_contact' => [], ]); $vendorMap[$key] = $vendor; $vendorMap[$transaction['vendor_orig']] = $vendor; From dea596566f0d0149cea677300074f288ef7763e2 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 16 May 2016 21:07:08 +0300 Subject: [PATCH 027/111] Fix for #867 --- resources/views/payments/payment.blade.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/resources/views/payments/payment.blade.php b/resources/views/payments/payment.blade.php index 949723e7f17a..556695b69460 100644 --- a/resources/views/payments/payment.blade.php +++ b/resources/views/payments/payment.blade.php @@ -13,7 +13,6 @@ var data = { name: $('#first_name').val() + ' ' + $('#last_name').val(), - email: $('#email').val(), address_line1: $('#address1').val(), address_line2: $('#address2').val(), address_city: $('#city').val(), @@ -24,7 +23,7 @@ exp_month: $('#expiration_month').val(), exp_year: $('#expiration_year').val() }; - + // allow space until there's a setting to disable if ($('#cvv').val() != ' ') { data.cvc = $('#cvv').val(); @@ -39,16 +38,16 @@ $('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn(); return false; } - + if (data.hasOwnProperty('cvc') && !Stripe.card.validateCVC(data.cvc)) { $('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn(); return false; } - + // Disable the submit button to prevent repeated clicks $form.find('button').prop('disabled', true); $('#js-error-message').hide(); - + Stripe.card.createToken(data, stripeResponseHandler); // Prevent the form from submitting with the default action @@ -324,9 +323,9 @@ {!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) )) ->submit() ->large() !!} - +

     

    - + @@ -354,4 +353,4 @@ -@stop \ No newline at end of file +@stop From 88ffc4f08ce97034baadaaaaf0c991fe5a9bfb68 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 17 May 2016 11:49:18 +0300 Subject: [PATCH 028/111] Added support for GAE filesystem --- composer.json | 7 +- composer.lock | 247 +++++++++++++++++++++++++++++++---------- config/app.php | 4 +- config/filesystems.php | 11 +- 4 files changed, 204 insertions(+), 65 deletions(-) diff --git a/composer.json b/composer.json index 9bb49326b1f8..56b8c0d1aaca 100644 --- a/composer.json +++ b/composer.json @@ -72,14 +72,15 @@ "asgrim/ofxparser": "^1.1", "league/flysystem-aws-s3-v3": "~1.0", "league/flysystem-rackspace": "~1.0", - "barracudanetworks/archivestream-php": "^1.0" + "barracudanetworks/archivestream-php": "^1.0", + "websight/l5-google-cloud-storage": "^1.0", + "fzaninotto/faker": "^1.5" }, "require-dev": { "phpunit/phpunit": "~4.0", "phpspec/phpspec": "~2.1", "codeception/codeception": "*", "codeception/c3": "~2.0", - "fzaninotto/faker": "^1.5", "symfony/dom-crawler": "~3.0" }, "autoload": { @@ -122,4 +123,4 @@ "config": { "preferred-install": "dist" } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 4dd6f3db14ed..e9649ae9fcb3 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": "cf82d2ddb25cb1a7d6b4867bcc8692b8", - "content-hash": "481a95753b873249aebceb99e7426421", + "hash": "959e373ec2049b8d396f90b313da08b5", + "content-hash": "0a191e645db4d3edd709ec706c26503d", "packages": [ { "name": "agmscode/omnipay-agms", @@ -127,7 +127,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/formers/former/zipball/78ae8c65b1f8134e2db1c9491c251c03638823ca", + "url": "https://api.github.com/repos/formers/former/zipball/37f6876a5d211427b5c445cd64f0eb637f42f685", "reference": "d97f907741323b390f43954a90a227921ecc6b96", "shasum": "" }, @@ -505,7 +505,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e97ed532f09e290b91ff7713b785ed7ab11d0812", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/35ebf3a2ba9443e11fbdb9066cc363ec7b2245e4", "reference": "e97ed532f09e290b91ff7713b785ed7ab11d0812", "shasum": "" }, @@ -1964,6 +1964,97 @@ ], "time": "2015-01-16 08:41:13" }, + { + "name": "fzaninotto/faker", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "shasum": "" + }, + "require": { + "php": "^5.3.3|^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2016-04-29 12:21:54" + }, + { + "name": "google/apiclient", + "version": "1.1.7", + "source": { + "type": "git", + "url": "https://github.com/google/google-api-php-client.git", + "reference": "400f250a30ae1dd4c4a0a4f750fe973fc70e6311" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/google-api-php-client/zipball/400f250a30ae1dd4c4a0a4f750fe973fc70e6311", + "reference": "400f250a30ae1dd4c4a0a4f750fe973fc70e6311", + "shasum": "" + }, + "require": { + "php": ">=5.2.1" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "time": "2016-02-02 18:50:42" + }, { "name": "guzzle/guzzle", "version": "v3.8.1", @@ -2338,7 +2429,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/e368d262887dbb2fdfaf710880571ede51e9c0e6", + "url": "https://api.github.com/repos/Intervention/image/zipball/22088b04728a039bd1fc32f7e79a89a118b78698", "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6", "shasum": "" }, @@ -2727,7 +2818,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/40c9f86df6573ad98ae1dd0d29712ccbc789a74e", + "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/c8d80c3b48bae2bab071f283f75b1cd8624ed3c7", "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e", "shasum": "" }, @@ -6489,6 +6580,53 @@ ], "time": "2015-08-12 08:09:37" }, + { + "name": "superbalist/flysystem-google-storage", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Superbalist/flysystem-google-storage.git", + "reference": "441a8529680986a2d2063a268ee2918f51db1b79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Superbalist/flysystem-google-storage/zipball/441a8529680986a2d2063a268ee2918f51db1b79", + "reference": "441a8529680986a2d2063a268ee2918f51db1b79", + "shasum": "" + }, + "require": { + "google/apiclient": "~1.1|^2.0.0@RC", + "league/flysystem": "~1.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Superbalist\\Flysystem\\GoogleStorage\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Superbalist.com a division of Takealot Online (Pty) Ltd", + "email": "info@superbalist.com" + } + ], + "description": "Flysystem adapter for Google Cloud Storage", + "time": "2016-04-12 14:56:22" + }, { "name": "swiftmailer/swiftmailer", "version": "v5.4.1", @@ -7936,6 +8074,49 @@ ], "time": "2016-02-25 10:29:59" }, + { + "name": "websight/l5-google-cloud-storage", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/websightgmbh/l5-google-cloud-storage.git", + "reference": "c1cac9985dfce60010234c9dca127f3543d2d594" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/websightgmbh/l5-google-cloud-storage/zipball/c1cac9985dfce60010234c9dca127f3543d2d594", + "reference": "c1cac9985dfce60010234c9dca127f3543d2d594", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.0.17|5.1.*|5.2.*", + "superbalist/flysystem-google-storage": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Websight\\GcsProvider\\": "src/Websight/GcsProvider/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cedric Ziel", + "email": "ziel@websight.de" + } + ], + "description": "Laravel 5 Flysystem Google Cloud Storage Service Provider", + "homepage": "https://github.com/websightgmbh/l5-google-cloud-storage", + "keywords": [ + "Flysystem", + "laravel", + "laravel5" + ], + "time": "2016-03-04 11:57:00" + }, { "name": "wildbit/laravel-postmark-provider", "version": "3.0.0", @@ -8740,58 +8921,6 @@ ], "time": "2015-12-31 15:58:49" }, - { - "name": "fzaninotto/faker", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "d0190b156bcca848d401fb80f31f504f37141c8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d0190b156bcca848d401fb80f31f504f37141c8d", - "reference": "d0190b156bcca848d401fb80f31f504f37141c8d", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "suggest": { - "ext-intl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2015-05-29 06:29:14" - }, { "name": "phpspec/php-diff", "version": "v1.0.2", @@ -9900,4 +10029,4 @@ "prefer-lowest": false, "platform": [], "platform-dev": [] -} \ No newline at end of file +} diff --git a/config/app.php b/config/app.php index 4a75f65f9e5f..d9b9aa916b30 100644 --- a/config/app.php +++ b/config/app.php @@ -139,7 +139,7 @@ return [ 'Illuminate\Validation\ValidationServiceProvider', 'Illuminate\View\ViewServiceProvider', 'Illuminate\Broadcasting\BroadcastServiceProvider', - + /* * Additional Providers */ @@ -153,6 +153,7 @@ return [ 'Laravel\Socialite\SocialiteServiceProvider', 'Jlapp\Swaggervel\SwaggervelServiceProvider', 'Maatwebsite\Excel\ExcelServiceProvider', + Websight\GcsProvider\CloudStorageServiceProvider::class, /* * Application Service Providers... @@ -211,6 +212,7 @@ return [ 'Schema' => 'Illuminate\Support\Facades\Schema', 'Seeder' => 'Illuminate\Database\Seeder', 'Session' => 'Illuminate\Support\Facades\Session', + 'Storage' => 'Illuminate\Support\Facades\Storage', 'Str' => 'Illuminate\Support\Str', 'URL' => 'Illuminate\Support\Facades\URL', 'Validator' => 'Illuminate\Support\Facades\Validator', diff --git a/config/filesystems.php b/config/filesystems.php index da16e0e16766..68e35a498670 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -47,12 +47,12 @@ return [ 'driver' => 'local', 'root' => storage_path().'/app', ], - + 'logos' => [ 'driver' => 'local', 'root' => env('LOGO_PATH', public_path().'/logo'), ], - + 'documents' => [ 'driver' => 'local', 'root' => storage_path().'/documents', @@ -76,6 +76,13 @@ return [ 'url_type' => env('RACKSPACE_URL_TYPE', 'publicURL') ], + 'gcs' => [ + 'driver' => 'gcs', + 'service_account' => env('GCS_USERNAME', ''), + 'service_account_certificate' => storage_path() . '/credentials.p12', + 'service_account_certificate_password' => env('GCS_PASSWORD', ''), + 'bucket' => env('GCS_BUCKET', 'cloud-storage-bucket'), + ], ], ]; From 3ed5fdf09f262fc8643c4b9bcbf8ee50901a36df Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 17 May 2016 14:36:12 +0300 Subject: [PATCH 029/111] Disabled iOS push notifications by default --- app/Http/routes.php | 27 ++++++++++++------------- app/Ninja/Notifications/PushFactory.php | 7 +++---- app/Services/PushService.php | 6 +++++- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index 15f0b247a144..1785c18772ca 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -52,7 +52,7 @@ Route::group(['middleware' => 'auth:client'], function() { Route::get('client/documents/js/{documents}/{filename}', 'PublicClientController@getDocumentVFSJS'); Route::get('client/documents/{invitation_key}/{documents}/{filename?}', 'PublicClientController@getDocument'); Route::get('client/documents/{invitation_key}/{filename?}', 'PublicClientController@getInvoiceDocumentsZip'); - + Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable')); Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable')); Route::get('api/client.documents', array('as'=>'api.client.documents', 'uses'=>'PublicClientController@documentDatatable')); @@ -112,7 +112,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('hide_message', 'HomeController@hideMessage'); Route::get('force_inline_pdf', 'UserController@forcePDFJS'); Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData')); - + Route::get('settings/user_details', 'AccountController@showUserDetails'); Route::post('settings/user_details', 'AccountController@saveUserDetails'); Route::post('users/change_password', 'UserController@changePassword'); @@ -145,7 +145,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('documents/js/{documents}/{filename}', 'DocumentController@getVFSJS'); Route::get('documents/preview/{documents}/{filename?}', 'DocumentController@getPreview'); Route::post('document', 'DocumentController@postUpload'); - + Route::get('quotes/create/{client_id?}', 'QuoteController@create'); Route::get('quotes/{invoices}/clone', 'InvoiceController@cloneInvoice'); Route::get('quotes/{invoices}/edit', 'InvoiceController@edit'); @@ -600,9 +600,8 @@ if (!defined('CONTACT_EMAIL')) { define('DEFAULT_API_PAGE_SIZE', 15); define('MAX_API_PAGE_SIZE', 100); - define('IOS_PRODUCTION_PUSH', env('IOS_PRODUCTION_PUSH', 'ninjaIOS')); - define('IOS_DEV_PUSH', env('IOS_DEV_PUSH', 'devNinjaIOS')); - + define('IOS_PUSH_CERTIFICATE', env('IOS_PUSH_CERTIFICATE', '')); + define('TOKEN_BILLING_DISABLED', 1); define('TOKEN_BILLING_OPT_IN', 2); define('TOKEN_BILLING_OPT_OUT', 3); @@ -651,7 +650,7 @@ if (!defined('CONTACT_EMAIL')) { define('RESELLER_REVENUE_SHARE', 'A'); define('RESELLER_LIMITED_USERS', 'B'); - + // These must be lowercase define('PLAN_FREE', 'free'); define('PLAN_PRO', 'pro'); @@ -659,7 +658,7 @@ if (!defined('CONTACT_EMAIL')) { define('PLAN_WHITE_LABEL', 'white_label'); define('PLAN_TERM_MONTHLY', 'month'); define('PLAN_TERM_YEARLY', 'year'); - + // Pro define('FEATURE_CUSTOMIZE_INVOICE_DESIGN', 'customize_invoice_design'); define('FEATURE_REMOVE_CREATED_BY', 'remove_created_by'); @@ -674,23 +673,23 @@ if (!defined('CONTACT_EMAIL')) { define('FEATURE_API', 'api'); define('FEATURE_CLIENT_PORTAL_PASSWORD', 'client_portal_password'); define('FEATURE_CUSTOM_URL', 'custom_url'); - + define('FEATURE_MORE_CLIENTS', 'more_clients'); // No trial allowed - + // Whitelabel define('FEATURE_CLIENT_PORTAL_CSS', 'client_portal_css'); define('FEATURE_WHITE_LABEL', 'feature_white_label'); // Enterprise define('FEATURE_DOCUMENTS', 'documents'); - + // No Trial allowed define('FEATURE_USERS', 'users');// Grandfathered for old Pro users define('FEATURE_USER_PERMISSIONS', 'user_permissions'); - + // Pro users who started paying on or before this date will be able to manage users define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-05-15'); - + $creditCards = [ 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], @@ -746,4 +745,4 @@ if (Utils::isNinjaDev()) //ini_set('memory_limit','1024M'); //Auth::loginUsingId(1); } -*/ \ No newline at end of file +*/ diff --git a/app/Ninja/Notifications/PushFactory.php b/app/Ninja/Notifications/PushFactory.php index 7f882ff1d0a0..723ff824debe 100644 --- a/app/Ninja/Notifications/PushFactory.php +++ b/app/Ninja/Notifications/PushFactory.php @@ -19,13 +19,12 @@ class PushFactory * * Static variables defined in routes.php * - * IOS_PRODUCTION_PUSH - * IOS_DEV_PUSH + * IOS_PUSH_CERTIFICATE */ public function __construct() { - $this->certificate = IOS_DEV_PUSH; + $this->certificate = IOS_PUSH_CERTIFICATE; } /** @@ -93,4 +92,4 @@ class PushFactory return $feedback->getFeedback(); } -} \ No newline at end of file +} diff --git a/app/Services/PushService.php b/app/Services/PushService.php index 17151f93af3f..a96042b92419 100644 --- a/app/Services/PushService.php +++ b/app/Services/PushService.php @@ -41,6 +41,10 @@ class PushService public function sendNotification($invoice, $type) { + if (! IOS_PUSH_CERTIFICATE) { + return; + } + //check user has registered for push notifications if(!$this->checkDeviceExists($invoice->account)) return; @@ -168,4 +172,4 @@ class PushService -} \ No newline at end of file +} From 861672e9e935d4c3269f89483780d7ffbfbc3cff Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 17 May 2016 15:05:07 +0300 Subject: [PATCH 030/111] Fix for deleting account --- app/Http/Controllers/AccountController.php | 5 +- .../2016_04_16_103943_enterprise_plan.php | 48 +++++++++---------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index e67c0908f003..d7ccfd1dc995 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -758,7 +758,7 @@ class AccountController extends BaseController return Redirect::to('settings/'.ACCOUNT_CLIENT_PORTAL); } - + private function saveEmailTemplates() { if (Auth::user()->account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) { @@ -1254,8 +1254,9 @@ class AccountController extends BaseController $this->accountRepo->unlinkAccount($account); if ($account->company->accounts->count() == 1) { $account->company->forceDelete(); + } else { + $account->forceDelete(); } - $account->forceDelete(); Auth::logout(); Session::flush(); diff --git a/database/migrations/2016_04_16_103943_enterprise_plan.php b/database/migrations/2016_04_16_103943_enterprise_plan.php index 8a3a63717367..fa86ef19a15f 100644 --- a/database/migrations/2016_04_16_103943_enterprise_plan.php +++ b/database/migrations/2016_04_16_103943_enterprise_plan.php @@ -19,10 +19,10 @@ class EnterprisePlan extends Migration } $timeout = max($timeout - 10, $timeout * .9); $startTime = time(); - + if (!Schema::hasTable('companies')) { Schema::create('companies', function($table) - { + { $table->increments('id'); $table->enum('plan', array('pro', 'enterprise', 'white_label'))->nullable(); @@ -44,15 +44,15 @@ class EnterprisePlan extends Migration $table->softDeletes(); }); } - + if (!Schema::hasColumn('accounts', 'company_id')) { Schema::table('accounts', function($table) { $table->unsignedInteger('company_id')->nullable(); - $table->foreign('company_id')->references('id')->on('companies'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); }); - } - + } + $single_account_ids = \DB::table('users') ->leftJoin('user_accounts', function ($join) { $join->on('user_accounts.user_id1', '=', 'users.id'); @@ -69,14 +69,14 @@ class EnterprisePlan extends Migration $query->orWhere('users.public_id', '=', 0); }) ->lists('users.account_id'); - + if (count($single_account_ids)) { foreach (Account::find($single_account_ids) as $account) { $this->upAccounts($account); $this->checkTimeout($timeout, $startTime); } } - + $group_accounts = \DB::select( 'SELECT u1.account_id as account1, u2.account_id as account2, u3.account_id as account3, u4.account_id as account4, u5.account_id as account5 FROM `user_accounts` LEFT JOIN users u1 ON (u1.public_id IS NULL OR u1.public_id = 0) AND user_accounts.user_id1 = u1.id @@ -94,14 +94,14 @@ class EnterprisePlan extends Migration OR (a3.id IS NOT NULL AND a3.company_id IS NULL) OR (a4.id IS NOT NULL AND a4.company_id IS NULL) OR (a5.id IS NOT NULL AND a5.company_id IS NULL)'); - + if (count($group_accounts)) { foreach ($group_accounts as $group_account) { $this->upAccounts(null, Account::find(get_object_vars($group_account))); $this->checkTimeout($timeout, $startTime); } } - + if (Schema::hasColumn('accounts', 'pro_plan_paid')) { Schema::table('accounts', function($table) { @@ -110,16 +110,16 @@ class EnterprisePlan extends Migration }); } } - + private function upAccounts($primaryAccount, $otherAccounts = array()) { if(!$primaryAccount) { $primaryAccount = $otherAccounts->first(); } - + if (empty($primaryAccount)) { return; } - + $company = Company::create(); if ($primaryAccount->pro_plan_paid && $primaryAccount->pro_plan_paid != '0000-00-00') { $company->plan = 'pro'; @@ -145,7 +145,7 @@ class EnterprisePlan extends Migration } } elseif ($company->plan_paid != NINJA_DATE) { $company->plan_expires = $expires; - } + } } if ($primaryAccount->pro_plan_trial && $primaryAccount->pro_plan_trial != '0000-00-00') { @@ -157,7 +157,7 @@ class EnterprisePlan extends Migration $primaryAccount->company_id = $company->id; $primaryAccount->save(); - + if (!empty($otherAccounts)) { foreach ($otherAccounts as $account) { if ($account && $account->id != $primaryAccount->id) { @@ -167,13 +167,13 @@ class EnterprisePlan extends Migration } } } - + protected function checkTimeout($timeout, $startTime) { if (time() - $startTime >= $timeout) { exit('Migration reached time limit; please run again to continue'); } } - + /** * Reverse the migrations. * @@ -187,7 +187,7 @@ class EnterprisePlan extends Migration } $timeout = max($timeout - 10, $timeout * .9); $startTime = time(); - + if (!Schema::hasColumn('accounts', 'pro_plan_paid')) { Schema::table('accounts', function($table) { @@ -195,7 +195,7 @@ class EnterprisePlan extends Migration $table->date('pro_plan_trial')->nullable(); }); } - + $company_ids = \DB::table('companies') ->leftJoin('accounts', 'accounts.company_id', '=', 'companies.id') ->whereNull('accounts.pro_plan_paid') @@ -205,9 +205,9 @@ class EnterprisePlan extends Migration $query->orWhereNotNull('companies.trial_started'); }) ->lists('companies.id'); - + $company_ids = array_unique($company_ids); - + if (count($company_ids)) { foreach (Company::find($company_ids) as $company) { foreach ($company->accounts as $account) { @@ -218,7 +218,7 @@ class EnterprisePlan extends Migration $this->checkTimeout($timeout, $startTime); } } - + if (Schema::hasColumn('accounts', 'company_id')) { Schema::table('accounts', function($table) { @@ -226,7 +226,7 @@ class EnterprisePlan extends Migration $table->dropColumn('company_id'); }); } - + Schema::dropIfExists('companies'); } -} \ No newline at end of file +} From 29616b292758255eb1fbaf134f12cbf22fbccb80 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 17 May 2016 22:06:15 +0300 Subject: [PATCH 031/111] Added initial CONTRIBUTING.md --- readme.md => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename readme.md => README.md (100%) diff --git a/readme.md b/README.md similarity index 100% rename from readme.md rename to README.md From 49b26152eab2d9cf298365b9f8d3b2a2afba4a76 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 17 May 2016 22:09:20 +0300 Subject: [PATCH 032/111] Added initial CONTRIBUTING.md --- CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000000..1e26492d5d83 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Contributing to Invoice Ninja + +We welcome contributions! We'll improve this guide over time... + +*Please note: although our application is open-source we run a for-profit hosted service at [invoiceninja.com](https://www.invoiceninja.com).* + +Guidelines +- Please try to follow [PSR-2 guidlines](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) +- Add translations in our [Transifex](https://www.transifex.com/invoice-ninja/) project From 26e4fadbec446baf06188b136d3626e86836dd14 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 17 May 2016 23:45:00 +0300 Subject: [PATCH 033/111] Updated readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 54057e1a3782..bc8085eec9c0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ [![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=master)](https://travis-ci.org/invoiceninja/invoiceninja) [![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +**To update the translations please use [Transifex](https://www.transifex.com/invoice-ninja/invoice-ninja/dashboard/)** + ### Affiliates Programs * Referral program (we pay you): $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/) * White-label reseller (you pay us): 10% of revenue with a $100 sign up fee From 17ea888e9013bca556d6e8abb3b26cab95171cbc Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 18 May 2016 10:12:58 +0300 Subject: [PATCH 034/111] Bumped version number --- app/Http/routes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index 1785c18772ca..29ad336a00d9 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -553,7 +553,7 @@ if (!defined('CONTACT_EMAIL')) { define('NINJA_WEB_URL', env('NINJA_WEB_URL', 'https://www.invoiceninja.com')); define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); define('NINJA_DATE', '2000-01-01'); - define('NINJA_VERSION', '2.5.2' . env('NINJA_VERSION_SUFFIX')); + define('NINJA_VERSION', '2.5.2.1' . env('NINJA_VERSION_SUFFIX')); define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); @@ -601,7 +601,7 @@ if (!defined('CONTACT_EMAIL')) { define('MAX_API_PAGE_SIZE', 100); define('IOS_PUSH_CERTIFICATE', env('IOS_PUSH_CERTIFICATE', '')); - + define('TOKEN_BILLING_DISABLED', 1); define('TOKEN_BILLING_OPT_IN', 2); define('TOKEN_BILLING_OPT_OUT', 3); From 4aa11be28f9dc612f516cbe3ed3064f91a622fbf Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 22 May 2016 22:02:42 +0300 Subject: [PATCH 035/111] Added user permission to tranformer --- app/Ninja/Transformers/UserTransformer.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Ninja/Transformers/UserTransformer.php b/app/Ninja/Transformers/UserTransformer.php index 532c1f6fa4f2..1619deb1f2c2 100644 --- a/app/Ninja/Transformers/UserTransformer.php +++ b/app/Ninja/Transformers/UserTransformer.php @@ -26,6 +26,8 @@ class UserTransformer extends EntityTransformer 'notify_viewed' => (bool) $user->notify_viewed, 'notify_paid' => (bool) $user->notify_paid, 'notify_approved' => (bool) $user->notify_approved, + 'is_admin' => (bool) $user->is_admin, + 'permissions' => (int) $user->getOriginal('permissions'), ]; } -} \ No newline at end of file +} From 05903d413aebbdb16ee54779e7c6ba91e6b81b1f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 22 May 2016 22:17:57 +0300 Subject: [PATCH 036/111] Open document when clicked --- resources/views/expenses/edit.blade.php | 24 ++++++++++------- resources/views/invoices/edit.blade.php | 36 ++++++++++++++----------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 49e3b9e379d3..920162e67dd2 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -14,7 +14,7 @@ @stop @section('content') - + {!! Former::open($url)->addClass('warn-on-exit main-form')->method($method) !!}
    {!! Former::text('action') !!} @@ -216,13 +216,13 @@ @else $('#amount').focus(); @endif - + @if (Auth::user()->account->isPro()) $('.main-form').submit(function(){ if($('#document-upload .fallback input').val())$(this).attr('enctype', 'multipart/form-data') else $(this).removeAttr('enctype') }) - + // Initialize document upload dropzone = new Dropzone('#document-upload', { url:{!! json_encode(url('document')) !!}, @@ -286,7 +286,7 @@ } } } - + if (data) { ko.mapping.fromJS(data, self.mapping, this); } @@ -327,11 +327,11 @@ } var expenseCurrencyId = self.expense_currency_id() || self.account_currency_id(); var invoiceCurrencyId = self.invoice_currency_id() || self.account_currency_id(); - return expenseCurrencyId != invoiceCurrencyId + return expenseCurrencyId != invoiceCurrencyId || invoiceCurrencyId != self.account_currency_id() || expenseCurrencyId != self.account_currency_id(); }) - + self.addDocument = function() { var documentModel = new DocumentModel(); self.documents.push(documentModel); @@ -359,11 +359,17 @@ if (data) { self.update(data); - } + } } - + @if (Auth::user()->account->hasFeature(FEATURE_DOCUMENTS)) function handleDocumentAdded(file){ + // open document when clicked + if (file.url) { + file.previewElement.addEventListener("click", function() { + window.open(file.url, '_blank'); + }); + } if(file.mock)return; file.index = model.documents().length; model.addDocument({name:file.name, size:file.size, type:file.type}); @@ -384,4 +390,4 @@ @endif -@stop \ No newline at end of file +@stop diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 09be1ed4134e..4f1d96f20ad6 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -17,7 +17,7 @@ label.control-label[for=invoice_number] { font-weight: normal !important; } - + select.tax-select { width: 50%; float: left; @@ -79,7 +79,7 @@   
    {{ trans('texts.deleted') }}
    @endif - + @can('view', $invoice->client) @can('edit', $invoice->client) {{ trans('texts.edit_client') }} | @@ -90,7 +90,7 @@
    @endif - + {!! Former::select('client')->addOption('', '')->data_bind("dropdown: client")->addClass('client-input')->addGroupClass('client_select closer-row') !!}
    @@ -419,7 +419,7 @@ ->options($taxRateOptions) ->addClass('tax-select') ->data_bind('value: tax1') - ->raw() !!} + ->raw() !!} {!! Former::select('') @@ -427,7 +427,7 @@ ->options($taxRateOptions) ->addClass('tax-select') ->data_bind('value: tax2') - ->raw() !!} + ->raw() !!} @@ -569,7 +569,7 @@ {!! Former::text('client[work_phone]') ->label('work_phone') ->data_bind("value: work_phone, valueUpdate: 'afterkeydown'") !!} - + @if (Auth::user()->hasFeature(FEATURE_INVOICE_SETTINGS)) @@ -756,7 +756,7 @@ var $clientSelect = $('select#client'); var invoiceDesigns = {!! $invoiceDesigns !!}; var invoiceFonts = {!! $invoiceFonts !!}; - + $(function() { // create client dictionary for (var i=0; iaccount->hasFeature(FEATURE_DOCUMENTS)) $('.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', { url:{!! json_encode(url('document')) !!}, @@ -1224,7 +1224,7 @@ } onPartialChange(true); - + return true; } @@ -1379,24 +1379,30 @@ number = number.replace('{$custom2}', client.custom_value2 ? client.custom_value1 : ''); model.invoice().invoice_number(number); } - + @if ($account->hasFeature(FEATURE_DOCUMENTS)) function handleDocumentAdded(file){ + // open document when clicked + if (file.url) { + file.previewElement.addEventListener("click", function() { + window.open(file.url, '_blank'); + }); + } if(file.mock)return; file.index = model.invoice().documents().length; model.invoice().addDocument({name:file.name, size:file.size, type:file.type}); } - + 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); - + if(response.document.preview_url){ dropzone.emit('thumbnail', file, response.document.preview_url); } From 15cf3671049c5c42064c656f89a8cfafd1133b87 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 24 May 2016 09:58:27 +0300 Subject: [PATCH 037/111] Updated Postmark driver to fix #891 --- composer.lock | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/composer.lock b/composer.lock index e9649ae9fcb3..894c25f76d27 100644 --- a/composer.lock +++ b/composer.lock @@ -127,7 +127,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/formers/former/zipball/37f6876a5d211427b5c445cd64f0eb637f42f685", + "url": "https://api.github.com/repos/formers/former/zipball/d97f907741323b390f43954a90a227921ecc6b96", "reference": "d97f907741323b390f43954a90a227921ecc6b96", "shasum": "" }, @@ -505,7 +505,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/35ebf3a2ba9443e11fbdb9066cc363ec7b2245e4", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/e97ed532f09e290b91ff7713b785ed7ab11d0812", "reference": "e97ed532f09e290b91ff7713b785ed7ab11d0812", "shasum": "" }, @@ -657,7 +657,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Chumper/Datatable/zipball/546e8768d7987b0d9a8501e67432309349ef5504", + "url": "https://api.github.com/repos/Chumper/Datatable/zipball/04ef2bf", "reference": "04ef2bf", "shasum": "" }, @@ -2429,7 +2429,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Intervention/image/zipball/22088b04728a039bd1fc32f7e79a89a118b78698", + "url": "https://api.github.com/repos/Intervention/image/zipball/e368d262887dbb2fdfaf710880571ede51e9c0e6", "reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6", "shasum": "" }, @@ -2818,7 +2818,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/c8d80c3b48bae2bab071f283f75b1cd8624ed3c7", + "url": "https://api.github.com/repos/labs7in0/omnipay-wechat/zipball/40c9f86df6573ad98ae1dd0d29712ccbc789a74e", "reference": "40c9f86df6573ad98ae1dd0d29712ccbc789a74e", "shasum": "" }, @@ -3539,6 +3539,7 @@ "php", "url" ], + "abandoned": "league/uri", "time": "2015-07-15 08:24:12" }, { @@ -4202,7 +4203,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/b27d2823d052f5c227eeb29324bb564cfdb8f9af", + "url": "https://api.github.com/repos/thephpleague/omnipay-2checkout/zipball/e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "reference": "e9c079c2dde0d7ba461903b3b7bd5caf6dee1248", "shasum": "" }, @@ -4320,7 +4321,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-bitpay/zipball/9cadfb7955bd361d1a00ac8f0570aee4c05c6bb4", + "url": "https://api.github.com/repos/thephpleague/omnipay-bitpay/zipball/cf813f1d5436a1d2f942d3df6666695d1e2b5280", "reference": "cf813f1d5436a1d2f942d3df6666695d1e2b5280", "shasum": "" }, @@ -5011,7 +5012,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-mollie/zipball/efd491fdac7d1243e2dd1da5a964514e3aab2a5a", + "url": "https://api.github.com/repos/thephpleague/omnipay-mollie/zipball/22956c1a62a9662afa5f5d119723b413770ac525", "reference": "22956c1a62a9662afa5f5d119723b413770ac525", "shasum": "" }, @@ -8150,16 +8151,16 @@ }, { "name": "wildbit/swiftmailer-postmark", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/wildbit/swiftmailer-postmark.git", - "reference": "3673ace0473ec72de31bc7e62d78e8b8f95d15f0" + "reference": "4032d3336ff97761edad3d0a88769820fb84e56f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wildbit/swiftmailer-postmark/zipball/3673ace0473ec72de31bc7e62d78e8b8f95d15f0", - "reference": "3673ace0473ec72de31bc7e62d78e8b8f95d15f0", + "url": "https://api.github.com/repos/wildbit/swiftmailer-postmark/zipball/4032d3336ff97761edad3d0a88769820fb84e56f", + "reference": "4032d3336ff97761edad3d0a88769820fb84e56f", "shasum": "" }, "require": { @@ -8189,7 +8190,7 @@ } ], "description": "A Swiftmailer Transport for Postmark.", - "time": "2015-11-30 18:23:03" + "time": "2016-04-18 14:38:52" }, { "name": "zendframework/zend-escaper", From 0b643b4741d9f5a6c3740b9958524934c203c442 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 24 May 2016 10:17:03 +0300 Subject: [PATCH 038/111] Fixes for initial setup --- ...11_05_180133_confide_setup_users_table.php | 148 +++++++++--------- ...2016_01_04_175228_create_vendors_table.php | 4 +- ...2016_01_24_112646_add_bank_subaccounts.php | 8 +- ..._01_27_173015_add_header_footer_option.php | 20 ++- ...135956_add_source_currency_to_expenses.php | 8 +- ...214710_add_support_three_decimal_taxes.php | 4 +- ...3_23_215049_support_multiple_tax_rates.php | 34 ++-- 7 files changed, 121 insertions(+), 105 deletions(-) diff --git a/database/migrations/2013_11_05_180133_confide_setup_users_table.php b/database/migrations/2013_11_05_180133_confide_setup_users_table.php index 6bd69f503980..e9c10da1ad1e 100644 --- a/database/migrations/2013_11_05_180133_confide_setup_users_table.php +++ b/database/migrations/2013_11_05_180133_confide_setup_users_table.php @@ -9,37 +9,37 @@ class ConfideSetupUsersTable extends Migration { * @return void */ public function up() - { - Schema::dropIfExists('payment_terms'); - Schema::dropIfExists('themes'); - Schema::dropIfExists('credits'); + { + Schema::dropIfExists('payment_terms'); + Schema::dropIfExists('themes'); + Schema::dropIfExists('credits'); Schema::dropIfExists('activities'); Schema::dropIfExists('invitations'); Schema::dropIfExists('payments'); Schema::dropIfExists('account_gateways'); Schema::dropIfExists('invoice_items'); Schema::dropIfExists('products'); - Schema::dropIfExists('tax_rates'); + Schema::dropIfExists('tax_rates'); Schema::dropIfExists('contacts'); Schema::dropIfExists('invoices'); Schema::dropIfExists('password_reminders'); Schema::dropIfExists('clients'); Schema::dropIfExists('users'); Schema::dropIfExists('accounts'); - Schema::dropIfExists('currencies'); + Schema::dropIfExists('currencies'); Schema::dropIfExists('invoice_statuses'); Schema::dropIfExists('countries'); - Schema::dropIfExists('timezones'); - Schema::dropIfExists('frequencies'); - Schema::dropIfExists('date_formats'); - Schema::dropIfExists('datetime_formats'); + Schema::dropIfExists('timezones'); + Schema::dropIfExists('frequencies'); + Schema::dropIfExists('date_formats'); + Schema::dropIfExists('datetime_formats'); Schema::dropIfExists('sizes'); Schema::dropIfExists('industries'); Schema::dropIfExists('gateways'); Schema::dropIfExists('payment_types'); Schema::create('countries', function($table) - { + { $table->increments('id'); $table->string('capital', 255)->nullable(); $table->string('citizenship', 255)->nullable(); @@ -53,7 +53,7 @@ class ConfideSetupUsersTable extends Migration { $table->string('name', 255)->default(''); $table->string('region_code', 3)->default(''); $table->string('sub_region_code', 3)->default(''); - $table->boolean('eea')->default(0); + $table->boolean('eea')->default(0); }); Schema::create('themes', function($t) @@ -85,21 +85,21 @@ class ConfideSetupUsersTable extends Migration { Schema::create('date_formats', function($t) { $t->increments('id'); - $t->string('format'); - $t->string('picker_format'); - $t->string('label'); + $t->string('format'); + $t->string('picker_format'); + $t->string('label'); }); Schema::create('datetime_formats', function($t) { $t->increments('id'); - $t->string('format'); - $t->string('label'); + $t->string('format'); + $t->string('label'); }); Schema::create('currencies', function($t) { - $t->increments('id'); + $t->increments('id'); $t->string('name'); $t->string('symbol'); @@ -107,20 +107,20 @@ class ConfideSetupUsersTable extends Migration { $t->string('thousand_separator'); $t->string('decimal_separator'); $t->string('code'); - }); + }); Schema::create('sizes', function($t) { $t->increments('id'); $t->string('name'); - }); + }); Schema::create('industries', function($t) { $t->increments('id'); $t->string('name'); - }); - + }); + Schema::create('accounts', function($t) { $t->increments('id'); @@ -136,13 +136,13 @@ class ConfideSetupUsersTable extends Migration { $t->string('ip'); $t->string('account_key')->unique(); $t->timestamp('last_login')->nullable(); - + $t->string('address1')->nullable(); $t->string('address2')->nullable(); $t->string('city')->nullable(); $t->string('state')->nullable(); $t->string('postal_code')->nullable(); - $t->unsignedInteger('country_id')->nullable(); + $t->unsignedInteger('country_id')->nullable(); $t->text('invoice_terms')->nullable(); $t->text('email_footer')->nullable(); $t->unsignedInteger('industry_id')->nullable(); @@ -158,17 +158,17 @@ class ConfideSetupUsersTable extends Migration { $t->foreign('currency_id')->references('id')->on('currencies'); $t->foreign('industry_id')->references('id')->on('industries'); $t->foreign('size_id')->references('id')->on('sizes'); - }); - + }); + Schema::create('gateways', function($t) { $t->increments('id'); - $t->timestamps(); + $t->timestamps(); $t->string('name'); $t->string('provider'); $t->boolean('visible')->default(true); - }); + }); Schema::create('users', function($t) { @@ -206,31 +206,31 @@ class ConfideSetupUsersTable extends Migration { $t->unsignedInteger('gateway_id'); $t->timestamps(); $t->softDeletes(); - + $t->text('config'); $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('gateway_id')->references('id')->on('gateways'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - + $t->unsignedInteger('public_id')->index(); $t->unique( array('account_id','public_id') ); - }); + }); Schema::create('password_reminders', function($t) { $t->string('email'); $t->timestamps(); - + $t->string('token'); - }); + }); Schema::create('clients', function($t) { $t->increments('id'); $t->unsignedInteger('user_id'); - $t->unsignedInteger('account_id')->index(); + $t->unsignedInteger('account_id')->index(); $t->unsignedInteger('currency_id')->nullable(); $t->timestamps(); $t->softDeletes(); @@ -255,14 +255,14 @@ class ConfideSetupUsersTable extends Migration { $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - $t->foreign('country_id')->references('id')->on('countries'); - $t->foreign('industry_id')->references('id')->on('industries'); - $t->foreign('size_id')->references('id')->on('sizes'); + $t->foreign('country_id')->references('id')->on('countries'); + $t->foreign('industry_id')->references('id')->on('industries'); + $t->foreign('size_id')->references('id')->on('sizes'); $t->foreign('currency_id')->references('id')->on('currencies'); - + $t->unsignedInteger('public_id')->index(); $t->unique( array('account_id','public_id') ); - }); + }); Schema::create('contacts', function($t) { @@ -279,14 +279,14 @@ class ConfideSetupUsersTable extends Migration { $t->string('last_name')->nullable(); $t->string('email')->nullable(); $t->string('phone')->nullable(); - $t->timestamp('last_login')->nullable(); + $t->timestamp('last_login')->nullable(); - $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); + $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; $t->unsignedInteger('public_id')->nullable(); $t->unique( array('account_id','public_id') ); - }); + }); Schema::create('invoice_statuses', function($t) { @@ -325,15 +325,15 @@ class ConfideSetupUsersTable extends Migration { $t->timestamp('last_sent_date')->nullable(); $t->unsignedInteger('recurring_invoice_id')->index()->nullable(); - $t->string('tax_name'); - $t->decimal('tax_rate', 13, 2); + $t->string('tax_name1'); + $t->decimal('tax_rate1', 13, 3); $t->decimal('amount', 13, 2); $t->decimal('balance', 13, 2); - + $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); - $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); - $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $t->foreign('invoice_status_id')->references('id')->on('invoice_statuses'); $t->foreign('recurring_invoice_id')->references('id')->on('invoices')->onDelete('cascade'); @@ -375,11 +375,11 @@ class ConfideSetupUsersTable extends Migration { $t->softDeletes(); $t->string('name'); - $t->decimal('rate', 13, 2); - - $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $t->decimal('rate', 13, 3); + + $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; - + $t->unsignedInteger('public_id'); $t->unique( array('account_id','public_id') ); }); @@ -396,10 +396,10 @@ class ConfideSetupUsersTable extends Migration { $t->text('notes'); $t->decimal('cost', 13, 2); $t->decimal('qty', 13, 2)->nullable(); - - $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + + $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; - + $t->unsignedInteger('public_id'); $t->unique( array('account_id','public_id') ); }); @@ -420,8 +420,8 @@ class ConfideSetupUsersTable extends Migration { $t->decimal('cost', 13, 2); $t->decimal('qty', 13, 2)->nullable(); - $t->string('tax_name')->nullable(); - $t->decimal('tax_rate', 13, 2)->nullable(); + $t->string('tax_name1')->nullable(); + $t->decimal('tax_rate1', 13, 3)->nullable(); $t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); $t->foreign('product_id')->references('id')->on('products')->onDelete('cascade'); @@ -458,10 +458,10 @@ class ConfideSetupUsersTable extends Migration { $t->foreign('account_gateway_id')->references('id')->on('account_gateways')->onDelete('cascade'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; $t->foreign('payment_type_id')->references('id')->on('payment_types'); - + $t->unsignedInteger('public_id')->index(); $t->unique( array('account_id','public_id') ); - }); + }); Schema::create('credits', function($t) { @@ -471,21 +471,21 @@ class ConfideSetupUsersTable extends Migration { $t->unsignedInteger('user_id'); $t->timestamps(); $t->softDeletes(); - + $t->boolean('is_deleted')->default(false); $t->decimal('amount', 13, 2); $t->decimal('balance', 13, 2); $t->date('credit_date')->nullable(); $t->string('credit_number')->nullable(); $t->text('private_notes'); - + $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; - + $t->unsignedInteger('public_id')->index(); $t->unique( array('account_id','public_id') ); - }); + }); Schema::create('activities', function($t) { @@ -500,13 +500,13 @@ class ConfideSetupUsersTable extends Migration { $t->unsignedInteger('invoice_id')->nullable(); $t->unsignedInteger('credit_id')->nullable(); $t->unsignedInteger('invitation_id')->nullable(); - + $t->text('message')->nullable(); $t->text('json_backup')->nullable(); - $t->integer('activity_type_id'); + $t->integer('activity_type_id'); $t->decimal('adjustment', 13, 2)->nullable(); $t->decimal('balance', 13, 2)->nullable(); - + $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); }); @@ -519,9 +519,9 @@ class ConfideSetupUsersTable extends Migration { */ public function down() { - Schema::dropIfExists('payment_terms'); - Schema::dropIfExists('themes'); - Schema::dropIfExists('credits'); + Schema::dropIfExists('payment_terms'); + Schema::dropIfExists('themes'); + Schema::dropIfExists('credits'); Schema::dropIfExists('activities'); Schema::dropIfExists('invitations'); Schema::dropIfExists('payments'); @@ -535,16 +535,16 @@ class ConfideSetupUsersTable extends Migration { Schema::dropIfExists('clients'); Schema::dropIfExists('users'); Schema::dropIfExists('accounts'); - Schema::dropIfExists('currencies'); + Schema::dropIfExists('currencies'); Schema::dropIfExists('invoice_statuses'); Schema::dropIfExists('countries'); - Schema::dropIfExists('timezones'); - Schema::dropIfExists('frequencies'); - Schema::dropIfExists('date_formats'); - Schema::dropIfExists('datetime_formats'); + Schema::dropIfExists('timezones'); + Schema::dropIfExists('frequencies'); + Schema::dropIfExists('date_formats'); + Schema::dropIfExists('datetime_formats'); Schema::dropIfExists('sizes'); Schema::dropIfExists('industries'); - Schema::dropIfExists('gateways'); + Schema::dropIfExists('gateways'); Schema::dropIfExists('payment_types'); } } diff --git a/database/migrations/2016_01_04_175228_create_vendors_table.php b/database/migrations/2016_01_04_175228_create_vendors_table.php index 1295748b0801..5e8551004c20 100644 --- a/database/migrations/2016_01_04_175228_create_vendors_table.php +++ b/database/migrations/2016_01_04_175228_create_vendors_table.php @@ -57,7 +57,7 @@ class CreateVendorsTable extends Migration $table->foreign('vendor_id')->references('id')->on('vendors')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); - + $table->unsignedInteger('public_id')->nullable(); $table->unique(array('account_id', 'public_id')); }); @@ -79,7 +79,7 @@ class CreateVendorsTable extends Migration $table->date('expense_date')->nullable(); $table->text('private_notes'); $table->text('public_notes'); - $table->unsignedInteger('currency_id')->nullable(); + $table->unsignedInteger('invoice_currency_id')->nullable(false); $table->boolean('should_be_invoiced')->default(true); // Relations diff --git a/database/migrations/2016_01_24_112646_add_bank_subaccounts.php b/database/migrations/2016_01_24_112646_add_bank_subaccounts.php index 92b283a52662..5c01fdd5de8b 100644 --- a/database/migrations/2016_01_24_112646_add_bank_subaccounts.php +++ b/database/migrations/2016_01_24_112646_add_bank_subaccounts.php @@ -35,13 +35,13 @@ class AddBankSubaccounts extends Migration { Schema::table('expenses', function($table) { - $table->string('transaction_id'); - $table->unsignedInteger('bank_id'); + $table->string('transaction_id')->nullable(); + $table->unsignedInteger('bank_id')->nullable(); }); Schema::table('vendors', function($table) { - $table->string('transaction_name'); + $table->string('transaction_name')->nullable(); }); } @@ -53,7 +53,7 @@ class AddBankSubaccounts extends Migration { public function down() { Schema::drop('bank_subaccounts'); - + Schema::table('expenses', function($table) { $table->dropColumn('transaction_id'); diff --git a/database/migrations/2016_01_27_173015_add_header_footer_option.php b/database/migrations/2016_01_27_173015_add_header_footer_option.php index 0cb690a17383..d840b31e8f13 100644 --- a/database/migrations/2016_01_27_173015_add_header_footer_option.php +++ b/database/migrations/2016_01_27_173015_add_header_footer_option.php @@ -25,20 +25,24 @@ class AddHeaderFooterOption extends Migration { $table->boolean('is_offsite'); $table->boolean('is_secure'); }); - + Schema::table('expenses', function($table) { - $table->string('transaction_id')->nullable()->change(); - $table->unsignedInteger('bank_id')->nullable()->change(); - }); + if (Schema::hasColumn('expenses', 'transaction_id')) { + $table->string('transaction_id')->nullable()->change(); + $table->unsignedInteger('bank_id')->nullable()->change(); + } + }); Schema::table('vendors', function($table) { - $table->string('transaction_name')->nullable()->change(); - }); - + if (Schema::hasColumn('vendors', 'transaction_name')) { + $table->string('transaction_name')->nullable()->change(); + } + }); + } - + /** * Reverse the migrations. * diff --git a/database/migrations/2016_02_01_135956_add_source_currency_to_expenses.php b/database/migrations/2016_02_01_135956_add_source_currency_to_expenses.php index 4fcfa367d6be..a4b1fa62436b 100644 --- a/database/migrations/2016_02_01_135956_add_source_currency_to_expenses.php +++ b/database/migrations/2016_02_01_135956_add_source_currency_to_expenses.php @@ -16,10 +16,12 @@ class AddSourceCurrencyToExpenses extends Migration $table->dropColumn('foreign_amount'); - $table->unsignedInteger('currency_id')->nullable(false)->change(); - $table->renameColumn('currency_id', 'invoice_currency_id'); - $table->unsignedInteger('expense_currency_id'); + if (Schema::hasColumn('expenses', 'currency_id')) { + $table->unsignedInteger('currency_id')->nullable(false)->change(); + $table->renameColumn('currency_id', 'invoice_currency_id'); + } + $table->unsignedInteger('expense_currency_id'); }); Schema::table('expenses', function (Blueprint $table) { diff --git a/database/migrations/2016_03_14_214710_add_support_three_decimal_taxes.php b/database/migrations/2016_03_14_214710_add_support_three_decimal_taxes.php index af35fc927564..339a29b521b7 100644 --- a/database/migrations/2016_03_14_214710_add_support_three_decimal_taxes.php +++ b/database/migrations/2016_03_14_214710_add_support_three_decimal_taxes.php @@ -11,7 +11,9 @@ class AddSupportThreeDecimalTaxes extends Migration { public function up() { Schema::table('tax_rates', function($table) { - $table->decimal('rate', 13, 3)->change(); + if (Schema::hasColumn('tax_rates', 'rate')) { + $table->decimal('rate', 13, 3)->change(); + } }); } /** diff --git a/database/migrations/2016_03_23_215049_support_multiple_tax_rates.php b/database/migrations/2016_03_23_215049_support_multiple_tax_rates.php index 4c2d8a29b931..cda2aef7b428 100644 --- a/database/migrations/2016_03_23_215049_support_multiple_tax_rates.php +++ b/database/migrations/2016_03_23_215049_support_multiple_tax_rates.php @@ -13,30 +13,38 @@ class SupportMultipleTaxRates extends Migration public function up() { Schema::table('invoices', function($table) { - $table->decimal('tax_rate', 13, 3)->change(); - }); + if (Schema::hasColumn('invoices', 'tax_rate')) { + $table->decimal('tax_rate', 13, 3)->change(); + } + }); Schema::table('invoice_items', function($table) { - $table->decimal('tax_rate', 13, 3)->change(); - }); - + if (Schema::hasColumn('invoice_items', 'tax_rate')) { + $table->decimal('tax_rate', 13, 3)->change(); + } + }); + Schema::table('invoices', function($table) { + if (Schema::hasColumn('invoices', 'tax_rate')) { $table->renameColumn('tax_rate', 'tax_rate1'); $table->renameColumn('tax_name', 'tax_name1'); - $table->string('tax_name2')->nullable(); - $table->decimal('tax_rate2', 13, 3); + } + $table->string('tax_name2')->nullable(); + $table->decimal('tax_rate2', 13, 3); }); Schema::table('invoice_items', function($table) { + if (Schema::hasColumn('invoice_items', 'tax_rate')) { $table->renameColumn('tax_rate', 'tax_rate1'); $table->renameColumn('tax_name', 'tax_name1'); - $table->string('tax_name2')->nullable(); - $table->decimal('tax_rate2', 13, 3); + } + $table->string('tax_name2')->nullable(); + $table->decimal('tax_rate2', 13, 3); }); - Schema::table('accounts', function($table) { - $table->boolean('enable_client_portal_dashboard')->default(true); - }); + Schema::table('accounts', function($table) { + $table->boolean('enable_client_portal_dashboard')->default(true); + }); } /** * Reverse the migrations. @@ -65,4 +73,4 @@ class SupportMultipleTaxRates extends Migration $table->dropColumn('enable_client_portal_dashboard'); }); } -} \ No newline at end of file +} From e201651aa69dbbf21b6af3ca2613696fc064b625 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 24 May 2016 10:17:53 +0300 Subject: [PATCH 039/111] Bumped version --- 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 29ad336a00d9..81a44b5ee930 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -553,7 +553,7 @@ if (!defined('CONTACT_EMAIL')) { define('NINJA_WEB_URL', env('NINJA_WEB_URL', 'https://www.invoiceninja.com')); define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com')); define('NINJA_DATE', '2000-01-01'); - define('NINJA_VERSION', '2.5.2.1' . env('NINJA_VERSION_SUFFIX')); + define('NINJA_VERSION', '2.5.2.2' . env('NINJA_VERSION_SUFFIX')); define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja')); define('SOCIAL_LINK_TWITTER', env('SOCIAL_LINK_TWITTER', 'https://twitter.com/invoiceninja')); From f502e5952e5db816afe3b1aa5d14af466ec80978 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 12:00:37 +0300 Subject: [PATCH 040/111] Lazy load invoice documents --- resources/views/invoices/edit.blade.php | 92 ++++++++++++++----------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 4f1d96f20ad6..723d2e5c3a6b 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -974,47 +974,59 @@ }) // Initialize document upload - dropzone = new Dropzone('#document-upload .dropzone', { - url:{!! json_encode(url('document')) !!}, - params:{ - _token:"{{ Session::getToken() }}" - }, - acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!}, - addRemoveLinks:true, - @foreach(trans('texts.dropzone') as $key=>$text) - "dict{{strval($key)}}":"{{strval($text)}}", - @endforeach - maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, - }); - if(dropzone instanceof Dropzone){ - dropzone.on("addedfile",handleDocumentAdded); - dropzone.on("removedfile",handleDocumentRemoved); - dropzone.on("success",handleDocumentUploaded); - for (var i=0; i Date: Wed, 25 May 2016 12:11:18 +0300 Subject: [PATCH 041/111] Prevent setting pdfstring when saving an invoice --- resources/views/invoices/edit.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 723d2e5c3a6b..f3fcba28e598 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -1186,7 +1186,7 @@ submitAction(''); } } else { - preparePdfData(''); + submitAction(''); } } From 62af653121a1aae9ee67c8a5dccad3541279449b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 14:43:34 +0300 Subject: [PATCH 042/111] Prevent clicking save while document is still uploading --- resources/views/invoices/edit.blade.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index f3fcba28e598..1fe288fa2304 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -1222,6 +1222,10 @@ } function onFormSubmit(event) { + if (window.countUploadingDocuments > 0) { + return false; + } + if (!isSaveValid()) { model.showClientForm(); return false; @@ -1392,6 +1396,7 @@ model.invoice().invoice_number(number); } + window.countUploadingDocuments = 0; @if ($account->hasFeature(FEATURE_DOCUMENTS)) function handleDocumentAdded(file){ // open document when clicked @@ -1403,6 +1408,7 @@ if(file.mock)return; file.index = model.invoice().documents().length; model.invoice().addDocument({name:file.name, size:file.size, type:file.type}); + window.countUploadingDocuments++; } function handleDocumentRemoved(file){ @@ -1413,6 +1419,7 @@ function handleDocumentUploaded(file, response){ file.public_id = response.document.public_id model.invoice().documents()[file.index].update(response.document); + window.countUploadingDocuments--; refreshPDF(true); if(response.document.preview_url){ From 2fa471ab81fc23fcf545ee41cd7a753ec4cc24fe Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 15:19:15 +0300 Subject: [PATCH 043/111] Change document to click to full size --- resources/views/expenses/edit.blade.php | 2 +- resources/views/invoices/edit.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 920162e67dd2..a4e2700b8c63 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -249,7 +249,7 @@ public_id:document.public_id(), status:Dropzone.SUCCESS, accepted:true, - url:document.preview_url()||document.url(), + url:document.url(), mock:true, index:i }; diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 1fe288fa2304..7c66d3eeecf1 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -1010,7 +1010,7 @@ public_id:document.public_id(), status:Dropzone.SUCCESS, accepted:true, - url:document.preview_url()||document.url(), + url:document.url(), mock:true, index:i }; From c8794a401e839eeff2fc15de320c69da1e76f8b7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 16:16:56 +0300 Subject: [PATCH 044/111] Fix for cancelled accounts --- .../2016_03_22_168362_add_documents.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/database/migrations/2016_03_22_168362_add_documents.php b/database/migrations/2016_03_22_168362_add_documents.php index 233f420c198b..b5e9da2deb1a 100644 --- a/database/migrations/2016_03_22_168362_add_documents.php +++ b/database/migrations/2016_03_22_168362_add_documents.php @@ -18,7 +18,7 @@ class AddDocuments extends Migration { $table->boolean('invoice_embed_documents')->default(1); $table->boolean('document_email_attachment')->default(1); }); - + \DB::table('accounts')->update(array('logo' => '')); Schema::dropIfExists('documents'); Schema::create('documents', function($t) @@ -41,14 +41,14 @@ class AddDocuments extends Migration { $t->timestamps(); - $t->foreign('account_id')->references('id')->on('accounts'); - $t->foreign('user_id')->references('id')->on('users'); - $t->foreign('invoice_id')->references('id')->on('invoices'); - $t->foreign('expense_id')->references('id')->on('expenses'); - + $t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); + $t->foreign('expense_id')->references('id')->on('expenses')->onDelete('cascade'); + $t->unique( array('account_id','public_id') ); - }); + }); } /** * Reverse the migrations. @@ -65,7 +65,7 @@ class AddDocuments extends Migration { $table->dropColumn('invoice_embed_documents'); $table->dropColumn('document_email_attachment'); }); - + Schema::dropIfExists('documents'); } } From fc981c0604a63f0a413af494f741ab650caa7bcf Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 16:28:24 +0300 Subject: [PATCH 045/111] Fix for lazy load change --- resources/views/expenses/edit.blade.php | 2 +- resources/views/invoices/edit.blade.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index a4e2700b8c63..fdeac3cf2293 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -232,7 +232,7 @@ acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!}, addRemoveLinks:true, @foreach(trans('texts.dropzone') as $key=>$text) - "dict{{strval($key)}}":"{{strval($text)}}", + "dict{{strval($key)}}":"{{strval($text)}}", @endforeach maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, }); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 7c66d3eeecf1..300b1dcb148c 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -992,8 +992,8 @@ }, acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!}, addRemoveLinks:true, - @foreach(['default_message', 'fallback_message', 'fallback_text', 'file_too_big', 'invalid_file_type', 'response_error', 'cancel_upload', 'cancel_upload_confirmation', 'remove_file'] as $key) - "dict{{Utils::toClassCase($key)}}":"{{trans('texts.dropzone_'.$key)}}", + @foreach(trans('texts.dropzone') as $key=>$text) + "dict{{strval($key)}}":"{{strval($text)}}", @endforeach maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, }); From afd7b7ed38ca15543d63b1d5f9326a6e189daeda Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 21:44:06 +0300 Subject: [PATCH 046/111] check document completed upload before user submitted form --- app/Ninja/Repositories/ExpenseRepository.php | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index 442af74bd32b..8836cd65dac9 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -12,7 +12,7 @@ use Session; class ExpenseRepository extends BaseRepository { protected $documentRepo; - + // Expenses public function getClassName() { @@ -23,7 +23,7 @@ class ExpenseRepository extends BaseRepository { $this->documentRepo = $documentRepo; } - + public function all() { return Expense::scope() @@ -156,20 +156,23 @@ class ExpenseRepository extends BaseRepository $rate = isset($input['exchange_rate']) ? Utils::parseFloat($input['exchange_rate']) : 1; $expense->exchange_rate = round($rate, 4); $expense->amount = round(Utils::parseFloat($input['amount']), 2); - + $expense->save(); // Documents $document_ids = !empty($input['document_ids'])?array_map('intval', $input['document_ids']):array();; foreach ($document_ids as $document_id){ - $document = Document::scope($document_id)->first(); - if($document && Auth::user()->can('edit', $document)){ - $document->invoice_id = null; - $document->expense_id = $expense->id; - $document->save(); + // check document completed upload before user submitted form + if ($document_id) { + $document = Document::scope($document_id)->first(); + if($document && Auth::user()->can('edit', $document)){ + $document->invoice_id = null; + $document->expense_id = $expense->id; + $document->save(); + } } } - + if(!empty($input['documents']) && Auth::user()->can('create', ENTITY_DOCUMENT)){ // Fallback upload $doc_errors = array(); @@ -188,7 +191,7 @@ class ExpenseRepository extends BaseRepository Session::flash('error', implode('
    ',array_map('htmlentities',$doc_errors))); } } - + foreach ($expense->documents as $document){ if(!in_array($document->public_id, $document_ids)){ // Not checking permissions; deleting a document is just editing the invoice From 537034d2150bf2ebf79d3ea8c4df303a052f0ba7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 25 May 2016 22:04:39 +0300 Subject: [PATCH 047/111] Prevent saving expense before documents have uploaded --- resources/views/expenses/edit.blade.php | 24 ++++++++++++++++++++++-- resources/views/invoices/edit.blade.php | 7 ++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index fdeac3cf2293..343e11ed7179 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -15,7 +15,10 @@ @section('content') - {!! Former::open($url)->addClass('warn-on-exit main-form')->method($method) !!} + {!! Former::open($url) + ->addClass('warn-on-exit main-form') + ->onsubmit('return onFormSubmit(event)') + ->method($method) !!}
    {!! Former::text('action') !!}
    @@ -154,6 +157,15 @@ clientMap[client.public_id] = client; } + function onFormSubmit(event) { + if (window.countUploadingDocuments > 0) { + alert("{!! trans('texts.wait_for_upload') !!}"); + return false; + } + + return true; + } + function onClientChange() { var clientId = $('select#client_id').val(); var client = clientMap[clientId]; @@ -240,6 +252,7 @@ dropzone.on("addedfile",handleDocumentAdded); dropzone.on("removedfile",handleDocumentRemoved); dropzone.on("success",handleDocumentUploaded); + dropzone.on("canceled",handleDocumentCanceled); for (var i=0; iaccount->hasFeature(FEATURE_DOCUMENTS)) function handleDocumentAdded(file){ // open document when clicked @@ -373,6 +387,7 @@ if(file.mock)return; file.index = model.documents().length; model.addDocument({name:file.name, size:file.size, type:file.type}); + window.countUploadingDocuments++; } function handleDocumentRemoved(file){ @@ -382,11 +397,16 @@ function handleDocumentUploaded(file, response){ file.public_id = response.document.public_id model.documents()[file.index].update(response.document); - + window.countUploadingDocuments--; if(response.document.preview_url){ dropzone.emit('thumbnail', file, response.document.preview_url); } } + + function handleDocumentCanceled() + { + window.countUploadingDocuments--; + } @endif diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 300b1dcb148c..1cba75934a7b 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -1001,6 +1001,7 @@ dropzone.on("addedfile",handleDocumentAdded); dropzone.on("removedfile",handleDocumentRemoved); dropzone.on("success",handleDocumentUploaded); + dropzone.on("canceled",handleDocumentCanceled); for (var i=0; i From 26b0169ae91dafe831e81d4cd3c9c0331e36c728 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 15:51:13 +0300 Subject: [PATCH 048/111] Fix permission issue with quotes --- app/Http/Controllers/QuoteController.php | 12 ++++++------ app/Models/EntityModel.php | 13 +++++++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index a8ea0beaa476..58807ba4a9eb 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -113,16 +113,16 @@ class QuoteController extends BaseController $rates = TaxRate::scope()->orderBy('name')->get(); $options = []; $defaultTax = false; - + foreach ($rates as $rate) { - $options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%'; - + $options[$rate->rate . ' ' . $rate->name] = $rate->name . ' ' . ($rate->rate+0) . '%'; + // load default invoice tax if ($rate->id == $account->default_tax_rate_id) { $defaultTax = $rate; } - } - + } + return [ 'entityType' => ENTITY_QUOTE, 'account' => Auth::user()->account, @@ -130,7 +130,7 @@ class QuoteController extends BaseController 'taxRateOptions' => $options, 'defaultTax' => $defaultTax, 'countries' => Cache::get('countries'), - 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), + 'clients' => Client::scope()->viewable()->with('contacts', 'country')->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'currencies' => Cache::get('currencies'), 'sizes' => Cache::get('sizes'), diff --git a/app/Models/EntityModel.php b/app/Models/EntityModel.php index 95e85e6acef4..4b724d3953ee 100644 --- a/app/Models/EntityModel.php +++ b/app/Models/EntityModel.php @@ -30,7 +30,7 @@ class EntityModel extends Eloquent } else { $lastEntity = $className::scope(false, $entity->account_id); } - + $lastEntity = $lastEntity->orderBy('public_id', 'DESC') ->first(); @@ -86,6 +86,15 @@ class EntityModel extends Eloquent return $query; } + public function scopeViewable($query) + { + if (Auth::check() && ! Auth::user()->hasPermission('view_all')) { + $query->where($this->getEntityType(). 's.user_id', '=', Auth::user()->id); + } + + return $query; + } + public function scopeWithArchived($query) { return $query->withTrashed()->where('is_deleted', '=', false); @@ -110,7 +119,7 @@ class EntityModel extends Eloquent { return 'App\\Ninja\\Transformers\\' . ucwords(Utils::toCamelCase($entityType)) . 'Transformer'; } - + public function setNullValues() { foreach ($this->fillable as $field) { From 94331c2858fe771629a2cb2ef39369cd763a9b82 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 16:05:38 +0300 Subject: [PATCH 049/111] Show permissions if not enabled --- resources/views/users/edit.blade.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index 94b74664a7e3..6770760fd18e 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -1,6 +1,6 @@ @extends('header') -@section('content') +@section('content') @parent @include('accounts.nav', ['selected' => ACCOUNT_USER_MANAGEMENT]) @@ -31,13 +31,19 @@
    -@if (Utils::hasFeature(FEATURE_USER_PERMISSIONS))

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

    + @if ( ! Utils::hasFeature(FEATURE_USER_PERMISSIONS)) + + @endif {!! Former::checkbox('is_admin') ->label(' ') @@ -61,12 +67,11 @@ ->id('permissions_edit_all') ->text(trans('texts.user_edit_all')) ->help(trans('texts.edit_all_help')) !!} - -
    -
    -@endif - {!! Former::actions( + + + + {!! Former::actions( Button::normal(trans('texts.cancel'))->asLinkTo(URL::to('/settings/user_management'))->appendIcon(Icon::create('remove-circle'))->large(), Button::success(trans($user && $user->confirmed ? 'texts.save' : 'texts.send_invite'))->submit()->large()->appendIcon(Icon::create($user && $user->confirmed ? 'floppy-disk' : 'send')) )!!} @@ -88,4 +93,4 @@ if(!viewChecked)$('#permissions_edit_all').prop('checked',false) } fixCheckboxes(); -@stop \ No newline at end of file +@stop From 7599edbc8cdf35bcaee2285eb2befc43f07dc466 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 18:28:36 +0300 Subject: [PATCH 050/111] Fixed date formatting in document list --- app/Ninja/Repositories/DocumentRepository.php | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/app/Ninja/Repositories/DocumentRepository.php b/app/Ninja/Repositories/DocumentRepository.php index 094b4848ebe0..a2278b1843ae 100644 --- a/app/Ninja/Repositories/DocumentRepository.php +++ b/app/Ninja/Repositories/DocumentRepository.php @@ -61,31 +61,31 @@ class DocumentRepository extends BaseRepository { $extension = strtolower($uploaded->getClientOriginalExtension()); if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){ - $documentType = Document::$extraExtensions[$extension]; + $documentType = Document::$extraExtensions[$extension]; } else{ $documentType = $extension; } - + if(empty(Document::$types[$documentType])){ return 'Unsupported file type'; } - + $documentTypeData = Document::$types[$documentType]; - + $filePath = $uploaded->path(); $name = $uploaded->getClientOriginalName(); $size = filesize($filePath); - + if($size/1000 > MAX_DOCUMENT_SIZE){ return 'File too large'; } - - - + + + $hash = sha1_file($filePath); $filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType; - + $document = Document::createNew(); $disk = $document->getDisk(); if(!$disk->exists($filename)){// Have we already stored the same file @@ -93,7 +93,7 @@ class DocumentRepository extends BaseRepository $disk->getDriver()->putStream($filename, $stream, ['mimetype'=>$documentTypeData['mime']]); fclose($stream); } - + // This is an image; check if we need to create a preview if(in_array($documentType, array('jpeg','png','gif','bmp','tiff','psd'))){ $makePreview = false; @@ -105,32 +105,32 @@ class DocumentRepository extends BaseRepository // Needs to be converted $makePreview = true; } else if($width > DOCUMENT_PREVIEW_SIZE || $height > DOCUMENT_PREVIEW_SIZE){ - $makePreview = true; + $makePreview = true; } - + if(in_array($documentType,array('bmp','tiff','psd'))){ if(!class_exists('Imagick')){ // Cant't read this $makePreview = false; } else { $imgManagerConfig['driver'] = 'imagick'; - } + } } - + if($makePreview){ $previewType = 'jpeg'; if(in_array($documentType, array('png','gif','tiff','psd'))){ // Has transparency $previewType = 'png'; } - + $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); - + $img = $imgManager->make($filePath); - + if($width <= DOCUMENT_PREVIEW_SIZE && $height <= DOCUMENT_PREVIEW_SIZE){ $previewWidth = $width; $previewHeight = $height; @@ -141,9 +141,9 @@ class DocumentRepository extends BaseRepository $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); @@ -153,23 +153,23 @@ class DocumentRepository extends BaseRepository } }else{ $base64 = base64_encode(file_get_contents($filePath)); - } + } } - + $document->path = $filename; $document->type = $documentType; $document->size = $size; $document->hash = $hash; $document->name = substr($name, -255); - + if(!empty($imageSize)){ $document->width = $imageSize[0]; $document->height = $imageSize[1]; } - + $document->save(); $doc_array = $document->toArray(); - + if(!empty($base64)){ $mime = Document::$types[!empty($previewType)?$previewType:$documentType]['mime']; $doc_array['base64'] = 'data:'.$mime.';base64,'.$base64; @@ -177,10 +177,10 @@ class DocumentRepository extends BaseRepository return $document; } - + public function getClientDatatable($contactId, $entityType, $search) { - + $query = DB::table('invitations') ->join('accounts', 'accounts.id', '=', 'invitations.account_id') ->join('invoices', 'invoices.id', '=', 'invitations.invoice_id') @@ -192,7 +192,7 @@ class DocumentRepository extends BaseRepository ->where('clients.deleted_at', '=', null) ->where('invoices.is_recurring', '=', false) // This needs to be a setting to also hide the activity on the dashboard page - //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) + //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) ->select( 'invitations.invitation_key', 'invoices.invoice_number', @@ -205,22 +205,22 @@ class DocumentRepository extends BaseRepository $table = \Datatable::query($query) ->addColumn('invoice_number', function ($model) { return link_to( - '/view/'.$model->invitation_key, + '/view/'.$model->invitation_key, $model->invoice_number - )->toHtml(); + )->toHtml(); }) ->addColumn('name', function ($model) { return link_to( - '/client/documents/'.$model->invitation_key.'/'.$model->public_id.'/'.$model->name, + '/client/documents/'.$model->invitation_key.'/'.$model->public_id.'/'.$model->name, $model->name, ['target'=>'_blank'] - )->toHtml(); + )->toHtml(); }) ->addColumn('document_date', function ($model) { - return Utils::fromSqlDate($model->created_at); + return Utils::dateToString($model->created_at); }) ->addColumn('document_size', function ($model) { - return Form::human_filesize($model->size); + return Form::human_filesize($model->size); }); return $table->make(); From b32a9110c7cc86f43f654725d8095381cf3fc008 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 19:04:43 +0300 Subject: [PATCH 051/111] Fix for deleting documents --- app/Http/Controllers/DocumentController.php | 44 ++++++++++++--------- app/Http/Requests/UpdateDocumentRequest.php | 26 ++++++++++++ app/Http/routes.php | 1 + resources/views/expenses/edit.blade.php | 8 ++++ resources/views/invoices/edit.blade.php | 8 ++++ 5 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 app/Http/Requests/UpdateDocumentRequest.php diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index d597ba004474..e4ea605d57a0 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -14,6 +14,7 @@ use App\Ninja\Repositories\DocumentRepository; use App\Http\Requests\DocumentRequest; use App\Http\Requests\CreateDocumentRequest; +use App\Http\Requests\UpdateDocumentRequest; class DocumentController extends BaseController { @@ -26,20 +27,20 @@ class DocumentController extends BaseController $this->documentRepo = $documentRepo; } - + public function get(DocumentRequest $request) { return static::getDownloadResponse($request->entity()); } - + public static function getDownloadResponse($document){ $direct_url = $document->getDirectUrl(); if($direct_url){ return redirect($direct_url); } - + $stream = $document->getStream(); - + if($stream){ $headers = [ 'Content-Type' => Document::$types[$document->type]['mime'], @@ -54,59 +55,59 @@ class DocumentController extends BaseController $response = Response::make($document->getRaw(), 200); $response->header('content-type', Document::$types[$document->type]['mime']); } - + return $response; } - + public function getPreview(DocumentRequest $request) { $document = $request->entity(); - + 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); } - + $previewType = pathinfo($document->preview, PATHINFO_EXTENSION); $response = Response::make($document->getRawPreview(), 200); $response->header('content-type', Document::$types[$previewType]['mime']); - + return $response; } - + public function getVFSJS(DocumentRequest $request, $publicId, $name) { $document = $request->entity(); - + if(substr($name, -3)=='.js'){ $name = substr($name, 0, -3); } - + if(!$document->isPDFEmbeddable()){ 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(CreateDocumentRequest $request) { if (!Utils::hasFeature(FEATURE_DOCUMENTS)) { return; } - + $result = $this->documentRepo->upload(Input::all()['file'], $doc_array); - + if(is_string($result)){ return Response::json([ 'error' => $result, @@ -120,4 +121,11 @@ class DocumentController extends BaseController ], 200); } } + + public function delete(UpdateDocumentRequest $request) + { + $request->entity()->delete(); + + return RESULT_SUCCESS; + } } diff --git a/app/Http/Requests/UpdateDocumentRequest.php b/app/Http/Requests/UpdateDocumentRequest.php new file mode 100644 index 000000000000..3a950c9f3475 --- /dev/null +++ b/app/Http/Requests/UpdateDocumentRequest.php @@ -0,0 +1,26 @@ +user()->can('edit', $this->entity()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + + ]; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 81a44b5ee930..2c3fba6fec4b 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -145,6 +145,7 @@ Route::group(['middleware' => 'auth:user'], function() { Route::get('documents/js/{documents}/{filename}', 'DocumentController@getVFSJS'); Route::get('documents/preview/{documents}/{filename?}', 'DocumentController@getPreview'); Route::post('document', 'DocumentController@postUpload'); + Route::delete('documents/{documents}', 'DocumentController@delete'); Route::get('quotes/create/{client_id?}', 'QuoteController@create'); Route::get('quotes/{invoices}/clone', 'InvoiceController@cloneInvoice'); diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 343e11ed7179..f0b44e700e7c 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -243,6 +243,7 @@ }, acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!}, addRemoveLinks:true, + dictRemoveFileConfirmation:"{{trans('texts.are_you_sure')}}", @foreach(trans('texts.dropzone') as $key=>$text) "dict{{strval($key)}}":"{{strval($text)}}", @endforeach @@ -392,6 +393,13 @@ function handleDocumentRemoved(file){ model.removeDocument(file.public_id); + $.ajax({ + url: '{{ '/documents/' }}' + file.public_id, + type: 'DELETE', + success: function(result) { + // Do something with the result + } + }); } function handleDocumentUploaded(file, response){ diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 1cba75934a7b..51736e2ec8c9 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -992,6 +992,7 @@ }, acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!}, addRemoveLinks:true, + dictRemoveFileConfirmation:"{{trans('texts.are_you_sure')}}", @foreach(trans('texts.dropzone') as $key=>$text) "dict{{strval($key)}}":"{{strval($text)}}", @endforeach @@ -1415,6 +1416,13 @@ function handleDocumentRemoved(file){ model.invoice().removeDocument(file.public_id); refreshPDF(true); + $.ajax({ + url: '{{ '/documents/' }}' + file.public_id, + type: 'DELETE', + success: function(result) { + // Do something with the result + } + }); } function handleDocumentUploaded(file, response){ From 145c394e6b681b82b0fe10394333800743deef85 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 26 May 2016 20:48:45 +0300 Subject: [PATCH 052/111] Ensure all files are deleted with the account --- app/Http/Controllers/AccountController.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index d7ccfd1dc995..67bb78ca844a 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -1251,6 +1251,10 @@ class AccountController extends BaseController $account = Auth::user()->account; \Log::info("Canceled Account: {$account->name} - {$user->email}"); + Document::scope()->each(function($item, $key) { + $item->delete(); + }); + $this->accountRepo->unlinkAccount($account); if ($account->company->accounts->count() == 1) { $account->company->forceDelete(); From 8885ae293963e3defc73b26f31d4324b1e3e52b4 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 16:46:21 +0300 Subject: [PATCH 053/111] Fix for cloning quote --- resources/views/invoices/edit.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 51736e2ec8c9..7db80f69089c 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -824,7 +824,7 @@ model.invoice().has_tasks(true); @endif - if(model.invoice().expenses() && !model.invoice().public_id()){ + if(model.invoice().expenses().length && !model.invoice().public_id()){ model.expense_currency_id({{ isset($expenseCurrencyId) ? $expenseCurrencyId : 0 }}); // move the blank invoice line item to the end From 7650870902555a33917e6a8550fc78fff5c64e7a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 17:21:07 +0300 Subject: [PATCH 054/111] Improved footer images --- public/images/report_logo1.jpg | Bin 5234 -> 16547 bytes public/images/report_logo2.jpg | Bin 13504 -> 25009 bytes public/images/report_logo3.jpg | Bin 31804 -> 37848 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/report_logo1.jpg b/public/images/report_logo1.jpg index c1bf4aae1108232778be346c561d71a342a60d8a..92a6c3f4d0a51dacfd542151e338cc21687d59dd 100644 GIT binary patch literal 16547 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 diff --git a/public/images/report_logo2.jpg b/public/images/report_logo2.jpg index e4b5ba66ddd7c80b63044bcf7c33d466402b083f..453c3acda16c22da7551eb0e1d4130703fc9bad2 100644 GIT binary patch literal 25009 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 z z=W@Pq-}JK3%BY51v}2kinjf9`+-H__ti_kzVFJ<6d2&#Gw4YKWb3@z7L|8;PP4vsw z@(X8$T*@44{qR0wqI_|Uj>?9&<{>*SyrHTD2HmpF^kj?(?O5e@n)@y{B1RTcu-TTG z<^u%fAD^a3$BeG%eWboCUH>M!29JbRb|LjHO%m;+qJvBt9VWW_&-|V*yNQ2FGe8Qb znZA4o@lH!mpe;4cO_D1My*I9LEBk&-qax$$w##6pWs!jI#GTxCjsuL--l{({`FN5} zQqPpaTNR>}nWtN58%r^WoBu9pm#xx~nBLMb(iBJNlc{L_XVmca&GHIN-E3w7dVE`w z#;5YlpQCS&uNk#z=M|WI<2L;UX{A#y<|{kuLg(4i)gn0VB;U^uxwtfV zvfKdv5-EDsgM%Wg^AB!1p)_V*YZ>a19Z`u#b;Ee39|N`8#GKG0i5ZSwUwZGJ96fL_ zI1Q}30_1EKgy+H|`z66&L6uchTc=7qc`6^0bp_01$luO974xMhe9=mWvtSD#8gEh} zuNqMYsUqBchoR#4)bGT7UbHNG_IUo8I~GQKq4`A0n`mf-u1u-lmxxdw$B{YlD19ls z3uvDS#QKnZ^#ENe*Affu4j$ z4UMl2?>e8nOMDfeEIncRu=UfSvrc>)#s`zUo~50oM*u>LKm8^JRYA<4xx?d3?Puo6 z-sIM0-JvKbh$Q~PUMwc*86`KVGZV7Myz{9N(2V*iL>HR^Qx8Ogz+XlDNB-`bK?H;F zhpI%LHyVP4i{IZ0PcMl-ogP&>c(`A^CdwXTi;62g>nPWYx^6bc=y!t_wrE-EB_jH5 zrgLv8YUmy2E~}HF(W{BICV*NF4U>GLgBb+_M=Hnrs5qq+JtDU3&d~wVV}gB?$S%e( z?OU4J8<9e5xZh7H+QJ`yVj9*yA?#?ssomr3tMumegFWJVj|s?=KV7`7DB@B0rKZQ#yR5jUAtdK< z!ErEAS}9oJNQ7k0n8>W>3x)5T!K=ovW??Sn%lOv&_sm!>xM!DRZ7d z=?V3qQ|Glkm9JlDt=qV5pRXq$VyvMu3ke0_*k^KImh9E5e>yqn*q>g~QSM1sTu>~^ zJP~(DPOR!JtPgwZsfV^fTpTH2{6t=A;k`6sY5D~if0HgK;ht_2}>jXp%*0_l1 zYSFjuJp+@9QNcz(qy)A@+BxleG8%~=atYK?ruS8k=* zE%1Jocn0>y68BIuc~oZryFv%OKunD*I9fpQE&>AC6%ak`kyKl`s()u=h5AuRhFV+@ zYFFgTD$SK-ex!nAtq=Kh`vNW+2x>IRA;A~>F%lvvHXgC-U+EO?a9b>YsAVKUD6)v_ zEFM!FB$YlNo{KL+Ex@Spz)DJiKS;gYsBw$})w+--;KdjVylfENh z)(~yp9v|-1iP2>+x}i+WtvT#!t}MZ&G4gt&Xq#X}qhl0*l+wx8ujlkk7&%T-By2M7 zOB2z90h7gXBNb(R9%e-5+xgXcLiiQh7zurwgMJ7^N~-n{gL|8y3^(tVOnI{o;X5r<&{h5fQ;oy+uan{C3gGt#j|TPe*dWVV@#- z`t2_3sWbw)BL8<0skqSrXp* zD(juML|vwM z{5SPRCnHk=tjlw#WCh~_p0wYhLQMv4vv6u zaiXp=k}i}$$Mq$D3W~=HlhYd;E+q|Pik>A!@{FYCNvpX?xJ#l27jj64Mi*w61*c7G zxZo}C3-+^Z@5N1()YFJq@0Y7ay^R)bqo=+T9_OiE=Y46;i&1lFos}uGuNyr5&k>u; z74V{?qha-)|0_cPy#FxTGy_5)^|W=F^P7~jI6}XWG-F6;1RESq?a7uel3spQZ&4{Z gCjMec$4;I1@@|!S@ot?*r**>SH&6Wk0L{Pu*#V5Xr~m)} literal 13504 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 VN|Iy7?FR%=cD6v#2UmCLzW{#JF#rGn diff --git a/public/images/report_logo3.jpg b/public/images/report_logo3.jpg index 8696d0535fa2c89da2c8877239466683a47f9edc..4e0c2ebec1b2f8479a186d20ccb50894a746939f 100644 GIT binary patch literal 37848 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 Date: Sun, 29 May 2016 17:32:08 +0300 Subject: [PATCH 055/111] Enable mobile app for non-pro users --- app/Http/Middleware/ApiCheck.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Http/Middleware/ApiCheck.php b/app/Http/Middleware/ApiCheck.php index b20b19841fcb..5200c3264a96 100644 --- a/app/Http/Middleware/ApiCheck.php +++ b/app/Http/Middleware/ApiCheck.php @@ -23,10 +23,11 @@ class ApiCheck { { $loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register'); $headers = Utils::getApiHeaders(); + $hasApiSecret = hash_equals($request->api_secret ?: '', env(API_SECRET)); if ($loggingIn) { // check API secret - if ( ! $request->api_secret || ! env(API_SECRET) || ! hash_equals($request->api_secret, env(API_SECRET))) { + if ( ! $hasApiSecret) { sleep(ERROR_DELAY); return Response::json('Invalid secret', 403, $headers); } @@ -48,7 +49,7 @@ class ApiCheck { return $next($request); } - if (!Utils::hasFeature(FEATURE_API) && !$loggingIn) { + if (!Utils::hasFeature(FEATURE_API) && !$hasApiSecret) { return Response::json('API requires pro plan', 403, $headers); } else { $key = Auth::check() ? Auth::user()->account->id : $request->getClientIp(); @@ -59,7 +60,7 @@ class ApiCheck { $hour_throttle = Cache::get("hour_throttle:{$key}", null); $last_api_request = Cache::get("last_api_request:{$key}", 0); $last_api_diff = time() - $last_api_request; - + if (is_null($hour_throttle)) { $new_hour_throttle = 0; } else { @@ -83,4 +84,4 @@ class ApiCheck { return $next($request); } -} \ No newline at end of file +} From c1029dfb69f5b58ee774803208c5b16221bb54d7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 17:37:32 +0300 Subject: [PATCH 056/111] Fix for rollback --- database/migrations/2014_05_17_175626_add_quotes.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/migrations/2014_05_17_175626_add_quotes.php b/database/migrations/2014_05_17_175626_add_quotes.php index 96ae916cafae..332d6295612d 100644 --- a/database/migrations/2014_05_17_175626_add_quotes.php +++ b/database/migrations/2014_05_17_175626_add_quotes.php @@ -14,9 +14,9 @@ class AddQuotes extends Migration { { Schema::table('invoices', function($table) { - $table->boolean('is_quote')->default(0); + $table->boolean('is_quote')->default(0); $table->unsignedInteger('quote_id')->nullable(); - $table->unsignedInteger('quote_invoice_id')->nullable(); + $table->unsignedInteger('quote_invoice_id')->nullable(); }); } @@ -29,7 +29,7 @@ class AddQuotes extends Migration { { Schema::table('invoices', function($table) { - $table->dropColumn('is_quote'); + $table->dropColumn('invoice_type_id'); $table->dropColumn('quote_id'); $table->dropColumn('quote_invoice_id'); }); From 7c13b5c9bf2d79149069a9c6aef66a53638b2c21 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 17:38:45 +0300 Subject: [PATCH 057/111] Fix for rollback --- database/migrations/2014_05_17_175626_add_quotes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2014_05_17_175626_add_quotes.php b/database/migrations/2014_05_17_175626_add_quotes.php index 332d6295612d..ae26c640fc81 100644 --- a/database/migrations/2014_05_17_175626_add_quotes.php +++ b/database/migrations/2014_05_17_175626_add_quotes.php @@ -29,7 +29,7 @@ class AddQuotes extends Migration { { Schema::table('invoices', function($table) { - $table->dropColumn('invoice_type_id'); + $table->dropColumn('is_quote'); $table->dropColumn('quote_id'); $table->dropColumn('quote_invoice_id'); }); From ced3daf9c6fd0b6e15e8acf9af0301e4652a9807 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 17:49:09 +0300 Subject: [PATCH 058/111] Added clear commands when updating --- app/Http/Controllers/AppController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index a602ae7ecb85..d776f7224692 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -262,6 +262,12 @@ class AppController extends BaseController if (!Utils::isNinjaProd()) { try { set_time_limit(60 * 5); + Artisan::call('clear-compiled'); + Artisan::call('cache:clear'); + Artisan::call('debugbar:clear'); + Artisan::call('route:clear'); + Artisan::call('view:clear'); + Artisan::call('config:clear'); Artisan::call('optimize', array('--force' => true)); Cache::flush(); Session::flush(); From 2cd1517dd63c0c2e15c90a7be8d2aaac2d7a99ea Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 18:40:01 +0300 Subject: [PATCH 059/111] Fix for max file size --- resources/views/expenses/edit.blade.php | 2 +- resources/views/invoices/edit.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index f0b44e700e7c..7fb96d2793f1 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -247,7 +247,7 @@ @foreach(trans('texts.dropzone') as $key=>$text) "dict{{strval($key)}}":"{{strval($text)}}", @endforeach - maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, + maxFilesize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, }); if(dropzone instanceof Dropzone){ dropzone.on("addedfile",handleDocumentAdded); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 7db80f69089c..ccf2d35f359c 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -996,7 +996,7 @@ @foreach(trans('texts.dropzone') as $key=>$text) "dict{{strval($key)}}":"{{strval($text)}}", @endforeach - maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, + maxFilesize:{{floatval(MAX_DOCUMENT_SIZE/1000)}}, }); if(dropzone instanceof Dropzone){ dropzone.on("addedfile",handleDocumentAdded); From da860c0d07c09888bd4426141f3f53e1f4ed70e8 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 29 May 2016 18:43:57 +0300 Subject: [PATCH 060/111] Fix for when document is too large --- resources/views/expenses/edit.blade.php | 8 ++++++-- resources/views/invoices/edit.blade.php | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 7fb96d2793f1..5c0f05b8ab35 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -254,6 +254,7 @@ dropzone.on("removedfile",handleDocumentRemoved); dropzone.on("success",handleDocumentUploaded); dropzone.on("canceled",handleDocumentCanceled); + dropzone.on("error",handleDocumentError); for (var i=0; i Date: Sun, 29 May 2016 22:12:26 +0300 Subject: [PATCH 061/111] Changed attach defaults --- database/migrations/2016_03_22_168362_add_documents.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/migrations/2016_03_22_168362_add_documents.php b/database/migrations/2016_03_22_168362_add_documents.php index b5e9da2deb1a..29fa07a6cbc1 100644 --- a/database/migrations/2016_03_22_168362_add_documents.php +++ b/database/migrations/2016_03_22_168362_add_documents.php @@ -15,8 +15,8 @@ class AddDocuments extends Migration { $table->unsignedInteger('logo_width'); $table->unsignedInteger('logo_height'); $table->unsignedInteger('logo_size'); - $table->boolean('invoice_embed_documents')->default(1); - $table->boolean('document_email_attachment')->default(1); + $table->boolean('invoice_embed_documents')->default(0); + $table->boolean('document_email_attachment')->default(0); }); \DB::table('accounts')->update(array('logo' => '')); @@ -45,7 +45,7 @@ class AddDocuments extends Migration { $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); $t->foreign('expense_id')->references('id')->on('expenses')->onDelete('cascade'); - + $t->unique( array('account_id','public_id') ); }); From 1760d0e0e4643e609ca85df73a7bee25297cd375 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 10:58:03 +0300 Subject: [PATCH 062/111] Prevent converting a qoute from incrementing the counter --- app/Models/Account.php | 218 ++++++++++--------- app/Ninja/Repositories/InvoiceRepository.php | 42 ++-- 2 files changed, 131 insertions(+), 129 deletions(-) diff --git a/app/Models/Account.php b/app/Models/Account.php index d3c2f2426062..b0fc07cb79f6 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -18,7 +18,7 @@ class Account extends Eloquent { use PresentableTrait; use SoftDeletes; - + public static $plan_prices = array( PLAN_PRO => array( PLAN_TERM_MONTHLY => PLAN_PRICE_PRO_MONTHLY, @@ -388,7 +388,7 @@ class Account extends Eloquent return $gateway; } } - + return false; } @@ -408,27 +408,27 @@ class Account extends Eloquent if($this->logo == ''){ $this->calculateLogoDetails(); } - + return !empty($this->logo); } - + public function getLogoDisk(){ return Storage::disk(env('LOGO_FILESYSTEM', '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_height = imagesy($image); $this->logo_size = $disk->size($this->logo); } else { $this->logo = null; @@ -440,33 +440,33 @@ class Account extends Eloquent if(!$this->hasLogo()){ return null; } - + $disk = $this->getLogoDisk(); return $disk->get($this->logo); } - + public function getLogoURL($cachebuster = false) { 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 ($cachebuster) { $logo_url .= '?no_cache='.time(); } - + if($count == 1){ return str_replace(DIRECTORY_SEPARATOR, '/', $logo_url); } } - + return Document::getDirectFileUrl($this->logo, $this->getLogoDisk()); } @@ -516,7 +516,7 @@ class Account extends Eloquent $invoice->start_date = Utils::today(); $invoice->invoice_design_id = $this->invoice_design_id; $invoice->client_id = $clientId; - + if ($entityType === ENTITY_RECURRING_INVOICE) { $invoice->invoice_number = microtime(true); $invoice->is_recurring = true; @@ -531,7 +531,7 @@ class Account extends Eloquent $invoice->invoice_number = $this->getNextInvoiceNumber($invoice); } } - + if (!$clientId) { $invoice->client = Client::createNew(); $invoice->client->public_id = 0; @@ -561,7 +561,7 @@ class Account extends Eloquent public function hasClientNumberPattern($invoice) { $pattern = $invoice->is_quote ? $this->quote_number_pattern : $this->invoice_number_pattern; - + return strstr($pattern, '$custom'); } @@ -633,31 +633,35 @@ class Account extends Eloquent public function getNextInvoiceNumber($invoice) { - if ($this->hasNumberPattern($invoice->is_quote)) { - return $this->getNumberPattern($invoice); + if ($this->hasNumberPattern($invoice->invoice_type_id)) { + $number = $this->getNumberPattern($invoice); + } else { + $counter = $this->getCounter($invoice->invoice_type_id); + $prefix = $this->getNumberPrefix($invoice->invoice_type_id); + $counterOffset = 0; + + // confirm the invoice number isn't already taken + do { + $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); + $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); + $counter++; + $counterOffset++; + } while ($check); + + // update the invoice counter to be caught up + if ($counterOffset > 1) { + if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { + $this->quote_number_counter += $counterOffset - 1; + } else { + $this->invoice_number_counter += $counterOffset - 1; + } + + $this->save(); + } } - $counter = $this->getCounter($invoice->is_quote); - $prefix = $this->getNumberPrefix($invoice->is_quote); - $counterOffset = 0; - - // confirm the invoice number isn't already taken - do { - $number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT); - $check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first(); - $counter++; - $counterOffset++; - } while ($check); - - // update the invoice counter to be caught up - if ($counterOffset > 1) { - if ($invoice->is_quote && !$this->share_counter) { - $this->quote_number_counter += $counterOffset - 1; - } else { - $this->invoice_number_counter += $counterOffset - 1; - } - - $this->save(); + if ($invoice->recurring_invoice_id) { + $number = $this->recurring_invoice_number_prefix . $number; } return $number; @@ -665,19 +669,17 @@ class Account extends Eloquent public function incrementCounter($invoice) { - if ($invoice->is_quote && !$this->share_counter) { + // if they didn't use the counter don't increment it + if ($invoice->invoice_number != $this->getNextInvoiceNumber($invoice)) { + return; + } + + if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { $this->quote_number_counter += 1; } else { - $default = $this->invoice_number_counter; - $actual = Utils::parseInt($invoice->invoice_number); - - if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $default != $actual) { - $this->invoice_number_counter = $actual + 1; - } else { - $this->invoice_number_counter += 1; - } + $this->invoice_number_counter += 1; } - + $this->save(); } @@ -700,7 +702,7 @@ class Account extends Eloquent $query->where('updated_at', '>=', $updatedAt); } }]); - } + } } public function loadLocalizationSettings($client = false) @@ -713,8 +715,8 @@ class Account extends Eloquent Session::put(SESSION_DATE_FORMAT, $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT); Session::put(SESSION_DATE_PICKER_FORMAT, $this->date_format ? $this->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT); - $currencyId = ($client && $client->currency_id) ? $client->currency_id : $this->currency_id ?: DEFAULT_CURRENCY; - $locale = ($client && $client->language_id) ? $client->language->locale : ($this->language_id ? $this->Language->locale : DEFAULT_LOCALE); + $currencyId = ($client && $client->currency_id) ? $client->currency_id : $this->currency_id ?: DEFAULT_CURRENCY; + $locale = ($client && $client->language_id) ? $client->language->locale : ($this->language_id ? $this->Language->locale : DEFAULT_LOCALE); Session::put(SESSION_CURRENCY, $currencyId); Session::put(SESSION_LOCALE, $locale); @@ -797,7 +799,7 @@ class Account extends Eloquent if ( ! Utils::isNinja()) { return; } - + $this->company->trial_plan = $plan; $this->company->trial_started = date_create()->format('Y-m-d'); $this->company->save(); @@ -808,18 +810,18 @@ class Account extends Eloquent if (Utils::isNinjaDev()) { return true; } - + $planDetails = $this->getPlanDetails(); $selfHost = !Utils::isNinjaProd(); - + if (!$selfHost && function_exists('ninja_account_features')) { $result = ninja_account_features($this, $feature); - + if ($result != null) { return $result; } } - + switch ($feature) { // Pro case FEATURE_CUSTOMIZE_INVOICE_DESIGN: @@ -836,7 +838,7 @@ class Account extends Eloquent case FEATURE_CLIENT_PORTAL_PASSWORD: case FEATURE_CUSTOM_URL: return $selfHost || !empty($planDetails); - + // Pro; No trial allowed, unless they're trialing enterprise with an active pro plan case FEATURE_MORE_CLIENTS: return $selfHost || !empty($planDetails) && (!$planDetails['trial'] || !empty($this->getPlanDetails(false, false))); @@ -849,26 +851,26 @@ class Account extends Eloquent // Fallthrough case FEATURE_CLIENT_PORTAL_CSS: return !empty($planDetails);// A plan is required even for self-hosted users - + // Enterprise; No Trial allowed; grandfathered for old pro users case FEATURE_USERS:// Grandfathered for old Pro users if($planDetails && $planDetails['trial']) { // Do they have a non-trial plan? $planDetails = $this->getPlanDetails(false, false); } - + return $selfHost || !empty($planDetails) && ($planDetails['plan'] == PLAN_ENTERPRISE || $planDetails['started'] <= date_create(PRO_USERS_GRANDFATHER_DEADLINE)); - + // Enterprise; No Trial allowed case FEATURE_DOCUMENTS: case FEATURE_USER_PERMISSIONS: return $selfHost || !empty($planDetails) && $planDetails['plan'] == PLAN_ENTERPRISE && !$planDetails['trial']; - + default: return false; } } - + public function isPro(&$plan_details = null) { if (!Utils::isNinjaProd()) { @@ -880,7 +882,7 @@ class Account extends Eloquent } $plan_details = $this->getPlanDetails(); - + return !empty($plan_details); } @@ -895,36 +897,36 @@ class Account extends Eloquent } $plan_details = $this->getPlanDetails(); - + return $plan_details && $plan_details['plan'] == PLAN_ENTERPRISE; } - + public function getPlanDetails($include_inactive = false, $include_trial = true) { if (!$this->company) { return null; } - + $plan = $this->company->plan; $trial_plan = $this->company->trial_plan; - + if(!$plan && (!$trial_plan || !$include_trial)) { return null; - } - + } + $trial_active = false; if ($trial_plan && $include_trial) { $trial_started = DateTime::createFromFormat('Y-m-d', $this->company->trial_started); $trial_expires = clone $trial_started; $trial_expires->modify('+2 weeks'); - + if ($trial_expires >= date_create()) { $trial_active = true; } } - + $plan_active = false; - if ($plan) { + if ($plan) { if ($this->company->plan_expires == null) { $plan_active = true; $plan_expires = false; @@ -935,11 +937,11 @@ class Account extends Eloquent } } } - + if (!$include_inactive && !$plan_active && !$trial_active) { return null; } - + // Should we show plan details or trial details? if (($plan && !$trial_plan) || !$include_trial) { $use_plan = true; @@ -966,7 +968,7 @@ class Account extends Eloquent $use_plan = $plan_expires >= $trial_expires; } } - + if ($use_plan) { return array( 'trial' => false, @@ -993,7 +995,7 @@ class Account extends Eloquent if (!Utils::isNinjaProd()) { return false; } - + $plan_details = $this->getPlanDetails(); return $plan_details && $plan_details['trial']; @@ -1008,7 +1010,7 @@ class Account extends Eloquent return array(PLAN_PRO, PLAN_ENTERPRISE); } } - + if ($this->company->trial_plan == PLAN_PRO) { if ($plan) { return $plan != PLAN_PRO; @@ -1016,28 +1018,28 @@ class Account extends Eloquent return array(PLAN_ENTERPRISE); } } - + return false; } public function getCountTrialDaysLeft() { $planDetails = $this->getPlanDetails(true); - + if(!$planDetails || !$planDetails['trial']) { return 0; } - + $today = new DateTime('now'); $interval = $today->diff($planDetails['expires']); - + return $interval ? $interval->d : 0; } public function getRenewalDate() { $planDetails = $this->getPlanDetails(); - + if ($planDetails) { $date = $planDetails['expires']; $date = max($date, date_create()); @@ -1167,7 +1169,7 @@ class Account extends Eloquent $field = "email_template_{$entityType}"; $template = $this->$field; } - + if (!$template) { $template = $this->getDefaultEmailTemplate($entityType, $message); } @@ -1236,7 +1238,7 @@ class Account extends Eloquent { $url = SITE_URL; $iframe_url = $this->iframe_url; - + if ($iframe_url) { return "{$iframe_url}/?"; } else if ($this->subdomain) { @@ -1271,10 +1273,10 @@ class Account extends Eloquent if (!$entity) { return false; } - + // convert (for example) 'custom_invoice_label1' to 'invoice.custom_value1' $field = str_replace(['invoice_', 'label'], ['', 'value'], $field); - + return Utils::isEmpty($entity->$field) ? false : true; } @@ -1282,7 +1284,7 @@ class Account extends Eloquent { return $this->hasFeature(FEATURE_PDF_ATTACHMENT) && $this->pdf_email_attachment; } - + public function getEmailDesignId() { return $this->hasFeature(FEATURE_CUSTOM_EMAILS) ? $this->email_design_id : EMAIL_DESIGN_PLAIN; @@ -1290,11 +1292,11 @@ class Account extends Eloquent public function clientViewCSS(){ $css = ''; - + if ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN)) { $bodyFont = $this->getBodyFontCss(); $headerFont = $this->getHeaderFontCss(); - + $css = 'body{'.$bodyFont.'}'; if ($headerFont != $bodyFont) { $css .= 'h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{'.$headerFont.'}'; @@ -1304,17 +1306,17 @@ class Account extends Eloquent // For self-hosted users, a white-label license is required for custom CSS $css .= $this->client_view_css; } - + return $css; } - + public function getFontsUrl($protocol = ''){ $bodyFont = $this->getHeaderFontId(); $headerFont = $this->getBodyFontId(); $bodyFontSettings = Utils::getFromCache($bodyFont, 'fonts'); $google_fonts = array($bodyFontSettings['google_font']); - + if($headerFont != $bodyFont){ $headerFontSettings = Utils::getFromCache($headerFont, 'fonts'); $google_fonts[] = $headerFontSettings['google_font']; @@ -1322,7 +1324,7 @@ class Account extends Eloquent return ($protocol?$protocol.':':'').'//fonts.googleapis.com/css?family='.implode('|',$google_fonts); } - + public function getHeaderFontId() { return ($this->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN) && $this->header_font_id) ? $this->header_font_id : DEFAULT_HEADER_FONT; } @@ -1334,47 +1336,47 @@ class Account extends Eloquent public function getHeaderFontName(){ return Utils::getFromCache($this->getHeaderFontId(), 'fonts')['name']; } - + public function getBodyFontName(){ return Utils::getFromCache($this->getBodyFontId(), 'fonts')['name']; } - + public function getHeaderFontCss($include_weight = true){ $font_data = Utils::getFromCache($this->getHeaderFontId(), 'fonts'); $css = 'font-family:'.$font_data['css_stack'].';'; - + if($include_weight){ $css .= 'font-weight:'.$font_data['css_weight'].';'; } - + return $css; } - + public function getBodyFontCss($include_weight = true){ $font_data = Utils::getFromCache($this->getBodyFontId(), 'fonts'); $css = 'font-family:'.$font_data['css_stack'].';'; - + if($include_weight){ $css .= 'font-weight:'.$font_data['css_weight'].';'; } - + return $css; } - + public function getFonts(){ return array_unique(array($this->getHeaderFontId(), $this->getBodyFontId())); } - + public function getFontsData(){ $data = array(); - + foreach($this->getFonts() as $font){ $data[] = Utils::getFromCache($font, 'fonts'); } - + return $data; } - + public function getFontFolders(){ return array_map(function($item){return $item['folder'];}, $this->getFontsData()); } diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 91ffddfea138..5d09ec2d7432 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -163,7 +163,7 @@ class InvoiceRepository extends BaseRepository ->where('contacts.is_primary', '=', true) ->where('invoices.is_recurring', '=', false) // This needs to be a setting to also hide the activity on the dashboard page - //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) + //->where('invoices.invoice_status_id', '>=', INVOICE_STATUS_SENT) ->select( DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'), DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'), @@ -240,7 +240,7 @@ class InvoiceRepository extends BaseRepository $account->invoice_footer = trim($data['invoice_footer']); } $account->save(); - } + } if (isset($data['invoice_number']) && !$invoice->is_recurring) { $invoice->invoice_number = trim($data['invoice_number']); @@ -277,7 +277,7 @@ class InvoiceRepository extends BaseRepository $invoice->start_date = Utils::toSqlDate($data['start_date']); $invoice->end_date = Utils::toSqlDate($data['end_date']); $invoice->auto_bill = isset($data['auto_bill']) && $data['auto_bill'] ? true : false; - + if (isset($data['recurring_due_date'])) { $invoice->due_date = $data['recurring_due_date']; } elseif (isset($data['due_date'])) { @@ -299,7 +299,7 @@ class InvoiceRepository extends BaseRepository } else { $invoice->terms = ''; } - + $invoice->invoice_footer = (isset($data['invoice_footer']) && trim($data['invoice_footer'])) ? trim($data['invoice_footer']) : (!$publicId && $account->invoice_footer ? $account->invoice_footer : ''); $invoice->public_notes = isset($data['public_notes']) ? trim($data['public_notes']) : null; @@ -318,8 +318,8 @@ class InvoiceRepository extends BaseRepository // provide backwards compatability if (isset($data['tax_name']) && isset($data['tax_rate'])) { - $data['tax_name1'] = $data['tax_name']; - $data['tax_rate1'] = $data['tax_rate']; + $data['tax_name1'] = $data['tax_name']; + $data['tax_rate1'] = $data['tax_rate']; } $total = 0; @@ -353,11 +353,11 @@ class InvoiceRepository extends BaseRepository } if (isset($item['tax_rate1']) && Utils::parseFloat($item['tax_rate1']) > 0) { - $invoiceItemTaxRate = Utils::parseFloat($item['tax_rate1']); + $invoiceItemTaxRate = Utils::parseFloat($item['tax_rate1']); $itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2); } if (isset($item['tax_rate2']) && Utils::parseFloat($item['tax_rate2']) > 0) { - $invoiceItemTaxRate = Utils::parseFloat($item['tax_rate2']); + $invoiceItemTaxRate = Utils::parseFloat($item['tax_rate2']); $itemTax += round($lineTotal * $invoiceItemTaxRate / 100, 2); } } @@ -401,7 +401,7 @@ class InvoiceRepository extends BaseRepository $taxAmount1 = round($total * $invoice->tax_rate1 / 100, 2); $taxAmount2 = round($total * $invoice->tax_rate2 / 100, 2); - $total = round($total + $taxAmount1 + $taxAmount2, 2); + $total = round($total + $taxAmount1 + $taxAmount2, 2); $total += $itemTax; // custom fields not charged taxes @@ -424,24 +424,24 @@ class InvoiceRepository extends BaseRepository if ($publicId) { $invoice->invoice_items()->forceDelete(); } - + $document_ids = !empty($data['document_ids'])?array_map('intval', $data['document_ids']):array();; foreach ($document_ids as $document_id){ $document = Document::scope($document_id)->first(); if($document && Auth::user()->can('edit', $document)){ - + if($document->invoice_id && $document->invoice_id != $invoice->id){ // From a clone $document = $document->cloneDocument(); $document_ids[] = $document->public_id;// Don't remove this document } - + $document->invoice_id = $invoice->id; $document->expense_id = null; $document->save(); } } - + if(!empty($data['documents']) && Auth::user()->can('create', ENTITY_DOCUMENT)){ // Fallback upload $doc_errors = array(); @@ -460,7 +460,7 @@ class InvoiceRepository extends BaseRepository Session::flash('error', implode('
    ',array_map('htmlentities',$doc_errors))); } } - + foreach ($invoice->documents as $document){ if(!in_array($document->public_id, $document_ids)){ // Removed @@ -534,12 +534,12 @@ class InvoiceRepository extends BaseRepository // provide backwards compatability if (isset($item['tax_name']) && isset($item['tax_rate'])) { - $item['tax_name1'] = $item['tax_name']; - $item['tax_rate1'] = $item['tax_rate']; + $item['tax_name1'] = $item['tax_name']; + $item['tax_rate1'] = $item['tax_rate']; } $invoiceItem->fill($item); - + $invoice->invoice_items()->save($invoiceItem); } @@ -623,9 +623,9 @@ class InvoiceRepository extends BaseRepository 'cost', 'qty', 'tax_name1', - 'tax_rate1', + 'tax_rate1', 'tax_name2', - 'tax_rate2', + 'tax_rate2', ] as $field) { $cloneItem->$field = $item->$field; } @@ -634,7 +634,7 @@ class InvoiceRepository extends BaseRepository } foreach ($invoice->documents as $document) { - $cloneDocument = $document->cloneDocument(); + $cloneDocument = $document->cloneDocument(); $invoice->documents()->save($cloneDocument); } @@ -708,7 +708,7 @@ class InvoiceRepository extends BaseRepository $invoice = Invoice::createNew($recurInvoice); $invoice->client_id = $recurInvoice->client_id; $invoice->recurring_invoice_id = $recurInvoice->id; - $invoice->invoice_number = $recurInvoice->account->recurring_invoice_number_prefix . $recurInvoice->account->getNextInvoiceNumber($recurInvoice); + $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber($invoice); $invoice->amount = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount; $invoice->invoice_date = date_create()->format('Y-m-d'); From e08e8c1962102c653e3132c68d64b6bec8d38fa5 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 12:56:59 +0300 Subject: [PATCH 063/111] Support downloading PDF through the API --- app/Http/Controllers/InvoiceApiController.php | 23 +++++++++++++++---- app/Http/routes.php | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index 667690395eb2..6d68573cdb42 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -183,7 +183,7 @@ class InvoiceApiController extends BaseAPIController $invoice = Invoice::scope($invoice->public_id) ->with('client', 'invoice_items', 'invitations') ->first(); - + return $this->itemResponse($invoice); } @@ -270,7 +270,7 @@ class InvoiceApiController extends BaseAPIController $item[$key] = $val; } } - + return $item; } @@ -309,7 +309,7 @@ class InvoiceApiController extends BaseAPIController public function update(UpdateInvoiceAPIRequest $request, $publicId) { if ($request->action == ACTION_CONVERT) { - $quote = $request->entity(); + $quote = $request->entity(); $invoice = $this->invoiceRepo->cloneInvoice($quote, $quote->id); return $this->itemResponse($invoice); } elseif ($request->action) { @@ -323,7 +323,7 @@ class InvoiceApiController extends BaseAPIController $invoice = Invoice::scope($publicId) ->with('client', 'invoice_items', 'invitations') ->firstOrFail(); - + return $this->itemResponse($invoice); } @@ -352,10 +352,23 @@ class InvoiceApiController extends BaseAPIController public function destroy(UpdateInvoiceAPIRequest $request) { $invoice = $request->entity(); - + $this->invoiceRepo->delete($invoice); return $this->itemResponse($invoice); } + public function download(InvoiceRequest $request) + { + $invoice = $request->entity(); + $pdfString = $invoice->getPDFString(); + + header('Content-Type: application/pdf'); + header('Content-Length: ' . strlen($pdfString)); + header('Content-disposition: attachment; filename="' . $invoice->getFileName() . '"'); + header('Cache-Control: public, must-revalidate, max-age=0'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + + return $pdfString; + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 2c3fba6fec4b..be5972c0d1d2 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -255,6 +255,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() //Route::get('quotes', 'QuoteApiController@index'); //Route::resource('quotes', 'QuoteApiController'); Route::get('invoices', 'InvoiceApiController@index'); + Route::get('download/{invoice_id}', 'InvoiceApiController@download'); Route::resource('invoices', 'InvoiceApiController'); Route::get('payments', 'PaymentApiController@index'); Route::resource('payments', 'PaymentApiController'); From 53eb0cb127b759e669995c8671df80af7a654b9f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 13:26:59 +0300 Subject: [PATCH 064/111] Fixed is_quote reference --- app/Models/Account.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Account.php b/app/Models/Account.php index b0fc07cb79f6..7f6218462536 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -650,7 +650,7 @@ class Account extends Eloquent // update the invoice counter to be caught up if ($counterOffset > 1) { - if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { + if ($invoice->is_quote && !$this->share_counter) { $this->quote_number_counter += $counterOffset - 1; } else { $this->invoice_number_counter += $counterOffset - 1; @@ -674,7 +674,7 @@ class Account extends Eloquent return; } - if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { + if ($invoice->is_quote && !$this->share_counter) { $this->quote_number_counter += 1; } else { $this->invoice_number_counter += 1; From e392ed1921f546a3c24dd2598ad67fc7e0ab82c7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 16:50:10 +0300 Subject: [PATCH 065/111] Display number of documents --- resources/views/invoices/edit.blade.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 0de42f092dea..e4d0ba7dc431 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -304,7 +304,12 @@
  • {{ trans("texts.footer") }}
  • @if ($account->hasFeature(FEATURE_DOCUMENTS)) -
  • {{ trans("texts.invoice_documents") }}
  • +
  • + {{ trans("texts.invoice_documents") }} + @if (count($invoice->documents)) + ({{ count($invoice->documents) }}) + @endif +
  • @endif From 0c2bd4414d8ce4835124223d9ffd082c0969a1ea Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 1 Jun 2016 17:33:16 +0300 Subject: [PATCH 066/111] Don't refresh PDF after file uploaded --- resources/views/invoices/edit.blade.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index e4d0ba7dc431..94245c508959 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -1435,7 +1435,9 @@ file.public_id = response.document.public_id model.invoice().documents()[file.index].update(response.document); window.countUploadingDocuments--; - refreshPDF(true); + @if ($account->invoice_embed_documents) + refreshPDF(true); + @endif if(response.document.preview_url){ dropzone.emit('thumbnail', file, response.document.preview_url); } From a2f9daf1906149aebf5af34b5c497ffca5dfa0df Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 4 Jun 2016 21:56:25 +0300 Subject: [PATCH 067/111] Hide 2nd tax rate until option is available --- resources/views/invoices/edit.blade.php | 38 ++++++++++++++----------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 94245c508959..1a65353f7e37 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -18,7 +18,7 @@ font-weight: normal !important; } - select.tax-select { + select.xtax-select { width: 50%; float: left; } @@ -271,14 +271,16 @@ ->raw() !!} - {!! Former::select('') - ->addOption('', '') - ->options($taxRateOptions) - ->data_bind('value: tax2') - ->addClass('tax-select') - ->raw() !!} - - +
    + {!! Former::select('') + ->addOption('', '') + ->options($taxRateOptions) + ->data_bind('value: tax2') + ->addClass('tax-select') + ->raw() !!} + + +
    @@ -427,14 +429,16 @@ ->raw() !!} - {!! Former::select('') - ->addOption('', '') - ->options($taxRateOptions) - ->addClass('tax-select') - ->data_bind('value: tax2') - ->raw() !!} - - +
    + {!! Former::select('') + ->addOption('', '') + ->options($taxRateOptions) + ->addClass('tax-select') + ->data_bind('value: tax2') + ->raw() !!} + + +
    From 02a6213e346db89dac399da9789ab035406f2427 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sat, 4 Jun 2016 23:22:27 +0300 Subject: [PATCH 068/111] Updated grandfathered user date --- 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 be5972c0d1d2..b5d52c56b497 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -690,7 +690,7 @@ if (!defined('CONTACT_EMAIL')) { define('FEATURE_USER_PERMISSIONS', 'user_permissions'); // Pro users who started paying on or before this date will be able to manage users - define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-05-15'); + define('PRO_USERS_GRANDFATHER_DEADLINE', '2016-06-04'); $creditCards = [ 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], From 4c8414f616693a787a6fcaba952c47cb87e2c0d2 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 5 Jun 2016 08:24:16 +0300 Subject: [PATCH 069/111] git update --- app/Http/routes.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index b5d52c56b497..5921285cf600 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,6 +1,5 @@ Date: Sun, 5 Jun 2016 18:47:51 +0300 Subject: [PATCH 070/111] Check for blank api secret --- app/Http/Middleware/ApiCheck.php | 5 +- app/Http/routes.php | 5 +- app/Models/Document.php | 79 ++++++++++++------------- resources/views/expenses/edit.blade.php | 4 +- resources/views/invoices/edit.blade.php | 5 +- resources/views/master.blade.php | 47 +++++++++------ 6 files changed, 78 insertions(+), 67 deletions(-) diff --git a/app/Http/Middleware/ApiCheck.php b/app/Http/Middleware/ApiCheck.php index 5200c3264a96..524b718cc44f 100644 --- a/app/Http/Middleware/ApiCheck.php +++ b/app/Http/Middleware/ApiCheck.php @@ -23,7 +23,10 @@ class ApiCheck { { $loggingIn = $request->is('api/v1/login') || $request->is('api/v1/register'); $headers = Utils::getApiHeaders(); - $hasApiSecret = hash_equals($request->api_secret ?: '', env(API_SECRET)); + + if ($secret = env(API_SECRET)) { + $hasApiSecret = hash_equals($request->api_secret ?: '', $secret); + } if ($loggingIn) { // check API secret diff --git a/app/Http/routes.php b/app/Http/routes.php index 5921285cf600..cf396c3de9e2 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -303,11 +303,10 @@ Route::get('/testimonials', function() { Route::get('/compare-online-invoicing{sites?}', function() { return Redirect::to(NINJA_WEB_URL, 301); }); -Route::get('/forgot_password', function() { - return Redirect::to(NINJA_APP_URL.'/forgot', 301); +Route::get('/forgot', function() { + return Redirect::to(NINJA_APP_URL.'/recover_password', 301); }); - if (!defined('CONTACT_EMAIL')) { define('CONTACT_EMAIL', Config::get('mail.from.address')); define('CONTACT_NAME', Config::get('mail.from.name')); diff --git a/app/Models/Document.php b/app/Models/Document.php index 6d9c24857143..f1d6d6b9c5bd 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -10,16 +10,16 @@ class Document extends EntityModel '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/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( 'png' => array( 'mime' => 'image/png', @@ -48,9 +48,6 @@ class Document extends EntityModel 'txt' => array( 'mime' => 'text/plain', ), - 'zip' => array( - 'mime' => 'application/zip', - ), 'doc' => array( 'mime' => 'application/msword', ), @@ -70,18 +67,18 @@ class Document extends EntityModel 'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', ), ); - + public function fill(array $attributes) { parent::fill($attributes); - + if(empty($this->attributes['disk'])){ $this->attributes['disk'] = env('DOCUMENT_FILESYSTEM', 'documents'); } - + return $this; } - + public function account() { return $this->belongsTo('App\Models\Account'); @@ -101,7 +98,7 @@ class Document extends EntityModel { return $this->belongsTo('App\Models\Invoice')->withTrashed(); } - + public function getDisk(){ return Storage::disk(!empty($this->disk)?$this->disk:env('DOCUMENT_FILESYSTEM', 'documents')); } @@ -110,19 +107,19 @@ class Document extends EntityModel { $this->attributes['disk'] = $value?$value:env('DOCUMENT_FILESYSTEM', 'documents'); } - + 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', [ @@ -136,12 +133,12 @@ class Document extends EntityModel $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()); @@ -150,64 +147,64 @@ class Document extends EntityModel return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry); } } - + return null; } - + public function getRaw(){ $disk = $this->getDisk(); - + return $disk->get($this->path); } - + public function getStream(){ $disk = $this->getDisk(); - + return $disk->readStream($this->path); } - + public function getRawPreview(){ $disk = $this->getDisk(); - + return $disk->get($this->preview); } - + public function getUrl(){ return url('documents/'.$this->public_id.'/'.$this->name); } - + public function getClientUrl($invitation){ return url('client/documents/'.$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('documents/js/'.$this->public_id.'/'.$this->name.'.js'); } - + public function getClientVFSJSUrl(){ if(!$this->isPDFEmbeddable())return null; return url('client/documents/js/'.$this->public_id.'/'.$this->name.'.js'); } - + public function getPreviewUrl(){ return $this->preview?url('documents/preview/'.$this->public_id.'/'.$this->name.'.'.pathinfo($this->preview, PATHINFO_EXTENSION)):null; } - + public function toArray() { $array = parent::toArray(); - + if(empty($this->visible) || in_array('url', $this->visible))$array['url'] = $this->getUrl(); if(empty($this->visible) || in_array('preview_url', $this->visible))$array['preview_url'] = $this->getPreviewUrl(); - + return $array; } - + public function cloneDocument(){ $document = Document::createNew($this); $document->path = $this->path; @@ -219,7 +216,7 @@ class Document extends EntityModel $document->size = $this->size; $document->width = $this->width; $document->height = $this->height; - + return $document; } } @@ -230,11 +227,11 @@ Document::deleted(function ($document) { ->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) @@ -245,5 +242,5 @@ Document::deleted(function ($document) { $document->getDisk()->delete($document->preview); } } - -}); \ No newline at end of file + +}); diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 5c0f05b8ab35..0f1175d9f971 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -378,7 +378,7 @@ } window.countUploadingDocuments = 0; - @if (Auth::user()->account->hasFeature(FEATURE_DOCUMENTS)) + function handleDocumentAdded(file){ // open document when clicked if (file.url) { @@ -419,7 +419,7 @@ function handleDocumentError() { window.countUploadingDocuments--; } - @endif + @stop diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 1a65353f7e37..404f4a8338f6 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -222,7 +222,7 @@ @endif {{ $invoiceLabels['unit_cost'] }} {{ $invoiceLabels['quantity'] }} - {{ trans('texts.tax') }} + {{ trans('texts.tax') }} {{ trans('texts.line_total') }} @@ -1409,7 +1409,7 @@ } window.countUploadingDocuments = 0; - @if ($account->hasFeature(FEATURE_DOCUMENTS)) + function handleDocumentAdded(file){ // open document when clicked if (file.url) { @@ -1454,7 +1454,6 @@ function handleDocumentError() { window.countUploadingDocuments--; } - @endif @if ($account->hasFeature(FEATURE_DOCUMENTS) && $account->invoice_embed_documents) diff --git a/resources/views/master.blade.php b/resources/views/master.blade.php index b0d59cc90254..19bb607ce612 100644 --- a/resources/views/master.blade.php +++ b/resources/views/master.blade.php @@ -4,7 +4,7 @@ @if (isset($hideLogo) && $hideLogo) {{ trans('texts.client_portal') }} @else - {{ isset($title) ? ($title . ' | Invoice Ninja') : ('Invoice Ninja | ' . trans('texts.app_title')) }} + {{ isset($title) ? ($title . ' | Invoice Ninja') : ('Invoice Ninja | ' . trans('texts.app_title')) }} @endif @@ -22,24 +22,37 @@ - + + + + + + + + + + - + @@ -132,7 +145,7 @@ - @if (isset($_ENV['TAG_MANAGER_KEY']) && $_ENV['TAG_MANAGER_KEY']) + @if (isset($_ENV['TAG_MANAGER_KEY']) && $_ENV['TAG_MANAGER_KEY']) @@ -140,20 +153,20 @@ new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); - })(window,document,'script','dataLayer','{{ $_ENV['TAG_MANAGER_KEY'] }}'); + })(window,document,'script','dataLayer','{{ $_ENV['TAG_MANAGER_KEY'] }}'); - @elseif (isset($_ENV['ANALYTICS_KEY']) && $_ENV['ANALYTICS_KEY']) + @elseif (isset($_ENV['ANALYTICS_KEY']) && $_ENV['ANALYTICS_KEY']) @endif - + @yield('body') + From 85d94b5a36178c1cca464b400206cf30058f9287 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 5 Jun 2016 19:11:37 +0300 Subject: [PATCH 071/111] Minor fixes --- app/Http/routes.php | 3 +++ app/Ninja/Repositories/ReferralRepository.php | 6 ++--- resources/lang/en/texts.php | 24 ++++++++++--------- resources/views/master.blade.php | 2 +- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/Http/routes.php b/app/Http/routes.php index cf396c3de9e2..f6574b9762c9 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -306,6 +306,9 @@ Route::get('/compare-online-invoicing{sites?}', function() { Route::get('/forgot', function() { return Redirect::to(NINJA_APP_URL.'/recover_password', 301); }); +Route::get('/feed', function() { + return Redirect::to(NINJA_WEB_URL.'/feed', 301); +}); if (!defined('CONTACT_EMAIL')) { define('CONTACT_EMAIL', Config::get('mail.from.address')); diff --git a/app/Ninja/Repositories/ReferralRepository.php b/app/Ninja/Repositories/ReferralRepository.php index f96475c72ddc..f6170af296e3 100644 --- a/app/Ninja/Repositories/ReferralRepository.php +++ b/app/Ninja/Repositories/ReferralRepository.php @@ -7,7 +7,7 @@ class ReferralRepository { public function getCounts($userId) { - $accounts = Account::where('referral_user_id', $userId); + $accounts = Account::where('referral_user_id', $userId)->get(); $counts = [ 'free' => 0, @@ -18,7 +18,7 @@ class ReferralRepository foreach ($accounts as $account) { $counts['free']++; $plan = $account->getPlanDetails(false, false); - + if ($plan) { $counts['pro']++; if ($plan['plan'] == PLAN_ENTERPRISE) { @@ -29,4 +29,4 @@ class ReferralRepository return $counts; } -} \ No newline at end of file +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 624a472f8ac9..571d2b518a00 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1054,14 +1054,14 @@ $LANG = array( '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', @@ -1079,8 +1079,8 @@ $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', - + 'view_payment' => 'View Payment', + 'january' => 'January', 'february' => 'February', 'march' => 'March', @@ -1093,7 +1093,7 @@ $LANG = array( 'october' => 'October', 'november' => 'November', 'december' => 'December', - + // Documents 'documents_header' => 'Documents:', 'email_documents_header' => 'Documents:', @@ -1125,11 +1125,11 @@ $LANG = array( 'enable_client_portal_help' => 'Show/hide the client portal.', 'enable_client_portal_dashboard' => 'Dashboard', 'enable_client_portal_dashboard_help' => 'Show/hide the dashboard page in the client portal.', - + // Plans 'account_management' => 'Account Management', 'plan_status' => 'Plan Status', - + 'plan_upgrade' => 'Upgrade', 'plan_change' => 'Change Plan', 'pending_change_to' => 'Changes To', @@ -1159,9 +1159,9 @@ $LANG = array( 'plan_paid' => 'Term Started', 'plan_started' => 'Plan Started', 'plan_expires' => 'Plan Expires', - + 'white_label_button' => 'White Label', - + 'pro_plan_year_description' => 'One year enrollment in the Invoice Ninja Pro Plan.', 'pro_plan_month_description' => 'One month enrollment in the Invoice Ninja Pro Plan.', 'enterprise_plan_product' => 'Enterprise Plan', @@ -1181,9 +1181,11 @@ $LANG = array( 'add_users_not_supported' => 'Upgrade to the Enterprise plan to add additional users to your account.', 'enterprise_plan_features' => 'The Enterprise plan adds support for multiple users and file attachments.', 'return_to_app' => 'Return to app', - + + 'update_font_cache' => 'Please force refresh the page to update the font cache.', + ); return $LANG; -?>. \ No newline at end of file +?>. diff --git a/resources/views/master.blade.php b/resources/views/master.blade.php index 19bb607ce612..3d4ee77e0cfb 100644 --- a/resources/views/master.blade.php +++ b/resources/views/master.blade.php @@ -48,7 +48,7 @@ } if (errorMsg.indexOf('No unicode cmap for font') > -1) { - alert("Please force refresh the page to update the font cache.\n\n - Windows: Ctrl + F5\n - Mac/Apple: Apple + R or Command + R\n - Linux: F5"); + alert("{{ trans('texts.update_font_cache') }}\n\n - Windows: Ctrl + F5\n - Mac/Apple: Apple + R or Command + R\n - Linux: F5"); } try { From d5669fdbf9f81e2c70a5ddf7e604faf9bc73532e Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 5 Jun 2016 21:08:41 +0300 Subject: [PATCH 072/111] Minor fixes --- app/Http/Controllers/AccountController.php | 6 +- .../Controllers/AccountGatewayController.php | 7 + app/Http/routes.php | 4 + resources/views/accounts/management.blade.php | 32 +++-- resources/views/header.blade.php | 128 +++++++++--------- 5 files changed, 97 insertions(+), 80 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 67bb78ca844a..57f581e3df11 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -206,7 +206,7 @@ class AccountController extends BaseController } } - if (!empty($new_plan)) { + if (!empty($new_plan) && $new_plan['plan'] != PLAN_FREE) { $time_used = $planDetails['paid']->diff(date_create()); $days_used = $time_used->days; @@ -1320,12 +1320,14 @@ class AccountController extends BaseController } $account = Auth::user()->account; + $invitation = $invoice->invitations->first(); // replace the variables with sample data $data = [ 'account' => $account, 'invoice' => $invoice, - 'invitation' => $invoice->invitations->first(), + 'invitation' => $invitation, + 'link' => $invitation->getLink(), 'client' => $invoice->client, 'amount' => $invoice->amount ]; diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index 0bc730129fe1..968fac50e6cd 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -39,6 +39,13 @@ class AccountGatewayController extends BaseController return $this->accountGatewayService->getDatatable(Auth::user()->account_id); } + public function show($publicId) + { + Session::reflash(); + + return Redirect::to("gateways/$publicId/edit"); + } + public function edit($publicId) { $accountGateway = AccountGateway::scope($publicId)->firstOrFail(); diff --git a/app/Http/routes.php b/app/Http/routes.php index f6574b9762c9..d9e4b2964b7e 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -309,6 +309,10 @@ Route::get('/forgot', function() { Route::get('/feed', function() { return Redirect::to(NINJA_WEB_URL.'/feed', 301); }); +Route::get('/comments/feed', function() { + return Redirect::to(NINJA_WEB_URL.'/comments/feed', 301); +}); + if (!defined('CONTACT_EMAIL')) { define('CONTACT_EMAIL', Config::get('mail.from.address')); diff --git a/resources/views/accounts/management.blade.php b/resources/views/accounts/management.blade.php index ac57cf737dd6..97ea6079df82 100644 --- a/resources/views/accounts/management.blade.php +++ b/resources/views/accounts/management.blade.php @@ -1,6 +1,6 @@ @extends('header') -@section('content') +@section('content') @parent @include('accounts.nav', ['selected' => ACCOUNT_MANAGEMENT]) @@ -134,10 +134,10 @@ @@ -165,12 +165,12 @@
     

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

      -  

    {!! Former::textarea('reason')->placeholder(trans('texts.reason_for_canceling'))->raw() !!}

      +  

    {!! Former::textarea('reason')->placeholder(trans('texts.reason_for_canceling'))->raw() !!}

     
    @@ -182,7 +182,7 @@ -@stop \ No newline at end of file +@stop diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 7b4fe014e71f..284bacba33f6 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -4,25 +4,25 @@ @section('head') - + @stop @@ -39,8 +39,8 @@ {!! DropdownButton::success(trans('texts.pay_now'))->withContents($paymentTypes)->large() !!} @else {{ trans('texts.pay_now') }} - @endif - @else + @endif + @else {!! Button::normal(trans('texts.download_pdf'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!} @endif @@ -67,7 +67,7 @@ @endif - + @if ($account->hasFeature(FEATURE_DOCUMENTS) && $account->invoice_embed_documents) @foreach ($invoice->documents as $document) @if($document->isPDFEmbeddable()) @@ -107,16 +107,16 @@ doc.getDataUrl(function(pdfString) { document.write(pdfString); document.close(); - + if (window.hasOwnProperty('pjsc_meta')) { window['pjsc_meta'].remainingTasks--; } }); - @else + @else refreshPDF(); @endif }); - + function onDownloadClick() { var doc = generatePDF(invoice, invoice.invoice_design.javascript, true); var fileName = invoice.is_quote ? invoiceLabels.quote : invoiceLabels.invoice; From 80202b6d92254c78d8489cda81f7d118cd415f0f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 6 Jun 2016 19:04:57 +0300 Subject: [PATCH 074/111] Added support for searching by invoice number in the API --- app/Http/Requests/InvoiceRequest.php | 16 +++++++++++++--- app/Ninja/Transformers/EntityTransformer.php | 10 +++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/Http/Requests/InvoiceRequest.php b/app/Http/Requests/InvoiceRequest.php index 5e2d93139003..edf43203c695 100644 --- a/app/Http/Requests/InvoiceRequest.php +++ b/app/Http/Requests/InvoiceRequest.php @@ -1,5 +1,7 @@ invoice_number && ! $invoice) { + $invoice = Invoice::scope() + ->whereInvoiceNumber($this->invoice_number) + ->withTrashed() + ->firstOrFail(); + } + // eager load the invoice items if ($invoice && ! $invoice->relationLoaded('invoice_items')) { $invoice->load('invoice_items'); } - + return $invoice; } -} \ No newline at end of file +} diff --git a/app/Ninja/Transformers/EntityTransformer.php b/app/Ninja/Transformers/EntityTransformer.php index d2d02bd0b6ee..691e6ff67f20 100644 --- a/app/Ninja/Transformers/EntityTransformer.php +++ b/app/Ninja/Transformers/EntityTransformer.php @@ -38,23 +38,23 @@ class EntityTransformer extends TransformerAbstract { return $date ? $date->getTimestamp() : null; } - + public function getDefaultIncludes() { return $this->defaultIncludes; } - + protected function getDefaults($entity) { $data = [ 'account_key' => $this->account->account_key, - 'is_owner' => (bool) Auth::user()->owns($entity), + 'is_owner' => (bool) (Auth::check() && Auth::user()->owns($entity)), ]; - + if ($entity->relationLoaded('user')) { $data['user_id'] = (int) $entity->user->public_id + 1; } - + return $data; } } From 1eb334f3b4fed4553c1ffa449f27d362984c8483 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 6 Jun 2016 22:18:15 +0300 Subject: [PATCH 075/111] Added back API route for quotes --- app/Http/Controllers/QuoteApiController.php | 52 +++++++++++++++++++++ app/Http/routes.php | 3 +- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 app/Http/Controllers/QuoteApiController.php diff --git a/app/Http/Controllers/QuoteApiController.php b/app/Http/Controllers/QuoteApiController.php new file mode 100644 index 000000000000..9acd645ed799 --- /dev/null +++ b/app/Http/Controllers/QuoteApiController.php @@ -0,0 +1,52 @@ +invoiceRepo = $invoiceRepo; + } + + /** + * @SWG\Get( + * path="/quotes", + * tags={"quote"}, + * summary="List of quotes", + * @SWG\Response( + * response=200, + * description="A list with quotes", + * @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Invoice")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + public function index() + { + $invoices = Invoice::scope() + ->withTrashed() + ->where('is_quote', '=', '1') + ->with('invoice_items', 'client') + ->orderBy('created_at', 'desc'); + + return $this->listResponse($invoices); + } + +} diff --git a/app/Http/routes.php b/app/Http/routes.php index d9e4b2964b7e..4955f9d159fe 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -251,8 +251,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::get('accounts', 'AccountApiController@show'); Route::put('accounts', 'AccountApiController@update'); Route::resource('clients', 'ClientApiController'); - //Route::get('quotes', 'QuoteApiController@index'); - //Route::resource('quotes', 'QuoteApiController'); + Route::get('quotes', 'QuoteApiController@index'); Route::get('invoices', 'InvoiceApiController@index'); Route::get('download/{invoice_id}', 'InvoiceApiController@download'); Route::resource('invoices', 'InvoiceApiController'); From 5489ade5ff7fec64603c129422aab602b979273a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 14:40:54 +0300 Subject: [PATCH 076/111] Fix for tests --- app/Ninja/Mailers/UserMailer.php | 8 ++++---- database/seeds/UserTableSeeder.php | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/Ninja/Mailers/UserMailer.php b/app/Ninja/Mailers/UserMailer.php index b08d50d7838f..0435a9af62d1 100644 --- a/app/Ninja/Mailers/UserMailer.php +++ b/app/Ninja/Mailers/UserMailer.php @@ -36,10 +36,10 @@ class UserMailer extends Mailer public function sendNotification(User $user, Invoice $invoice, $notificationType, Payment $payment = null) { - if (!$user->email) { + if (! $user->email || $user->cannot('view', $invoice)) { return; } - + $entityType = $invoice->getEntityType(); $view = ($notificationType == 'approved' ? ENTITY_QUOTE : ENTITY_INVOICE) . "_{$notificationType}"; $account = $user->account; @@ -64,7 +64,7 @@ class UserMailer extends Mailer 'invoice' => $invoice->invoice_number, 'client' => $client->getDisplayName() ]); - + $this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data); } @@ -88,7 +88,7 @@ class UserMailer extends Mailer 'contactName' => $invitation->contact->getDisplayName(), 'invoiceNumber' => $invoice->invoice_number, ]; - + $this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data); } } diff --git a/database/seeds/UserTableSeeder.php b/database/seeds/UserTableSeeder.php index dbaba660b75c..e86bbe4e1443 100644 --- a/database/seeds/UserTableSeeder.php +++ b/database/seeds/UserTableSeeder.php @@ -20,7 +20,7 @@ class UserTableSeeder extends Seeder $faker = Faker\Factory::create(); $company = Company::create(); - + $account = Account::create([ 'name' => $faker->name, 'address1' => $faker->streetAddress, @@ -28,7 +28,7 @@ class UserTableSeeder extends Seeder 'city' => $faker->city, 'state' => $faker->state, 'postal_code' => $faker->postcode, - 'country_id' => Country::all()->random()->id, + 'country_id' => Country::all()->random()->id, 'account_key' => str_random(RANDOM_KEY_LENGTH), 'invoice_terms' => $faker->text($faker->numberBetween(50, 300)), 'work_phone' => $faker->phoneNumber, @@ -50,12 +50,13 @@ class UserTableSeeder extends Seeder 'confirmed' => true, 'notify_sent' => false, 'notify_paid' => false, + 'is_admin' => 1, ]); Affiliate::create([ 'affiliate_key' => SELF_HOST_AFFILIATE_KEY ]); - + } -} \ No newline at end of file +} From db2e5f56425c186e2d908dedc5ee0fb83dcb32fe Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 15:15:46 +0300 Subject: [PATCH 077/111] Working on tests --- .travis.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f66f7aa29f7..b9ab5042baf4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,19 +64,19 @@ before_script: - curl -L http://ninja.dev:8000/update script: - - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php - php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance ExpenseCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance QuoteCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php - - php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance ExpenseCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance QuoteCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php + #- php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php #- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env #- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php @@ -96,4 +96,4 @@ after_script: notifications: email: on_success: never - on_failure: change \ No newline at end of file + on_failure: change From 40f18ed19f9418c3a36d7e08803b571f3fba48ef Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 16:16:25 +0300 Subject: [PATCH 078/111] Working on tests --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index b9ab5042baf4..5068eb042f5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -92,6 +92,8 @@ after_script: - mysql -u root -e 'select * from credits;' ninja - cat storage/logs/laravel-error.log - cat storage/logs/laravel-info.log + - FILES=$(find tests/_output -type f -name '*.png') + - for i in $FILES; do echo $i; base64 "$i"; echo "EOF"; done notifications: email: From bef4e5faeae7944a4ba42ce40c767ddee763b92a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 16:45:21 +0300 Subject: [PATCH 079/111] Fix for quote number prefix --- app/Models/Account.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Models/Account.php b/app/Models/Account.php index 7f6218462536..b86dcfeacd44 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -633,11 +633,11 @@ class Account extends Eloquent public function getNextInvoiceNumber($invoice) { - if ($this->hasNumberPattern($invoice->invoice_type_id)) { + if ($this->hasNumberPattern($invoice->is_quote)) { $number = $this->getNumberPattern($invoice); } else { - $counter = $this->getCounter($invoice->invoice_type_id); - $prefix = $this->getNumberPrefix($invoice->invoice_type_id); + $counter = $this->getCounter($invoice->is_quote); + $prefix = $this->getNumberPrefix($invoice->is_quote); $counterOffset = 0; // confirm the invoice number isn't already taken From 73ab6a308b3bdc2a881db4edc1ee8668e48e477d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 16:45:38 +0300 Subject: [PATCH 080/111] Only show last screenshot --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5068eb042f5d..d71b4345a9df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -93,7 +93,7 @@ after_script: - cat storage/logs/laravel-error.log - cat storage/logs/laravel-info.log - FILES=$(find tests/_output -type f -name '*.png') - - for i in $FILES; do echo $i; base64 "$i"; echo "EOF"; done + - for i in $FILES; do echo $i; base64 "$i"; break; done notifications: email: From a1f0461e02a0c1aec518694be24144b2d68d89ec Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 17:07:28 +0300 Subject: [PATCH 081/111] Working on tests --- tests/acceptance/CheckBalanceCest.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/acceptance/CheckBalanceCest.php b/tests/acceptance/CheckBalanceCest.php index 68cae45c3154..0cdb36362b36 100644 --- a/tests/acceptance/CheckBalanceCest.php +++ b/tests/acceptance/CheckBalanceCest.php @@ -17,7 +17,7 @@ class CheckBalanceCest public function checkBalance(AcceptanceTester $I) { $I->wantTo('ensure the balance is correct'); - + $clientEmail = $this->faker->safeEmail; $productKey = $this->faker->text(10); $productPrice = $this->faker->numberBetween(1, 20); @@ -30,7 +30,7 @@ class CheckBalanceCest $I->see($clientEmail); $clientId = $I->grabFromCurrentUrl('~clients/(\d+)~'); - + // create product $I->amOnPage('/products/create'); $I->fillField(['name' => 'product_key'], $productKey); @@ -39,7 +39,7 @@ class CheckBalanceCest $I->click('Save'); $I->wait(1); $I->see($productKey); - + // create invoice $I->amOnPage('/invoices/create'); $I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle'); @@ -51,7 +51,7 @@ class CheckBalanceCest $invoiceId = $I->grabFromCurrentUrl('~invoices/(\d+)~'); $I->amOnPage("/clients/{$clientId}"); $I->see('Balance $' . $productPrice); - + // update the invoice $I->amOnPage('/invoices/' . $invoiceId); $I->fillField(['name' => 'invoice_items[0][qty]'], 2); @@ -67,6 +67,7 @@ class CheckBalanceCest $I->see('Balance $0.00'); $I->see('Paid to Date $' . ($productPrice * 2)); + /* // archive the invoice $I->amOnPage('/invoices/' . $invoiceId); $I->executeJS('submitBulkAction("archive")'); @@ -74,7 +75,8 @@ class CheckBalanceCest $I->amOnPage("/clients/{$clientId}"); $I->see('Balance $0.00'); $I->see('Paid to Date $' . ($productPrice * 2)); - + */ + // delete the invoice $I->amOnPage('/invoices/' . $invoiceId); $I->executeJS('submitBulkAction("delete")'); @@ -91,4 +93,4 @@ class CheckBalanceCest $I->see('Balance $0.00'); $I->see('Paid to Date $' . ($productPrice * 2)); } -} \ No newline at end of file +} From 6b7542085aa0f6be8289a9bc39d9963d87e2a5c2 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 17:18:52 +0300 Subject: [PATCH 082/111] Working on tests --- tests/acceptance/CheckBalanceCest.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/acceptance/CheckBalanceCest.php b/tests/acceptance/CheckBalanceCest.php index 0cdb36362b36..6d37f01c98bc 100644 --- a/tests/acceptance/CheckBalanceCest.php +++ b/tests/acceptance/CheckBalanceCest.php @@ -67,18 +67,16 @@ class CheckBalanceCest $I->see('Balance $0.00'); $I->see('Paid to Date $' . ($productPrice * 2)); - /* // archive the invoice - $I->amOnPage('/invoices/' . $invoiceId); + $I->amOnPage('/invoices/' . $invoiceId . '/edit'); $I->executeJS('submitBulkAction("archive")'); $I->wait(1); $I->amOnPage("/clients/{$clientId}"); $I->see('Balance $0.00'); $I->see('Paid to Date $' . ($productPrice * 2)); - */ - + // delete the invoice - $I->amOnPage('/invoices/' . $invoiceId); + $I->amOnPage('/invoices/' . $invoiceId . '/edit'); $I->executeJS('submitBulkAction("delete")'); $I->wait(1); $I->amOnPage("/clients/{$clientId}"); @@ -86,7 +84,7 @@ class CheckBalanceCest $I->see('Paid to Date $0.00'); // restore the invoice - $I->amOnPage('/invoices/' . $invoiceId); + $I->amOnPage('/invoices/' . $invoiceId . '/edit'); $I->executeJS('submitBulkAction("restore")'); $I->wait(1); $I->amOnPage("/clients/{$clientId}"); From cc55cdc1b5f90623ff0472883d966e9f9cf2c503 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 17:29:26 +0300 Subject: [PATCH 083/111] Working on tests --- app/Http/Requests/InvoiceRequest.php | 4 +++- tests/acceptance/CheckBalanceCest.php | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/Http/Requests/InvoiceRequest.php b/app/Http/Requests/InvoiceRequest.php index edf43203c695..88366937e91f 100644 --- a/app/Http/Requests/InvoiceRequest.php +++ b/app/Http/Requests/InvoiceRequest.php @@ -10,6 +10,7 @@ class InvoiceRequest extends EntityRequest { { $invoice = parent::entity(); + /* // support loading an invoice by its invoice number if ($this->invoice_number && ! $invoice) { $invoice = Invoice::scope() @@ -17,7 +18,8 @@ class InvoiceRequest extends EntityRequest { ->withTrashed() ->firstOrFail(); } - + */ + // eager load the invoice items if ($invoice && ! $invoice->relationLoaded('invoice_items')) { $invoice->load('invoice_items'); diff --git a/tests/acceptance/CheckBalanceCest.php b/tests/acceptance/CheckBalanceCest.php index 6d37f01c98bc..2dfa8e7f35a8 100644 --- a/tests/acceptance/CheckBalanceCest.php +++ b/tests/acceptance/CheckBalanceCest.php @@ -68,7 +68,7 @@ class CheckBalanceCest $I->see('Paid to Date $' . ($productPrice * 2)); // archive the invoice - $I->amOnPage('/invoices/' . $invoiceId . '/edit'); + $I->amOnPage('/invoices/' . $invoiceId); $I->executeJS('submitBulkAction("archive")'); $I->wait(1); $I->amOnPage("/clients/{$clientId}"); @@ -76,7 +76,7 @@ class CheckBalanceCest $I->see('Paid to Date $' . ($productPrice * 2)); // delete the invoice - $I->amOnPage('/invoices/' . $invoiceId . '/edit'); + $I->amOnPage('/invoices/' . $invoiceId); $I->executeJS('submitBulkAction("delete")'); $I->wait(1); $I->amOnPage("/clients/{$clientId}"); @@ -84,7 +84,7 @@ class CheckBalanceCest $I->see('Paid to Date $0.00'); // restore the invoice - $I->amOnPage('/invoices/' . $invoiceId . '/edit'); + $I->amOnPage('/invoices/' . $invoiceId); $I->executeJS('submitBulkAction("restore")'); $I->wait(1); $I->amOnPage("/clients/{$clientId}"); From be11054b31c22747caab6bd472394b8de33253ab Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 17:39:18 +0300 Subject: [PATCH 084/111] Working on tests --- app/Http/Requests/EntityRequest.php | 14 +++++++------- app/Http/Requests/InvoiceRequest.php | 4 +--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/Http/Requests/EntityRequest.php b/app/Http/Requests/EntityRequest.php index 8679d3ca4195..87a877e4eae3 100644 --- a/app/Http/Requests/EntityRequest.php +++ b/app/Http/Requests/EntityRequest.php @@ -9,7 +9,7 @@ class EntityRequest extends Request { protected $entityType; private $entity; - public function entity() + public function entity() { if ($this->entity) { return $this->entity; @@ -20,24 +20,24 @@ class EntityRequest extends Request { foreach (['_id', 's'] as $suffix) { $field = $this->entityType . $suffix; if ($this->$field) { - $publicId= $this->$field; - } + $publicId= $this->$field; + } } if ( ! $publicId) { $publicId = Input::get('public_id') ?: Input::get('id'); } if ( ! $publicId) { return null; - } - + } + $class = Utils::getEntityClass($this->entityType); - + \Log::info('entity ' . $this->entityType . ' - ' . $publicId); if (method_exists($class, 'withTrashed')) { $this->entity = $class::scope($publicId)->withTrashed()->firstOrFail(); } else { $this->entity = $class::scope($publicId)->firstOrFail(); } - + \Log::info($this->entity); return $this->entity; } diff --git a/app/Http/Requests/InvoiceRequest.php b/app/Http/Requests/InvoiceRequest.php index 88366937e91f..edf43203c695 100644 --- a/app/Http/Requests/InvoiceRequest.php +++ b/app/Http/Requests/InvoiceRequest.php @@ -10,7 +10,6 @@ class InvoiceRequest extends EntityRequest { { $invoice = parent::entity(); - /* // support loading an invoice by its invoice number if ($this->invoice_number && ! $invoice) { $invoice = Invoice::scope() @@ -18,8 +17,7 @@ class InvoiceRequest extends EntityRequest { ->withTrashed() ->firstOrFail(); } - */ - + // eager load the invoice items if ($invoice && ! $invoice->relationLoaded('invoice_items')) { $invoice->load('invoice_items'); From 7e1a0634f5efa17ef9d7aa474a48d81efa83ce99 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 17:55:52 +0300 Subject: [PATCH 085/111] Working on tests --- app/Http/Requests/EntityRequest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Requests/EntityRequest.php b/app/Http/Requests/EntityRequest.php index 87a877e4eae3..d379578c41eb 100644 --- a/app/Http/Requests/EntityRequest.php +++ b/app/Http/Requests/EntityRequest.php @@ -33,8 +33,10 @@ class EntityRequest extends Request { $class = Utils::getEntityClass($this->entityType); \Log::info('entity ' . $this->entityType . ' - ' . $publicId); if (method_exists($class, 'withTrashed')) { + \Log::info('has withTrashed') $this->entity = $class::scope($publicId)->withTrashed()->firstOrFail(); } else { + \Log::info('does not have withTrashed') $this->entity = $class::scope($publicId)->firstOrFail(); } \Log::info($this->entity); From 2a109cbfbc192e1f4407661fe66bb05cb9106bed Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 18:00:30 +0300 Subject: [PATCH 086/111] Removing log messages --- app/Http/Requests/EntityRequest.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/Http/Requests/EntityRequest.php b/app/Http/Requests/EntityRequest.php index d379578c41eb..e1daf5c68d69 100644 --- a/app/Http/Requests/EntityRequest.php +++ b/app/Http/Requests/EntityRequest.php @@ -31,15 +31,13 @@ class EntityRequest extends Request { } $class = Utils::getEntityClass($this->entityType); - \Log::info('entity ' . $this->entityType . ' - ' . $publicId); + if (method_exists($class, 'withTrashed')) { - \Log::info('has withTrashed') $this->entity = $class::scope($publicId)->withTrashed()->firstOrFail(); } else { - \Log::info('does not have withTrashed') $this->entity = $class::scope($publicId)->firstOrFail(); } - \Log::info($this->entity); + return $this->entity; } From 1d3efda4ea859545b0d5327101708f8b808587d6 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 21:13:47 +0300 Subject: [PATCH 087/111] Fix for tests --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d71b4345a9df..b27c9d02af7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,9 +64,9 @@ before_script: - curl -L http://ninja.dev:8000/update script: - #- php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php - #- php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php - - php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php + - php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php #- php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php #- php ./vendor/codeception/codeception/codecept run --debug acceptance ExpenseCest.php #- php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php From 13fbb880e1080d79a887403c6bccf2a76f8fef66 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 21:49:20 +0300 Subject: [PATCH 088/111] Fix for invoice report totals --- app/Http/Controllers/ReportController.php | 30 +++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 4f162f260d37..349cda291817 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -299,7 +299,7 @@ class ReportController extends BaseController $account->formatMoney($tax['paid'], $client) ]; } - + $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $tax['amount']); $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $tax['paid']); } @@ -320,7 +320,7 @@ class ReportController extends BaseController $account = Auth::user()->account; $displayData = []; $reportTotals = []; - + $payments = Payment::scope() ->withTrashed() ->where('is_deleted', '=', false) @@ -350,7 +350,7 @@ class ReportController extends BaseController $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment->amount); } - + return [ 'columns' => $columns, 'displayData' => $displayData, @@ -361,11 +361,11 @@ class ReportController extends BaseController private function generateInvoiceReport($startDate, $endDate, $isExport) { $columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method']; - + $account = Auth::user()->account; $displayData = []; $reportTotals = []; - + $clients = Client::scope() ->withTrashed() ->with('contacts') @@ -383,10 +383,10 @@ class ReportController extends BaseController }, 'invoice_items']) ->withTrashed(); }]); - + foreach ($clients->get() as $client) { - foreach ($client->invoices as $invoice) { - + foreach ($client->invoices as $invoice) { + $payments = count($invoice->payments) ? $invoice->payments : [false]; foreach ($payments as $payment) { $displayData[] = [ @@ -397,10 +397,8 @@ class ReportController extends BaseController $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); - } + ]; + $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'paid', $payment ? $payment->amount : 0); } $reportTotals = $this->addToTotals($reportTotals, $client->currency_id, 'amount', $invoice->amount); @@ -491,7 +489,7 @@ class ReportController extends BaseController $reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'amount', $amount); $reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'amount', 0); - + $reportTotals = $this->addToTotals($reportTotals, $expense->invoice_currency_id, 'invoiced', $invoiced); $reportTotals = $this->addToTotals($reportTotals, $expense->expense_currency_id, 'invoiced', 0); } @@ -518,14 +516,14 @@ class ReportController extends BaseController private function export($reportType, $data, $columns, $totals) { $output = fopen('php://output', 'w') or Utils::fatalError(); - $reportType = trans("texts.{$reportType}s"); + $reportType = trans("texts.{$reportType}s"); $date = date('Y-m-d'); - + header('Content-Type:application/csv'); header("Content-Disposition:attachment;filename={$date}_Ninja_{$reportType}.csv"); Utils::exportData($output, $data, Utils::trans($columns)); - + fwrite($output, trans('texts.totals')); foreach ($totals as $currencyId => $fields) { foreach ($fields as $key => $value) { From 9ddb2f5680377b4715a66c522db1bd5c9c8a3a84 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 22:01:21 +0300 Subject: [PATCH 089/111] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc8085eec9c0..e8ea86167065 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=master)](https://travis-ci.org/invoiceninja/invoiceninja) [![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -**To update the translations please use [Transifex](https://www.transifex.com/invoice-ninja/invoice-ninja/dashboard/)** +We're often asked to recommend PHP developers, email us at contact@invoiceninja.com if you'd like us to consider you for the work. ### Affiliates Programs * Referral program (we pay you): $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/) From d9af83fd546776d95bb7cdb093045f10dbc90ea0 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 7 Jun 2016 22:03:07 +0300 Subject: [PATCH 090/111] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8ea86167065..79024cd8c173 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=master)](https://travis-ci.org/invoiceninja/invoiceninja) [![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -We're often asked to recommend PHP developers, email us at contact@invoiceninja.com if you'd like us to consider you for the work. +We're often asked to recommend PHP developers to help setup our app and make small adjustments, email us at contact@invoiceninja.com if you're interested in taking on the work. ### Affiliates Programs * Referral program (we pay you): $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/) From 44d46a01ab3a549c58c3361e78b25d247ce1e485 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 8 Jun 2016 08:11:48 +0300 Subject: [PATCH 091/111] Always load user even if they're deleted --- app/Models/AccountToken.php | 2 +- app/Models/Contact.php | 2 +- app/Models/Credit.php | 8 ++++---- app/Models/Document.php | 2 +- app/Models/Expense.php | 8 ++++---- app/Models/Task.php | 4 ++-- app/Models/Vendor.php | 2 +- app/Models/VendorContact.php | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/Models/AccountToken.php b/app/Models/AccountToken.php index 87728b37016e..55781aabb95e 100644 --- a/app/Models/AccountToken.php +++ b/app/Models/AccountToken.php @@ -19,6 +19,6 @@ class AccountToken extends EntityModel public function user() { - return $this->belongsTo('App\Models\User'); + return $this->belongsTo('App\Models\User')->withTrashed(); } } diff --git a/app/Models/Contact.php b/app/Models/Contact.php index 9c86c4ce5b84..54610e93f049 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -33,7 +33,7 @@ class Contact extends EntityModel implements AuthenticatableContract, CanResetPa public function user() { - return $this->belongsTo('App\Models\User'); + return $this->belongsTo('App\Models\User')->withTrashed(); } public function client() diff --git a/app/Models/Credit.php b/app/Models/Credit.php index c46095e80ee3..340417e7d424 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -8,7 +8,7 @@ class Credit extends EntityModel { use SoftDeletes; use PresentableTrait; - + protected $dates = ['deleted_at']; protected $presenter = 'App\Ninja\Presenters\CreditPresenter'; @@ -19,7 +19,7 @@ class Credit extends EntityModel public function user() { - return $this->belongsTo('App\Models\User'); + return $this->belongsTo('App\Models\User')->withTrashed(); } public function invoice() @@ -59,9 +59,9 @@ class Credit extends EntityModel } Credit::creating(function ($credit) { - + }); Credit::created(function ($credit) { event(new CreditWasCreated($credit)); -}); \ No newline at end of file +}); diff --git a/app/Models/Document.php b/app/Models/Document.php index f1d6d6b9c5bd..efa9426e9581 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -86,7 +86,7 @@ class Document extends EntityModel public function user() { - return $this->belongsTo('App\Models\User'); + return $this->belongsTo('App\Models\User')->withTrashed(); } public function expense() diff --git a/app/Models/Expense.php b/app/Models/Expense.php index 316491a5356b..ccb4ccb684ac 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -35,7 +35,7 @@ class Expense extends EntityModel public function user() { - return $this->belongsTo('App\Models\User'); + return $this->belongsTo('App\Models\User')->withTrashed(); } public function vendor() @@ -90,13 +90,13 @@ class Expense extends EntityModel { return round($this->amount * $this->exchange_rate, 2); } - + public function toArray() { $array = parent::toArray(); - + if(empty($this->visible) || in_array('converted_amount', $this->visible))$array['converted_amount'] = $this->convertedAmount(); - + return $array; } } diff --git a/app/Models/Task.php b/app/Models/Task.php index 17b667558c49..3aa2f8b2f8d7 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -24,7 +24,7 @@ class Task extends EntityModel public function user() { - return $this->belongsTo('App\Models\User'); + return $this->belongsTo('App\Models\User')->withTrashed(); } public function client() @@ -91,4 +91,4 @@ class Task extends EntityModel { return round($this->getDuration() / (60 * 60), 2); } -} \ No newline at end of file +} diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 6fcd10f14092..d537b1ca9d00 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -87,7 +87,7 @@ class Vendor extends EntityModel public function user() { - return $this->belongsTo('App\Models\User'); + return $this->belongsTo('App\Models\User')->withTrashed(); } public function payments() diff --git a/app/Models/VendorContact.php b/app/Models/VendorContact.php index 5546b27d2adb..6b2ad12c8025 100644 --- a/app/Models/VendorContact.php +++ b/app/Models/VendorContact.php @@ -9,7 +9,7 @@ class VendorContact extends EntityModel use SoftDeletes; protected $dates = ['deleted_at']; protected $table = 'vendor_contacts'; - + protected $fillable = [ 'first_name', 'last_name', @@ -30,7 +30,7 @@ class VendorContact extends EntityModel public function user() { - return $this->belongsTo('App\Models\User'); + return $this->belongsTo('App\Models\User')->withTrashed(); } public function vendor() From 956c672711a0d0947339d7930bfb2e426cdf8785 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 8 Jun 2016 08:44:12 +0300 Subject: [PATCH 092/111] Fix payments for clients w/o an email address --- resources/views/payments/payment.blade.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/resources/views/payments/payment.blade.php b/resources/views/payments/payment.blade.php index 556695b69460..49445dcb20a9 100644 --- a/resources/views/payments/payment.blade.php +++ b/resources/views/payments/payment.blade.php @@ -179,15 +179,16 @@ ->label('') !!} -
    -
    - {!! Former::text('email') - ->placeholder(trans('texts.email')) - ->autocomplete('email') - ->label('') !!} + @if (isset($paymentTitle) || ! empty($contact->email)) +
    +
    + {!! Former::text('email') + ->placeholder(trans('texts.email')) + ->autocomplete('email') + ->label('') !!} +
    -
    - + @endif

     
     

    @if ($showAddress) From 08c01f59d6edd17e1e77be7074845598feac9743 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 8 Jun 2016 16:06:48 +0300 Subject: [PATCH 093/111] Fix for analytics --- app/Listeners/AnalyticsListener.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/Listeners/AnalyticsListener.php b/app/Listeners/AnalyticsListener.php index 92c81205ab79..dd574368c462 100644 --- a/app/Listeners/AnalyticsListener.php +++ b/app/Listeners/AnalyticsListener.php @@ -11,30 +11,30 @@ class AnalyticsListener if ( ! Utils::isNinja() || ! env('ANALYTICS_KEY')) { return; } - + $payment = $event->payment; $invoice = $payment->invoice; $account = $payment->account; - + if ($account->account_key != NINJA_ACCOUNT_KEY) { return; } - + $analyticsId = env('ANALYTICS_KEY'); $client = $payment->client; $amount = $payment->amount; - - $base = "v=1&tid={$analyticsId}&cid{$client->public_id}&cu=USD&ti={$invoice->invoice_number}"; - - $url = $base . "&t=transaction&ta=ninja&tr={$amount}"; + + $base = "v=1&tid={$analyticsId}&cid={$client->public_id}&cu=USD&ti={$invoice->invoice_number}"; + + $url = $base . "&t=transaction&ta=ninja&tr={$amount}"; $this->sendAnalytics($url); //Log::info($url); - $url = $base . "&t=item&in=plan&ip={$amount}&iq=1"; + $url = $base . "&t=item&in=plan&ip={$amount}&iq=1"; $this->sendAnalytics($url); //Log::info($url); } - + private function sendAnalytics($data) { $data = json_encode($data); From 3c9e2769fb76852f77bbff6b446a9117f06a02b5 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 8 Jun 2016 17:21:02 +0300 Subject: [PATCH 094/111] Merge fix for outstanding balance on dashboard --- app/Http/Controllers/DashboardController.php | 41 +++++++++++--------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 5daa8827883a..718d73c67d42 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -13,7 +13,7 @@ class DashboardController extends BaseController { $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, @@ -27,17 +27,17 @@ class DashboardController extends BaseController ->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(); @@ -47,11 +47,11 @@ class DashboardController extends BaseController ->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(); @@ -66,11 +66,11 @@ class DashboardController extends BaseController ->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(); @@ -82,16 +82,21 @@ class DashboardController extends BaseController ->where('accounts.id', '=', Auth::user()->account_id) ->where('clients.is_deleted', '=', false) ->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(); + ->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')); + + if (!$view_all) { + $balances->where('clients.user_id', '=', $user_id); + } + + $balances = $balances->get(); $activities = Activity::where('activities.account_id', '=', Auth::user()->account_id) ->where('activities.activity_type_id', '>', 0); - + if(!$view_all){ $activities = $activities->where('activities.user_id', '=', $user_id); } - + $activities = $activities->orderBy('activities.created_at', 'desc') ->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account') ->take(50) @@ -111,11 +116,11 @@ class DashboardController extends BaseController ->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) @@ -136,11 +141,11 @@ class DashboardController extends BaseController ->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(); @@ -155,11 +160,11 @@ class DashboardController extends BaseController ->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) From 40241cbf384af9f20edc69dfd5a411dab17780f4 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 8 Jun 2016 17:56:48 +0300 Subject: [PATCH 095/111] Restrict client list --- app/Http/Controllers/CreditController.php | 8 ++++---- app/Http/Controllers/PaymentController.php | 11 ++++++----- app/Http/Controllers/TaskController.php | 12 ++++++------ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index c4250903fd32..ff1257d4456c 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -64,7 +64,7 @@ class CreditController extends BaseController 'method' => 'POST', 'url' => 'credits', 'title' => trans('texts.new_credit'), - 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clients' => Client::scope()->viewable()->with('contacts')->orderBy('name')->get(), ); return View::make('credits.edit', $data); @@ -74,9 +74,9 @@ class CreditController extends BaseController public function edit($publicId) { $credit = Credit::scope($publicId)->firstOrFail(); - + $this->authorize('edit', $credit); - + $credit->credit_date = Utils::fromSqlDate($credit->credit_date); $data = array( @@ -90,7 +90,7 @@ class CreditController extends BaseController return View::make('credit.edit', $data); } */ - + public function store(CreateCreditRequest $request) { $credit = $this->creditRepo->save($request->input()); diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 0b93d45374b0..2054b65bafcb 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -32,7 +32,7 @@ use App\Http\Requests\UpdatePaymentRequest; class PaymentController extends BaseController { protected $entityType = ENTITY_PAYMENT; - + public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService) { // parent::__construct(); @@ -71,6 +71,7 @@ class PaymentController extends BaseController public function create(PaymentRequest $request) { $invoices = Invoice::scope() + ->viewable() ->where('is_recurring', '=', false) ->where('is_quote', '=', false) ->where('invoices.balance', '>', 0) @@ -88,7 +89,7 @@ class PaymentController extends BaseController 'title' => trans('texts.new_payment'), 'paymentTypes' => Cache::get('paymentTypes'), 'paymentTypeId' => Input::get('paymentTypeId'), - 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), ); + 'clients' => Client::scope()->viewable()->with('contacts')->orderBy('name')->get(), ); return View::make('payments.edit', $data); } @@ -96,7 +97,7 @@ class PaymentController extends BaseController public function edit(PaymentRequest $request) { $payment = $request->entity(); - + $payment->payment_date = Utils::fromSqlDate($payment->payment_date); $data = array( @@ -565,7 +566,7 @@ class PaymentController extends BaseController Session::flash('error', $message); } return Redirect::to($invitation->getLink()); - } elseif (method_exists($gateway, 'completePurchase') + } elseif (method_exists($gateway, 'completePurchase') && !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT) && !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) { $details = $this->paymentService->getPaymentDetails($invitation, $accountGateway); @@ -597,7 +598,7 @@ class PaymentController extends BaseController public function store(CreatePaymentRequest $request) { $input = $request->input(); - + $input['invoice_id'] = Invoice::getPrivateId($input['invoice']); $input['client_id'] = Client::getPrivateId($input['client']); $payment = $this->paymentRepo->save($input); diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 229a4751116e..8eb90df15944 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -117,7 +117,7 @@ class TaskController extends BaseController $this->checkTimezone(); $task = $request->entity(); - + $actions = []; if ($task->invoice) { $actions[] = ['url' => URL::to("invoices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")]; @@ -167,14 +167,14 @@ class TaskController extends BaseController public function update(UpdateTaskRequest $request) { $task = $request->entity(); - + return $this->save($task->public_id); } private static function getViewModel() { return [ - 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clients' => Client::scope()->viewable()->with('contacts')->orderBy('name')->get(), 'account' => Auth::user()->account, ]; } @@ -182,7 +182,7 @@ class TaskController extends BaseController private function save($publicId = null) { $action = Input::get('action'); - + if (in_array($action, ['archive', 'delete', 'restore'])) { return self::bulk(); } @@ -210,7 +210,7 @@ class TaskController extends BaseController $tasks = Task::scope($ids)->with('client')->get(); $clientPublicId = false; $data = []; - + foreach ($tasks as $task) { if ($task->client) { if (!$clientPublicId) { @@ -228,7 +228,7 @@ class TaskController extends BaseController Session::flash('error', trans('texts.task_error_invoiced')); return Redirect::to('tasks'); } - + $account = Auth::user()->account; $data[] = [ 'publicId' => $task->public_id, From fc0a7cfbf01c92cc561515e1fa020227bc34733e Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 21 Jun 2016 21:02:01 +0300 Subject: [PATCH 096/111] Merge fixes from develop --- app/Http/Controllers/ClientApiController.php | 12 ++-- app/Models/Invoice.php | 62 ++++++++++---------- resources/views/expenses/edit.blade.php | 4 +- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/app/Http/Controllers/ClientApiController.php b/app/Http/Controllers/ClientApiController.php index dd82e9116131..2b135b70438b 100644 --- a/app/Http/Controllers/ClientApiController.php +++ b/app/Http/Controllers/ClientApiController.php @@ -53,7 +53,7 @@ class ClientApiController extends BaseAPIController $query->where('email', $email); }); } - + return $this->listResponse($clients); } @@ -112,11 +112,13 @@ class ClientApiController extends BaseAPIController if ($request->action) { return $this->handleAction($request); } - + $data = $request->input(); $data['public_id'] = $publicId; $client = $this->clientRepo->save($data, $request->entity()); + $client->load(['contacts']); + return $this->itemResponse($client); } @@ -146,10 +148,10 @@ class ClientApiController extends BaseAPIController public function destroy(UpdateClientRequest $request) { $client = $request->entity(); - + $this->clientRepo->delete($client); return $this->itemResponse($client); } - -} \ No newline at end of file + +} diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 5c006ecd9eaa..db0cae513f37 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -29,9 +29,9 @@ class Invoice extends EntityModel implements BalanceAffecting 'tax_name1', 'tax_rate1', 'tax_name2', - 'tax_rate2', + 'tax_rate2', ]; - + protected $casts = [ 'is_recurring' => 'boolean', 'has_tasks' => 'boolean', @@ -515,12 +515,12 @@ class Invoice extends EntityModel implements BalanceAffecting 'name', ]); } - + foreach ($this->expenses as $expense) { $expense->setVisible([ 'documents', ]); - + foreach ($expense->documents as $document) { $document->setVisible([ 'public_id', @@ -579,12 +579,12 @@ class Invoice extends EntityModel implements BalanceAffecting return $schedule[1]->getStart(); } - + public function getDueDate($invoice_date = null){ if(!$this->is_recurring) { return $this->due_date ? $this->due_date : null; } - else{ + else{ $now = time(); if($invoice_date) { // If $invoice_date is specified, all calculations are based on that date @@ -598,7 +598,7 @@ class Invoice extends EntityModel implements BalanceAffecting $now = $invoice_date->getTimestamp(); } } - + if($this->due_date && $this->due_date != '0000-00-00'){ // This is a recurring invoice; we're using a custom format here. // The year is always 1998; January is 1st, 2nd, last day of the month. @@ -607,7 +607,7 @@ class Invoice extends EntityModel implements BalanceAffecting $monthVal = (int)date('n', $dueDateVal); $dayVal = (int)date('j', $dueDateVal); $dueDate = false; - + if($monthVal == 1) {// January; day of month $currentDay = (int)date('j', $now); $lastDayOfMonth = (int)date('t', $now); @@ -634,7 +634,7 @@ class Invoice extends EntityModel implements BalanceAffecting if($dueDay > $lastDayOfMonth){ // No later than the end of the month $dueDay = $lastDayOfMonth; - } + } } $dueDate = mktime(0, 0, 0, $dueMonth, $dueDay, $dueYear); @@ -663,7 +663,7 @@ class Invoice extends EntityModel implements BalanceAffecting return date('Y-m-d', strtotime('+'.$days.' day', $now)); } } - + // Couldn't calculate one return null; } @@ -681,11 +681,11 @@ class Invoice extends EntityModel implements BalanceAffecting $dateStart = $date->getStart(); $date = $this->account->formatDate($dateStart); $dueDate = $this->getDueDate($dateStart); - + if($dueDate) { $date .= ' (' . trans('texts.due') . ' ' . $this->account->formatDate($dueDate) . ')'; } - + $dates[] = $date; } @@ -799,16 +799,16 @@ class Invoice extends EntityModel implements BalanceAffecting $invitation = $this->invitations[0]; $link = $invitation->getLink('view', true); $key = env('PHANTOMJS_CLOUD_KEY'); - + if (Utils::isNinjaDev()) { $link = env('TEST_LINK'); } $url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D"; - + $pdfString = file_get_contents($url); $pdfString = strip_tags($pdfString); - + if ( ! $pdfString || strlen($pdfString) < 200) { Utils::logError("PhantomJSCloud - failed to create pdf: {$pdfString}"); return false; @@ -861,55 +861,55 @@ class Invoice extends EntityModel implements BalanceAffecting return $total; } - // if $calculatePaid is true we'll loop through each payment to + // if $calculatePaid is true we'll loop through each payment to // determine the sum, otherwise we'll use the cached paid_to_date amount public function getTaxes($calculatePaid = false) { $taxes = []; $taxable = $this->getTaxable(); $paidAmount = $this->getAmountPaid($calculatePaid); - + if ($this->tax_name1) { $invoiceTaxAmount = round($taxable * ($this->tax_rate1 / 100), 2); - $invoicePaidAmount = $this->amount && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0; + $invoicePaidAmount = floatVal($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0; $this->calculateTax($taxes, $this->tax_name1, $this->tax_rate1, $invoiceTaxAmount, $invoicePaidAmount); } if ($this->tax_name2) { $invoiceTaxAmount = round($taxable * ($this->tax_rate2 / 100), 2); - $invoicePaidAmount = $this->amount && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0; + $invoicePaidAmount = floatVal($this->amount) && $invoiceTaxAmount ? ($paidAmount / $this->amount * $invoiceTaxAmount) : 0; $this->calculateTax($taxes, $this->tax_name2, $this->tax_rate2, $invoiceTaxAmount, $invoicePaidAmount); } foreach ($this->invoice_items as $invoiceItem) { $itemTaxAmount = $this->getItemTaxable($invoiceItem, $taxable); - + if ($invoiceItem->tax_name1) { $itemTaxAmount = round($taxable * ($invoiceItem->tax_rate1 / 100), 2); - $itemPaidAmount = $this->amount && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0; + $itemPaidAmount = floatVal($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0; $this->calculateTax($taxes, $invoiceItem->tax_name1, $invoiceItem->tax_rate1, $itemTaxAmount, $itemPaidAmount); } if ($invoiceItem->tax_name2) { $itemTaxAmount = round($taxable * ($invoiceItem->tax_rate2 / 100), 2); - $itemPaidAmount = $this->amount && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0; + $itemPaidAmount = floatVal($this->amount) && $itemTaxAmount ? ($paidAmount / $this->amount * $itemTaxAmount) : 0; $this->calculateTax($taxes, $invoiceItem->tax_name2, $invoiceItem->tax_rate2, $itemTaxAmount, $itemPaidAmount); } } - + return $taxes; } - private function calculateTax(&$taxes, $name, $rate, $amount, $paid) - { + private function calculateTax(&$taxes, $name, $rate, $amount, $paid) + { if ( ! $amount) { return; - } - + } + $amount = round($amount, 2); $paid = round($paid, 2); $key = $rate . ' ' . $name; - + if ( ! isset($taxes[$key])) { $taxes[$key] = [ 'name' => $name, @@ -920,14 +920,14 @@ class Invoice extends EntityModel implements BalanceAffecting } $taxes[$key]['amount'] += $amount; - $taxes[$key]['paid'] += $paid; + $taxes[$key]['paid'] += $paid; } - + public function hasDocuments(){ if(count($this->documents))return true; return $this->hasExpenseDocuments(); } - + public function hasExpenseDocuments(){ foreach($this->expenses as $expense){ if(count($expense->documents))return true; diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 0f1175d9f971..f2ee2d872ab3 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -108,7 +108,7 @@
    - @if ($account->isPro()) + @if ($account->hasFeature(FEATURE_DOCUMENTS))
    {{trans('texts.expense_documents')}}
    @@ -419,7 +419,7 @@ function handleDocumentError() { window.countUploadingDocuments--; } - + @stop From e36d23fcdcae9ca3247d4143c506d66eb0b987cd Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 23 Jun 2016 12:39:07 +0300 Subject: [PATCH 097/111] API changes for Zapier --- app/Http/Controllers/BaseAPIController.php | 43 ++++++++++++------- app/Http/Controllers/InvoiceApiController.php | 13 +++--- app/Ninja/Transformers/PaymentTransformer.php | 5 ++- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/app/Http/Controllers/BaseAPIController.php b/app/Http/Controllers/BaseAPIController.php index 0718e95b91e4..4531afc2525e 100644 --- a/app/Http/Controllers/BaseAPIController.php +++ b/app/Http/Controllers/BaseAPIController.php @@ -61,40 +61,40 @@ class BaseAPIController extends Controller } $this->serializer = Request::get('serializer') ?: API_SERIALIZER_ARRAY; - + if ($this->serializer === API_SERIALIZER_JSON) { $this->manager->setSerializer(new JsonApiSerializer()); } else { $this->manager->setSerializer(new ArraySerializer()); } - + if (Utils::isNinjaDev()) { \DB::enableQueryLog(); } } protected function handleAction($request) - { + { $entity = $request->entity(); $action = $request->action; - + $repo = Utils::toCamelCase($this->entityType) . 'Repo'; - + $this->$repo->$action($entity); - + return $this->itemResponse($entity); } protected function listResponse($query) { $transformerClass = EntityModel::getTransformerName($this->entityType); - $transformer = new $transformerClass(Auth::user()->account, Input::get('serializer')); + $transformer = new $transformerClass(Auth::user()->account, Input::get('serializer')); $includes = $transformer->getDefaultIncludes(); $includes = $this->getRequestIncludes($includes); $query->with($includes); - + if ($updatedAt = Input::get('updated_at')) { $updatedAt = date('Y-m-d H:i:s', $updatedAt); $query->where(function($query) use ($includes, $updatedAt) { @@ -106,14 +106,14 @@ class BaseAPIController extends Controller } }); } - + if ($clientPublicId = Input::get('client_id')) { $filter = function($query) use ($clientPublicId) { $query->where('public_id', '=', $clientPublicId); }; $query->whereHas('client', $filter); } - + if ( ! Utils::hasPermission('view_all')){ if ($this->entityType == ENTITY_USER) { $query->where('id', '=', Auth::user()->id); @@ -121,7 +121,7 @@ class BaseAPIController extends Controller $query->where('user_id', '=', Auth::user()->id); } } - + $data = $this->createCollection($query, $transformer, $this->entityType); return $this->response($data); @@ -130,10 +130,10 @@ class BaseAPIController extends Controller protected function itemResponse($item) { $transformerClass = EntityModel::getTransformerName($this->entityType); - $transformer = new $transformerClass(Auth::user()->account, Input::get('serializer')); + $transformer = new $transformerClass(Auth::user()->account, Input::get('serializer')); $data = $this->createItem($item, $transformer, $this->entityType); - + return $this->response($data); } @@ -160,7 +160,7 @@ class BaseAPIController extends Controller } else { $resource = new Collection($query, $transformer, $entityType); } - + return $this->manager->createData($resource)->toArray(); } @@ -171,7 +171,7 @@ class BaseAPIController extends Controller Log::info(Request::method() . ' - ' . Request::url() . ": $count queries"); Log::info(json_encode(\DB::getQueryLog())); } - + $index = Request::get('index') ?: 'data'; if ($index == 'none') { @@ -222,7 +222,18 @@ class BaseAPIController extends Controller $data[] = $include; } } - + + return $data; + } + + protected function fileReponse($name, $data) + { + header('Content-Type: application/pdf'); + header('Content-Length: ' . strlen($data)); + header('Content-disposition: attachment; filename="' . $name . '"'); + header('Cache-Control: public, must-revalidate, max-age=0'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + return $data; } } diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index 6d68573cdb42..a9b819cd1e0b 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -184,6 +184,10 @@ class InvoiceApiController extends BaseAPIController ->with('client', 'invoice_items', 'invitations') ->first(); + if (isset($data['download_invoice']) && boolval($data['download_invoice'])) { + return $this->fileReponse($invoice->getFileName(), $invoice->getPDFString()); + } + return $this->itemResponse($invoice); } @@ -361,14 +365,7 @@ class InvoiceApiController extends BaseAPIController public function download(InvoiceRequest $request) { $invoice = $request->entity(); - $pdfString = $invoice->getPDFString(); - header('Content-Type: application/pdf'); - header('Content-Length: ' . strlen($pdfString)); - header('Content-disposition: attachment; filename="' . $invoice->getFileName() . '"'); - header('Cache-Control: public, must-revalidate, max-age=0'); - header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); - - return $pdfString; + return $this->fileReponse($invoice->getFileName(), $invoice->getPDFString()); } } diff --git a/app/Ninja/Transformers/PaymentTransformer.php b/app/Ninja/Transformers/PaymentTransformer.php index c4e4328cb845..9f3ef3212863 100644 --- a/app/Ninja/Transformers/PaymentTransformer.php +++ b/app/Ninja/Transformers/PaymentTransformer.php @@ -29,7 +29,7 @@ class PaymentTransformer extends EntityTransformer public function __construct($account = null, $serializer = null, $invoice = null) { parent::__construct($account, $serializer); - + $this->invoice = $invoice; } @@ -57,6 +57,7 @@ class PaymentTransformer extends EntityTransformer 'is_deleted' => (bool) $payment->is_deleted, 'payment_type_id' => (int) $payment->payment_type_id, 'invoice_id' => (int) ($this->invoice ? $this->invoice->public_id : $payment->invoice->public_id), + 'invoice_number' => $this->invoice ? $this->invoice->invoice_number : $payment->invoice->invoice_number, ]); } -} \ No newline at end of file +} From 1fc1c11663626a397156fb2e03b73454fd585e13 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 23 Jun 2016 12:58:28 +0300 Subject: [PATCH 098/111] API changes for Zapier --- app/Http/Controllers/InvoiceApiController.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index a9b819cd1e0b..cdaea65af93a 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -22,6 +22,7 @@ use App\Http\Requests\InvoiceRequest; use App\Http\Requests\CreateInvoiceAPIRequest; use App\Http\Requests\UpdateInvoiceAPIRequest; use App\Services\InvoiceService; +use App\Services\PaymentService; class InvoiceApiController extends BaseAPIController { @@ -29,7 +30,7 @@ class InvoiceApiController extends BaseAPIController protected $entityType = ENTITY_INVOICE; - public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer) + public function __construct(InvoiceService $invoiceService, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, PaymentRepository $paymentRepo, Mailer $mailer, PaymentService $paymentService) { parent::__construct(); @@ -38,6 +39,7 @@ class InvoiceApiController extends BaseAPIController $this->paymentRepo = $paymentRepo; $this->invoiceService = $invoiceService; $this->mailer = $mailer; + $this->paymentService = $paymentService; } /** @@ -163,8 +165,9 @@ class InvoiceApiController extends BaseAPIController $invoice = $this->invoiceService->save($data); $payment = false; - // Optionally create payment with invoice - if (isset($data['paid']) && $data['paid']) { + if (isset($data['auto_bill']) && boolval($data['auto_bill'])) { + $payment = $this->paymentService->autoBillInvoice($invoice); + } else if (isset($data['paid']) && $data['paid']) { $payment = $this->paymentRepo->save([ 'invoice_id' => $invoice->id, 'client_id' => $client->id, From 3410043b0ad20129bb34e4847c9fdf29e702781e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Holger=20Lo=CC=88sken?= Date: Mon, 4 Jul 2016 22:30:27 +0200 Subject: [PATCH 099/111] Add code climate config --- codeclimate.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 codeclimate.yml diff --git a/codeclimate.yml b/codeclimate.yml new file mode 100644 index 000000000000..f006734c5893 --- /dev/null +++ b/codeclimate.yml @@ -0,0 +1,6 @@ +exclude_paths: + - "bootstrap/cache" + - "resources/" + - "storage/" + - "tests/" + - "**.md" \ No newline at end of file From 386e026dbbf12444e8b6325a07dc4c9d1b2d01b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Holger=20L=C3=B6sken?= Date: Mon, 4 Jul 2016 20:32:07 +0000 Subject: [PATCH 100/111] Renamed config for codeclimate --- codeclimate.yml => .codeclimate.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename codeclimate.yml => .codeclimate.yml (100%) diff --git a/codeclimate.yml b/.codeclimate.yml similarity index 100% rename from codeclimate.yml rename to .codeclimate.yml From 1adffcae37dff2aa1685cb90fade6db729a8e47e Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 5 Jul 2016 15:08:42 +0300 Subject: [PATCH 101/111] Updated reseller pricing to match develop --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 79024cd8c173..240cc58403ef 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ We're often asked to recommend PHP developers to help setup our app and make sma ### Affiliates Programs * Referral program (we pay you): $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/) -* White-label reseller (you pay us): 10% of revenue with a $100 sign up fee +* White-label reseller (you pay us): 10% of revenue with a $500 sign up fee ### Installation Options * [Self-Host Zip](https://www.invoiceninja.com/knowledgebase/self-host/) - Free From 9aa140011a54868ea7412286e09ac5ca9597013f Mon Sep 17 00:00:00 2001 From: Abby Armada Date: Tue, 5 Jul 2016 14:24:44 -0400 Subject: [PATCH 102/111] Update .codeclimate.yml - Add nodes for engines and ratings - Fix spacing --- .codeclimate.yml | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index f006734c5893..ba99e864e512 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,6 +1,33 @@ +engines: + csslint: + enabled: true + duplication: + enabled: true + config: + languages: + - ruby + - javascript + - python + - php + eslint: + enabled: true + fixme: + enabled: true + phpmd: + enabled: true +ratings: + paths: + - "**.css" + - "**.inc" + - "**.js" + - "**.jsx" + - "**.module" + - "**.php" + - "**.py" + - "**.rb" exclude_paths: - - "bootstrap/cache" - - "resources/" - - "storage/" - - "tests/" - - "**.md" \ No newline at end of file +- "bootstrap/cache" +- "resources/" +- "storage/" +- "tests/" +- "**.md" From af41cc2cb5d5c34e97206f4f50d173bebf5370ef Mon Sep 17 00:00:00 2001 From: Abby Armada Date: Tue, 5 Jul 2016 14:43:35 -0400 Subject: [PATCH 103/111] Update .codeclimate.yml - Duplication engine time out test --- .codeclimate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index ba99e864e512..0fdb26b84dbf 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -31,3 +31,5 @@ exclude_paths: - "storage/" - "tests/" - "**.md" +- "**.min.js" +- "**.min.php" From 3afc29a57146c5a3e6587e38e04b78e1b1fc7b3b Mon Sep 17 00:00:00 2001 From: Abby Armada Date: Tue, 5 Jul 2016 15:22:40 -0400 Subject: [PATCH 104/111] Update .codeclimate.yml Testing ESLint --- .codeclimate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 0fdb26b84dbf..66df79274d96 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -10,7 +10,7 @@ engines: - python - php eslint: - enabled: true + enabled: false fixme: enabled: true phpmd: From 6d3d7c725b0ac6898b71c4f5e42dadab817b313a Mon Sep 17 00:00:00 2001 From: Abby Armada Date: Tue, 5 Jul 2016 15:42:06 -0400 Subject: [PATCH 105/111] Update .codeclimate.yml - add more exclusions. --- .codeclimate.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 66df79274d96..390a5217b1ef 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -33,3 +33,4 @@ exclude_paths: - "**.md" - "**.min.js" - "**.min.php" +- "**.min.css" From 316284b7e2e55ac2ffaab6a355836c561fc4d28f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 11 Jul 2016 23:36:51 +0300 Subject: [PATCH 106/111] Bug fixes --- app/Http/Controllers/QuoteController.php | 2 +- app/Http/Controllers/TaskController.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 2efd429be52b..a9719798a1b2 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -119,7 +119,7 @@ class QuoteController extends BaseController 'taxRateOptions' => $options, 'defaultTax' => $defaultTax, 'countries' => Cache::get('countries'), - 'clients' => Client::scope()->viewable()->with('contacts', 'country')->orderBy('name')->get(), + 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->orderBy('name')->get(), 'currencies' => Cache::get('currencies'), 'sizes' => Cache::get('sizes'), diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 49def0aae85d..839c06395bcf 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -213,7 +213,7 @@ class TaskController extends BaseController private static function getViewModel() { return [ - 'clients' => Client::scope()->viewable()->with('contacts')->orderBy('name')->get(), + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), 'account' => Auth::user()->account, ]; } From a32e372d400f51ed59eb2eb577bd6b2a666579ff Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 12 Jul 2016 08:05:26 +0300 Subject: [PATCH 107/111] Bug fix --- app/Http/Controllers/BaseAPIController.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/Http/Controllers/BaseAPIController.php b/app/Http/Controllers/BaseAPIController.php index 2561a1c57b02..05ea89f89a0b 100644 --- a/app/Http/Controllers/BaseAPIController.php +++ b/app/Http/Controllers/BaseAPIController.php @@ -63,10 +63,6 @@ class BaseAPIController extends Controller } else { $this->manager->setSerializer(new ArraySerializer()); } - - if (Utils::isNinjaDev()) { - \DB::enableQueryLog(); - } } protected function handleAction($request) @@ -162,12 +158,6 @@ class BaseAPIController extends Controller protected function response($response) { - if (Utils::isNinjaDev()) { - $count = count(\DB::getQueryLog()); - Log::info(Request::method() . ' - ' . Request::url() . ": $count queries"); - //Log::info(json_encode(\DB::getQueryLog())); - } - $index = Request::get('index') ?: 'data'; if ($index == 'none') { From 92043afb9c2f62b414328ec293e4c0fcb94c8984 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 13 Jul 2016 16:35:56 +0300 Subject: [PATCH 108/111] WePay fixes --- app/Ninja/PaymentDrivers/WePayPaymentDriver.php | 17 ----------------- .../views/payments/braintree/paypal.blade.php | 2 ++ .../payments/wepay/bank_transfer.blade.php | 2 ++ 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/app/Ninja/PaymentDrivers/WePayPaymentDriver.php b/app/Ninja/PaymentDrivers/WePayPaymentDriver.php index c8865e74d103..a88cc1ac51af 100644 --- a/app/Ninja/PaymentDrivers/WePayPaymentDriver.php +++ b/app/Ninja/PaymentDrivers/WePayPaymentDriver.php @@ -7,8 +7,6 @@ use Exception; class WePayPaymentDriver extends BasePaymentDriver { - protected $sourceReferenceParam = 'accessToken'; - public function gatewayTypes() { $types = [ @@ -23,21 +21,6 @@ class WePayPaymentDriver extends BasePaymentDriver return $types; } - /* - public function startPurchase($input = false, $sourceId = false) - { - $data = parent::startPurchase($input, $sourceId); - - if ($this->isGatewayType(GATEWAY_TYPE_BANK_TRANSFER)) { - if ( ! $sourceId) { - throw new Exception(); - } - } - - return $data; - } - */ - public function tokenize() { return true; diff --git a/resources/views/payments/braintree/paypal.blade.php b/resources/views/payments/braintree/paypal.blade.php index 711124f6ecf9..3009d27f14be 100644 --- a/resources/views/payments/braintree/paypal.blade.php +++ b/resources/views/payments/braintree/paypal.blade.php @@ -39,4 +39,6 @@ @endif + {!! Former::close() !!} + @stop diff --git a/resources/views/payments/wepay/bank_transfer.blade.php b/resources/views/payments/wepay/bank_transfer.blade.php index cfca52b94a88..22ea75be4136 100644 --- a/resources/views/payments/wepay/bank_transfer.blade.php +++ b/resources/views/payments/wepay/bank_transfer.blade.php @@ -39,4 +39,6 @@ @endif + {!! Former::close() !!} + @stop From f472950ed0643c3e110f81f6cf802a47267887f7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 13 Jul 2016 21:10:43 +0300 Subject: [PATCH 109/111] Fix for [Object] problem with certain designs --- public/built.js | 2 -- public/js/pdf.pdfmake.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/public/built.js b/public/built.js index 6e96fb8e2408..f72e47d0968e 100644 --- a/public/built.js +++ b/public/built.js @@ -31773,8 +31773,6 @@ NINJA.processItem = function(item, section) { NINJA.parseMarkdownText = function(val, groupText) { - val = val + "\n"; - var rules = [ ['\\\*\\\*(\\\w.+?)\\\*\\\*', {'bold': true}], // **value** ['\\\*(\\\w.+?)\\\*', {'italics': true}], // *value* diff --git a/public/js/pdf.pdfmake.js b/public/js/pdf.pdfmake.js index a9624c5f3b50..4a7a54849932 100644 --- a/public/js/pdf.pdfmake.js +++ b/public/js/pdf.pdfmake.js @@ -737,8 +737,6 @@ NINJA.processItem = function(item, section) { NINJA.parseMarkdownText = function(val, groupText) { - val = val + "\n"; - var rules = [ ['\\\*\\\*(\\\w.+?)\\\*\\\*', {'bold': true}], // **value** ['\\\*(\\\w.+?)\\\*', {'italics': true}], // *value* From f47b6dc5ac9cf6d98eb44a78093fe21f98c45183 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 13 Jul 2016 21:11:26 +0300 Subject: [PATCH 110/111] Added dwolla to text files --- resources/lang/en/texts.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 4038ee96a24e..3029357cf526 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2028,6 +2028,7 @@ $LANG = array( 'apply_taxes' => 'Apply taxes', 'min_to_max_users' => ':min to :max users', 'max_users_reached' => 'The maximum number of users has been reached.' + 'dwolla' => 'Dwolla', ); From 09e06703ea426dfb1415818b8b0199270185614d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 13 Jul 2016 21:49:10 +0300 Subject: [PATCH 111/111] Added dwolla to text files --- resources/lang/en/texts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 3029357cf526..fc2689c4582d 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2027,7 +2027,7 @@ $LANG = array( 'restored_expense_category' => 'Successfully restored expense category', 'apply_taxes' => 'Apply taxes', 'min_to_max_users' => ':min to :max users', - 'max_users_reached' => 'The maximum number of users has been reached.' + 'max_users_reached' => 'The maximum number of users has been reached.', 'dwolla' => 'Dwolla', );
  • {{ trans("texts.terms") }}