diff --git a/.env.example b/.env.example index e8a53eaa41..3b870ff2b8 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,5 @@ MAIL_USERNAME MAIL_FROM_ADDRESS MAIL_FROM_NAME MAIL_PASSWORD + +PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address' \ No newline at end of file diff --git a/app/Console/Commands/CheckData.php b/app/Console/Commands/CheckData.php index 292ba0a9f3..b83e747e35 100644 --- a/app/Console/Commands/CheckData.php +++ b/app/Console/Commands/CheckData.php @@ -111,7 +111,7 @@ class CheckData extends Command { ->first(['invoices.amount', 'invoices.is_recurring', 'invoices.is_quote', 'invoices.deleted_at', 'invoices.id', 'invoices.is_deleted']); // Check if this invoice was once set as recurring invoice - if (!$invoice->is_recurring && DB::table('invoices') + if ($invoice && !$invoice->is_recurring && DB::table('invoices') ->where('recurring_invoice_id', '=', $activity->invoice_id) ->first(['invoices.id'])) { $invoice->is_recurring = 1; diff --git a/app/Console/Commands/SendRecurringInvoices.php b/app/Console/Commands/SendRecurringInvoices.php index 1720e7dda7..ed0e1589b7 100644 --- a/app/Console/Commands/SendRecurringInvoices.php +++ b/app/Console/Commands/SendRecurringInvoices.php @@ -37,12 +37,14 @@ class SendRecurringInvoices extends Command $this->info(count($invoices).' recurring invoice(s) found'); foreach ($invoices as $recurInvoice) { - $this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO')); - + $this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO')); $invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice); if ($invoice && !$invoice->isPaid()) { $recurInvoice->account->loadLocalizationSettings($invoice->client); + if ($invoice->account->pdf_email_attachment) { + $invoice->updateCachedPDF(); + } $this->mailer->sendInvoice($invoice); } } diff --git a/app/Console/Commands/SendReminders.php b/app/Console/Commands/SendReminders.php new file mode 100644 index 0000000000..40b1896eed --- /dev/null +++ b/app/Console/Commands/SendReminders.php @@ -0,0 +1,65 @@ +mailer = $mailer; + $this->invoiceRepo = $invoiceRepo; + $this->accountRepo = $accountRepo; + } + + public function fire() + { + $this->info(date('Y-m-d').' Running SendReminders...'); + $today = new DateTime(); + + $accounts = $this->accountRepo->findWithReminders(); + $this->info(count($accounts).' accounts found'); + + foreach ($accounts as $account) { + $invoices = $this->invoiceRepo->findNeedingReminding($account); + $this->info($account->name . ': ' . count($invoices).' invoices found'); + + foreach ($invoices as $invoice) { + if ($reminder = $invoice->getReminder()) { + $this->mailer->sendInvoice($invoice, $reminder); + } + } + } + + $this->info('Done'); + } + + protected function getArguments() + { + return array( + //array('example', InputArgument::REQUIRED, 'An example argument.'), + ); + } + + protected function getOptions() + { + return array( + //array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null), + ); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 9235cbf87b..25b6d0c7ad 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -16,6 +16,7 @@ class Kernel extends ConsoleKernel { 'App\Console\Commands\ResetData', 'App\Console\Commands\CheckData', 'App\Console\Commands\SendRenewalInvoices', + 'App\Console\Commands\SendReminders', ]; /** diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 5b1d941e14..d3485ef0ba 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -249,10 +249,14 @@ class AccountController extends BaseController if ($subSection == ACCOUNT_CUSTOMIZE_DESIGN) { $data['customDesign'] = ($account->custom_design && !$design) ? $account->custom_design : $design; } - } else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) { - $data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE); - $data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE); - $data['paymentEmail'] = $account->getEmailTemplate(ENTITY_PAYMENT); + } else if ($subSection == ACCOUNT_TEMPLATES_AND_REMINDERS) { + $data['templates'] = []; + foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) { + $data['templates'][$type] = [ + 'subject' => $account->getEmailSubject($type), + 'template' => $account->getEmailTemplate($type), + ]; + } $data['emailFooter'] = $account->getEmailFooter(); $data['title'] = trans('texts.email_templates'); } else if ($subSection == ACCOUNT_USER_MANAGEMENT) { @@ -289,7 +293,7 @@ class AccountController extends BaseController return AccountController::saveInvoiceDesign(); } elseif ($subSection == ACCOUNT_CUSTOMIZE_DESIGN) { return AccountController::saveCustomizeDesign(); - } elseif ($subSection == ACCOUNT_EMAIL_TEMPLATES) { + } elseif ($subSection == ACCOUNT_TEMPLATES_AND_REMINDERS) { return AccountController::saveEmailTemplates(); } } elseif ($section == ACCOUNT_PRODUCTS) { @@ -315,16 +319,28 @@ class AccountController extends BaseController if (Auth::user()->account->isPro()) { $account = Auth::user()->account; - $account->email_template_invoice = Input::get('email_template_invoice', $account->getEmailTemplate(ENTITY_INVOICE)); - $account->email_template_quote = Input::get('email_template_quote', $account->getEmailTemplate(ENTITY_QUOTE)); - $account->email_template_payment = Input::get('email_template_payment', $account->getEmailTemplate(ENTITY_PAYMENT)); + foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) { + $subjectField = "email_subject_{$type}"; + $account->$subjectField = Input::get($subjectField, $account->getEmailSubject($type)); + + $bodyField = "email_template_{$type}"; + $account->$bodyField = Input::get($bodyField, $account->getEmailTemplate($type)); + } + + foreach ([REMINDER1, REMINDER2, REMINDER3] as $type) { + $enableField = "enable_{$type}"; + $account->$enableField = Input::get($enableField) ? true : false; + + $numDaysField = "num_days_{$type}"; + $account->$numDaysField = Input::get($numDaysField); + } $account->save(); Session::flash('message', trans('texts.updated_settings')); } - return Redirect::to('company/advanced_settings/email_templates'); + return Redirect::to('company/advanced_settings/templates_and_reminders'); } private function saveProducts() @@ -346,7 +362,7 @@ class AccountController extends BaseController $rules = []; $user = Auth::user(); $iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH)); - $subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('substr(string, start)')), 0, MAX_SUBDOMAIN_LENGTH)); + $subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH)); if (!$subdomain || in_array($subdomain, ['www', 'app', 'mail', 'admin', 'blog', 'user', 'contact', 'payment', 'payments', 'billing', 'invoice', 'business', 'owner'])) { $subdomain = null; } @@ -361,7 +377,6 @@ class AccountController extends BaseController ->withErrors($validator) ->withInput(); } else { - $account = Auth::user()->account; $account->subdomain = $subdomain; $account->iframe_url = $iframeURL; diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index ff4c84c649..2f53f9d486 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -239,7 +239,7 @@ class AccountGatewayController extends BaseController foreach ($fields as $field => $details) { $value = trim(Input::get($gateway->id.'_'.$field)); // if the new value is masked use the original value - if ($value && $value === str_repeat('*', strlen($value))) { + if ($oldConfig && $value && $value === str_repeat('*', strlen($value))) { $value = $oldConfig->$field; } if (!$value && ($field == 'testMode' || $field == 'developerMode')) { diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index e876eb146f..27e0a1de90 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -62,12 +62,10 @@ class InvoiceApiController extends Controller // check if the invoice number is set and unique if (!isset($data['invoice_number']) && !isset($data['id'])) { $data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber(); - } else if (isset($data['invoice_number'])) { + } else if (isset($data['invoice_number'])) { $invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first(); if ($invoice) { $error = trans('validation.unique', ['attribute' => 'texts.invoice_number']); - } else { - $data['id'] = $invoice->public_id; } } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 07aaf81aa5..41c6358775 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -1,5 +1,6 @@ firstOrFail(); - $invoice = $invitation->invoice; + $invitation = Invitation::where('invitation_key', '=', $invitationKey)->first(); + + if (!$invitation) { + App::abort(404, trans('texts.invoice_not_found')); + } + $invoice = $invitation->invoice; + if (!$invoice || $invoice->is_deleted) { - return View::make('invoices.deleted'); + App::abort(404, trans('texts.invoice_not_found')); } $invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country'); @@ -186,7 +192,7 @@ class InvoiceController extends BaseController $account = $client->account; if (!$client || $client->is_deleted) { - return View::make('invoices.deleted'); + App::abort(404, trans('texts.invoice_not_found')); } if ($account->subdomain) { @@ -198,7 +204,7 @@ class InvoiceController extends BaseController } } - if (!Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { + if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) { Activity::viewInvoice($invitation); Event::fire(new InvoiceViewed($invoice)); } @@ -261,6 +267,7 @@ class InvoiceController extends BaseController 'contact' => $contact, 'paymentTypes' => $paymentTypes, 'paymentURL' => $paymentURL, + 'phantomjs' => Input::has('phantomjs'), ); return View::make('invoices.view', $data); @@ -521,7 +528,7 @@ class InvoiceController extends BaseController $pdfUpload = Input::get('pdfupload'); if (!empty($pdfUpload) && strpos($pdfUpload, 'data:application/pdf;base64,') === 0) { - $this->storePDF(Input::get('pdfupload'), $invoice); + $invoice->updateCachedPDF(Input::get('pdfupload')); } if ($action == 'clone') { @@ -684,10 +691,4 @@ class InvoiceController extends BaseController return View::make('invoices.history', $data); } - - private function storePDF($encodedString, $invoice) - { - $encodedString = str_replace('data:application/pdf;base64,', '', $encodedString); - file_put_contents($invoice->getPDFPath(), base64_decode($encodedString)); - } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 0739e12784..8fb8b78f71 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -225,7 +225,6 @@ Route::get('/forgot_password', function() { return Redirect::to(NINJA_APP_URL.'/forgot', 301); }); - if (!defined('CONTACT_EMAIL')) { define('CONTACT_EMAIL', Config::get('mail.from.address')); define('CONTACT_NAME', Config::get('mail.from.name')); @@ -260,7 +259,7 @@ if (!defined('CONTACT_EMAIL')) { define('ACCOUNT_CHART_BUILDER', 'chart_builder'); define('ACCOUNT_USER_MANAGEMENT', 'user_management'); define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations'); - define('ACCOUNT_EMAIL_TEMPLATES', 'email_templates'); + define('ACCOUNT_TEMPLATES_AND_REMINDERS', 'templates_and_reminders'); define('ACCOUNT_TOKEN_MANAGEMENT', 'token_management'); define('ACCOUNT_CUSTOMIZE_DESIGN', 'customize_design'); @@ -391,6 +390,7 @@ if (!defined('CONTACT_EMAIL')) { define('ZAPIER_URL', 'https://zapier.com/developer/invite/11276/85cf0ee4beae8e802c6c579eb4e351f1/'); define('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'); define('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'); + define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/single/browser/v1/'); define('COUNT_FREE_DESIGNS', 4); define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design @@ -425,6 +425,10 @@ if (!defined('CONTACT_EMAIL')) { define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN'); define('PAYMENT_TYPE_ANY', 'PAYMENT_TYPE_ANY'); + define('REMINDER1', 'reminder1'); + define('REMINDER2', 'reminder2'); + define('REMINDER3', 'reminder3'); + $creditCards = [ 1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'], 2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'], @@ -487,4 +491,5 @@ if (Auth::check() && Auth::user()->id === 1) { Auth::loginUsingId(1); } -*/ \ No newline at end of file +*/ + diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 073990e85d..9c54f5164a 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -776,4 +776,14 @@ class Utils } return $domain; } + + public static function replaceSubdomain($domain, $subdomain) { + $parsedUrl = parse_url($domain); + $host = explode('.', $parsedUrl['host']); + if (count($host) > 0) { + $oldSubdomain = $host[0]; + $domain = str_replace("://{$oldSubdomain}.", "://{$subdomain}.", $domain); + } + return $domain; + } } diff --git a/app/Models/Account.php b/app/Models/Account.php index ac44e1950d..b75c23ae31 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -25,6 +25,13 @@ class Account extends Eloquent return $this->hasMany('App\Models\User'); } + public function getPrimaryUser() + { + return $this->hasMany('App\Models\User') + ->whereRaw('public_id = 0 OR public_id IS NULL') + ->first(); + } + public function clients() { return $this->hasMany('App\Models\Client'); @@ -405,15 +412,35 @@ class Account extends Eloquent return $this; } + public function getEmailSubject($entityType) + { + $field = "email_subject_{$entityType}"; + $value = $this->$field; + + if ($value) { + return $value; + } + + if (strpos($entityType, 'reminder') !== false) { + $entityType = 'reminder'; + } + + return trans("texts.{$entityType}_subject", ['invoice' => '$invoice', 'account' => '$account']); + } + public function getEmailTemplate($entityType, $message = false) { - $field = "email_template_$entityType"; + $field = "email_template_{$entityType}"; $template = $this->$field; if ($template) { return $template; } + if (strpos($entityType, 'reminder') >= 0) { + $entityType = ENTITY_INVOICE; + } + $template = "\$client,

\r\n\r\n" . trans("texts.{$entityType}_message", ['amount' => '$amount']) . "

\r\n\r\n" . "\$link

\r\n\r\n"; @@ -431,7 +458,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') . "
\$account

"; + return "

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

"; } } @@ -449,6 +476,20 @@ class Account extends Eloquent { return $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT; } + + public function getSiteUrl() + { + $url = SITE_URL; + $iframe_url = $this->iframe_url; + + if ($iframe_url) { + return "{$iframe_url}/?"; + } else if ($this->subdomain) { + $url = Utils::replaceSubdomain($url, $this->subdomain); + } + + return $url; + } } Account::updated(function ($account) { diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index f31d102eeb..6b56cbdbd1 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -1,5 +1,6 @@ account->iframe_url; if ($iframe_url) { - return "{$iframe_url}?{$this->invitation_key}"; + return "{$iframe_url}/?{$this->invitation_key}"; } else if ($this->account->subdomain) { - $parsedUrl = parse_url($url); - $host = explode('.', $parsedUrl['host']); - $subdomain = $host[0]; - $url = str_replace("://{$subdomain}.", "://{$this->account->subdomain}.", $url); + $url = Utils::replaceSubdomain($url, $this->subdomain); } return "{$url}/view/{$this->invitation_key}"; diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 3da2548a75..52dea60217 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -262,6 +262,57 @@ class Invoice extends EntityModel return false; } + + public function getReminder() { + for ($i=1; $i<=3; $i++) { + $field = "enable_reminder{$i}"; + if (!$this->account->$field) { + continue; + } + $field = "num_days_reminder{$i}"; + $date = date('Y-m-d', strtotime("- {$this->account->$field} days")); + + if ($this->due_date == $date) { + return "reminder{$i}"; + } + } + + return false; + } + + public function updateCachedPDF($encodedString = false) + { + if (!$encodedString) { + $invitation = $this->invitations[0]; + $key = $invitation->getLink(); + + $curl = curl_init(); + $jsonEncodedData = json_encode([ + 'targetUrl' => SITE_URL . "/view/{$key}/?phantomjs=true", + 'requestType' => 'raw', + ]); + + $opts = [ + CURLOPT_URL => PHANTOMJS_CLOUD . env('PHANTOMJS_CLOUD_KEY'), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => $jsonEncodedData, + CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)], + ]; + + curl_setopt_array($curl, $opts); + $encodedString = strip_tags(curl_exec($curl)); + curl_close($curl); + + if (!$encodedString) { + return false; + } + } + + $encodedString = str_replace('data:application/pdf;base64,', '', $encodedString); + file_put_contents($this->getPDFPath(), base64_decode($encodedString)); + } } Invoice::creating(function ($invoice) { diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index 5d955b1a9f..4f4a91ec76 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -3,6 +3,7 @@ use Utils; use Event; use URL; +use Auth; use App\Models\Invoice; use App\Models\Payment; @@ -12,7 +13,7 @@ use App\Events\InvoiceSent; class ContactMailer extends Mailer { - public function sendInvoice(Invoice $invoice) + public function sendInvoice(Invoice $invoice, $reminder = false) { $invoice->load('invitations', 'client.language', 'account'); $entityType = $invoice->getEntityType(); @@ -23,65 +24,69 @@ class ContactMailer extends Mailer $account->loadLocalizationSettings($client); $view = 'invoice'; - $subject = trans("texts.{$entityType}_subject", ['invoice' => $invoice->invoice_number, 'account' => $invoice->account->getDisplayName()]); $accountName = $invoice->account->getDisplayName(); - $emailTemplate = $invoice->account->getEmailTemplate($entityType); - $invoiceAmount = Utils::formatMoney($invoice->getRequestedAmount(), $client->getCurrencyId()); + $emailTemplate = $invoice->account->getEmailTemplate($reminder ?: $entityType); + $emailSubject = $invoice->account->getEmailSubject($reminder ?: $entityType); $this->initClosure($invoice); + $response = false; + $sent = false; foreach ($invoice->invitations as $invitation) { - if (!$invitation->user || !$invitation->user->email || $invitation->user->trashed()) { - return false; + if (Auth::check()) { + $user = Auth::user(); + } else { + $user = $invitation->user; + if ($invitation->user->trashed()) { + $user = $account->getPrimaryUser(); + } } - if (!$invitation->contact || !$invitation->contact->email || $invitation->contact->trashed()) { - return false; + + if (!$user->email || !$user->confirmed) { + continue; + } + + if (!$invitation->contact->email + || $invitation->contact->trashed()) { + continue; } $invitation->sent_date = \Carbon::now()->toDateTimeString(); $invitation->save(); $variables = [ - '$footer' => $invoice->account->getEmailFooter(), - '$link' => $invitation->getLink(), - '$client' => $client->getDisplayName(), - '$account' => $accountName, - '$contact' => $invitation->contact->getDisplayName(), - '$amount' => $invoiceAmount, - '$advancedRawInvoice->' => '$' + 'account' => $account, + 'client' => $client, + 'invitation' => $invitation, + 'amount' => $invoice->getRequestedAmount() ]; - // Add variables for available payment types - foreach (Gateway::getPaymentTypeLinks() as $type) { - $variables["\${$type}_link"] = URL::to("/payment/{$invitation->invitation_key}/{$type}"); - } - - $data['body'] = str_replace(array_keys($variables), array_values($variables), $emailTemplate); - $data['body'] = preg_replace_callback('/\{\{\$?(.*)\}\}/', $this->advancedTemplateHandler, $data['body']); + $data['body'] = $this->processVariables($emailTemplate, $variables); $data['link'] = $invitation->getLink(); $data['entityType'] = $entityType; $data['invoice_id'] = $invoice->id; - $fromEmail = $invitation->user->email; + $subject = $this->processVariables($emailSubject, $variables); + $fromEmail = $user->email; $response = $this->sendTo($invitation->contact->email, $fromEmail, $accountName, $subject, $view, $data); - if ($response !== true) { - return $response; + if ($response === true) { + $sent = true; + Activity::emailInvoice($invitation); } - - Activity::emailInvoice($invitation); } - if (!$invoice->isSent()) { - $invoice->invoice_status_id = INVOICE_STATUS_SENT; - $invoice->save(); + if ($sent === true) { + if (!$invoice->isSent()) { + $invoice->invoice_status_id = INVOICE_STATUS_SENT; + $invoice->save(); + } + + $account->loadLocalizationSettings(); + Event::fire(new InvoiceSent($invoice)); } - - $account->loadLocalizationSettings(); - Event::fire(new InvoiceSent($invoice)); - - return $response; + return $response ?: trans('texts.email_error'); } public function sendPaymentConfirmation(Payment $payment) @@ -93,30 +98,38 @@ class ContactMailer extends Mailer $invoice = $payment->invoice; $view = 'payment_confirmation'; - $subject = trans('texts.payment_subject', ['invoice' => $invoice->invoice_number]); $accountName = $account->getDisplayName(); $emailTemplate = $account->getEmailTemplate(ENTITY_PAYMENT); + $emailSubject = $invoice->account->getEmailSubject(ENTITY_PAYMENT); - $variables = [ - '$footer' => $account->getEmailFooter(), - '$client' => $client->getDisplayName(), - '$account' => $accountName, - '$amount' => Utils::formatMoney($payment->amount, $client->getCurrencyId()) - ]; - + $this->initClosure($invoice); + if ($payment->invitation) { $user = $payment->invitation->user; $contact = $payment->contact; - $variables['$link'] = $payment->invitation->getLink(); + $invitation = $payment->invitation; } else { $user = $payment->user; $contact = $client->contacts[0]; - $variables['$link'] = $payment->invoice->invitations[0]->getLink(); + $invitation = $payment->invoice->invitations[0]; } - $data = ['body' => str_replace(array_keys($variables), array_values($variables), $emailTemplate)]; - - //$data['invoice_id'] = $payment->invoice->id; + $variables = [ + 'account' => $account, + 'client' => $client, + 'invitation' => $invitation, + 'amount' => $payment->amount + ]; + + $data = [ + 'body' => $this->processVariables($emailTemplate, $variables) + ]; + $subject = $this->processVariables($emailSubject, $variables); + + $data['invoice_id'] = $payment->invoice->id; + if ($invoice->account->pdf_email_attachment) { + $invoice->updateCachedPDF(); + } if ($user->email && $contact->email) { $this->sendTo($contact->email, $user->email, $accountName, $subject, $view, $data); @@ -148,6 +161,31 @@ class ContactMailer extends Mailer $this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data); } + private function processVariables($template, $data) + { + $variables = [ + '$footer' => $data['account']->getEmailFooter(), + '$link' => $data['invitation']->getLink(), + '$client' => $data['client']->getDisplayName(), + '$account' => $data['account']->getDisplayName(), + '$contact' => $data['invitation']->contact->getDisplayName(), + '$amount' => Utils::formatMoney($data['amount'], $data['client']->getCurrencyId()), + '$invoice' => $data['invitation']->invoice->invoice_number, + '$quote' => $data['invitation']->invoice->invoice_number, + '$advancedRawInvoice->' => '$' + ]; + + // Add variables for available payment types + foreach (Gateway::getPaymentTypeLinks() as $type) { + $variables["\${$type}_link"] = URL::to("/payment/{$data['invitation']->invitation_key}/{$type}"); + } + + $str = str_replace(array_keys($variables), array_values($variables), $template); + $str = preg_replace_callback('/\{\{\$?(.*)\}\}/', $this->advancedTemplateHandler, $str); + + return $str; + } + private function initClosure($object) { $this->advancedTemplateHandler = function($match) use ($object) { diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php index 9995a27c4d..09a7e88ecd 100644 --- a/app/Ninja/Mailers/Mailer.php +++ b/app/Ninja/Mailers/Mailer.php @@ -21,25 +21,25 @@ class Mailer $replyEmail = $fromEmail; $fromEmail = CONTACT_EMAIL; + $message->to($toEmail) + ->from($fromEmail, $fromName) + ->replyTo($replyEmail, $fromName) + ->subject($subject); + if (isset($data['invoice_id'])) { - $invoice = Invoice::with('account')->where('id', '=', $data['invoice_id'])->get()->first(); - if($invoice->account->pdf_email_attachment && file_exists($invoice->getPDFPath())) { + $invoice = Invoice::with('account')->where('id', '=', $data['invoice_id'])->first(); + if ($invoice->account->pdf_email_attachment && file_exists($invoice->getPDFPath())) { $message->attach( $invoice->getPDFPath(), array('as' => $invoice->getFileName(), 'mime' => 'application/pdf') ); } } - - $message->to($toEmail) - ->from($fromEmail, $fromName) - ->replyTo($replyEmail, $fromName) - ->subject($subject); - }); return true; } catch (Exception $exception) { + Utils::logError('Email Error: ' . $exception->getMessage()); if (isset($_ENV['POSTMARK_API_TOKEN'])) { $response = $exception->getResponse()->getBody()->getContents(); $response = json_decode($response); diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index cbd5d16f9f..d9ecc07384 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -392,4 +392,9 @@ class AccountRepository $userAccount->save(); } } + + public function findWithReminders() + { + return Account::whereRaw('enable_reminder1 = 1 OR enable_reminder2 = 1 OR enable_reminder3 = 1')->get(); + } } diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 28a92351a0..d98200c573 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -658,4 +658,25 @@ class InvoiceRepository return $invoice; } + + public function findNeedingReminding($account) + { + $dates = []; + for ($i=1; $i<=3; $i++) { + $field = "enable_reminder{$i}"; + if (!$account->$field) { + continue; + } + $field = "num_days_reminder{$i}"; + $dates[] = "due_date = '" . date('Y-m-d', strtotime("- {$account->$field} days")) . "'"; + } + $sql = implode(' OR ', $dates); + + $invoices = Invoice::whereAccountId($account->id) + ->where('balance', '>', 0) + ->whereRaw($sql) + ->get(); + + return $invoices; + } } diff --git a/database/migrations/2015_09_10_185135_add_reminder_emails.php b/database/migrations/2015_09_10_185135_add_reminder_emails.php new file mode 100644 index 0000000000..31a622e894 --- /dev/null +++ b/database/migrations/2015_09_10_185135_add_reminder_emails.php @@ -0,0 +1,68 @@ +string('email_subject_invoice')->nullable(); + $table->string('email_subject_quote')->nullable(); + $table->string('email_subject_payment')->nullable(); + + $table->string('email_subject_reminder1')->nullable(); + $table->string('email_subject_reminder2')->nullable(); + $table->string('email_subject_reminder3')->nullable(); + + $table->text('email_template_reminder1')->nullable(); + $table->text('email_template_reminder2')->nullable(); + $table->text('email_template_reminder3')->nullable(); + + $table->boolean('enable_reminder1')->default(false); + $table->boolean('enable_reminder2')->default(false); + $table->boolean('enable_reminder3')->default(false); + + $table->smallInteger('num_days_reminder1')->default(7); + $table->smallInteger('num_days_reminder2')->default(14); + $table->smallInteger('num_days_reminder3')->default(30); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function ($table) { + $table->dropColumn('email_subject_invoice'); + $table->dropColumn('email_subject_quote'); + $table->dropColumn('email_subject_payment'); + + $table->dropColumn('email_subject_reminder1'); + $table->dropColumn('email_subject_reminder2'); + $table->dropColumn('email_subject_reminder3'); + + $table->dropColumn('email_template_reminder1'); + $table->dropColumn('email_template_reminder2'); + $table->dropColumn('email_template_reminder3'); + + $table->dropColumn('enable_reminder1'); + $table->dropColumn('enable_reminder2'); + $table->dropColumn('enable_reminder3'); + + $table->dropColumn('num_days_reminder1'); + $table->dropColumn('num_days_reminder2'); + $table->dropColumn('num_days_reminder3'); + }); + } + +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index f0158920c5..62446b4687 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -14,13 +14,8 @@ class DatabaseSeeder extends Seeder { Eloquent::unguard(); $this->call('ConstantsSeeder'); - $this->command->info('Seeded the constants'); - $this->call('CountriesSeeder'); - $this->command->info('Seeded the countries'); - $this->call('PaymentLibrariesSeeder'); - $this->command->info('Seeded the Payment Libraries'); } } \ No newline at end of file diff --git a/public/js/built.js b/public/js/built.js index 63ce4dcc3b..3bbc29b3c3 100644 --- a/public/js/built.js +++ b/public/js/built.js @@ -31707,7 +31707,7 @@ NINJA.decodeJavascript = function(invoice, javascript) var match = matches[i]; field = match.substring(2, match.indexOf('Value')); field = toSnakeCase(field); - var value = getDescendantProp(invoice, field) || ' '; + var value = getDescendantProp(invoice, field) || ' '; value = doubleDollarSign(value); if (field.toLowerCase().indexOf('date') >= 0 && value != ' ') { @@ -31827,7 +31827,7 @@ NINJA.invoiceLines = function(invoice) { row.push({style:["quantity", rowStyle], text:qty || ' '}); } if (showItemTaxes) { - row.push({style:["tax", rowStyle], text:tax ? tax.toString() + '%' : ' '}); + row.push({style:["tax", rowStyle], text:tax ? (tax.toString() + '%') : ' '}); } row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '}); diff --git a/public/js/pdf.pdfmake.js b/public/js/pdf.pdfmake.js index 1962a1adc8..a835418c68 100644 --- a/public/js/pdf.pdfmake.js +++ b/public/js/pdf.pdfmake.js @@ -159,7 +159,7 @@ NINJA.decodeJavascript = function(invoice, javascript) var match = matches[i]; field = match.substring(2, match.indexOf('Value')); field = toSnakeCase(field); - var value = getDescendantProp(invoice, field) || ' '; + var value = getDescendantProp(invoice, field) || ' '; value = doubleDollarSign(value); if (field.toLowerCase().indexOf('date') >= 0 && value != ' ') { @@ -279,7 +279,7 @@ NINJA.invoiceLines = function(invoice) { row.push({style:["quantity", rowStyle], text:qty || ' '}); } if (showItemTaxes) { - row.push({style:["tax", rowStyle], text:(tax ? tax.toString() + '%') : ' '}); + row.push({style:["tax", rowStyle], text:tax ? (tax.toString() + '%') : ' '}); } row.push({style:["lineTotal", rowStyle], text:lineTotal || ' '}); diff --git a/resources/lang/da/texts.php b/resources/lang/da/texts.php index f3862eb07e..c34f56a7fc 100644 --- a/resources/lang/da/texts.php +++ b/resources/lang/da/texts.php @@ -776,6 +776,18 @@ 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', + ); \ No newline at end of file diff --git a/resources/lang/de/texts.php b/resources/lang/de/texts.php index a4e6993c5e..2f4da96b92 100644 --- a/resources/lang/de/texts.php +++ b/resources/lang/de/texts.php @@ -775,6 +775,18 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', + ); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index d71ec28b56..7defe23974 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -389,7 +389,7 @@ return array( 'deleted_quotes' => 'Successfully deleted :count quotes', 'converted_to_invoice' => 'Successfully converted quote to invoice', - 'quote_subject' => 'New quote from :account', + 'quote_subject' => 'New quote $quote from :account', 'quote_message' => 'To view your quote for :amount, click the link below.', 'quote_link_message' => 'To view your client quote click the link below:', 'notification_quote_sent_subject' => 'Quote :invoice was sent to :client', @@ -424,7 +424,7 @@ return array( 'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_quote' => 'Are you sure you want to email this quote?', - 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?', + 'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'cancel_account' => 'Cancel Account', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', @@ -775,6 +775,18 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', + ); diff --git a/resources/lang/es/texts.php b/resources/lang/es/texts.php index 60eebf1387..3e92d9b661 100644 --- a/resources/lang/es/texts.php +++ b/resources/lang/es/texts.php @@ -753,6 +753,17 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', diff --git a/resources/lang/es_ES/texts.php b/resources/lang/es_ES/texts.php index 5a917db546..1153704696 100644 --- a/resources/lang/es_ES/texts.php +++ b/resources/lang/es_ES/texts.php @@ -775,6 +775,18 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', + ); \ No newline at end of file diff --git a/resources/lang/fr/texts.php b/resources/lang/fr/texts.php index 20634a928d..9bb2ce8dd9 100644 --- a/resources/lang/fr/texts.php +++ b/resources/lang/fr/texts.php @@ -767,6 +767,17 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', diff --git a/resources/lang/fr_CA/texts.php b/resources/lang/fr_CA/texts.php index c835b32508..f3e0e2769a 100644 --- a/resources/lang/fr_CA/texts.php +++ b/resources/lang/fr_CA/texts.php @@ -768,6 +768,17 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', diff --git a/resources/lang/it/texts.php b/resources/lang/it/texts.php index a6a12aa712..7df6cf8791 100644 --- a/resources/lang/it/texts.php +++ b/resources/lang/it/texts.php @@ -416,7 +416,7 @@ return array( 'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_quote' => 'Are you sure you want to email this quote?', - 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?', + 'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'cancel_account' => 'Cancel Account', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', @@ -770,6 +770,17 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', ); diff --git a/resources/lang/lt/texts.php b/resources/lang/lt/texts.php index 84eb479858..69931deef0 100644 --- a/resources/lang/lt/texts.php +++ b/resources/lang/lt/texts.php @@ -424,7 +424,7 @@ return array( 'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_quote' => 'Are you sure you want to email this quote?', - 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?', + 'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'cancel_account' => 'Cancel Account', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', @@ -777,6 +777,17 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', diff --git a/resources/lang/nb_NO/texts.php b/resources/lang/nb_NO/texts.php index 774ddf2009..1ba2d6a483 100644 --- a/resources/lang/nb_NO/texts.php +++ b/resources/lang/nb_NO/texts.php @@ -424,7 +424,7 @@ return array( 'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_quote' => 'Are you sure you want to email this quote?', - 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?', + 'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'cancel_account' => 'Cancel Account', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', @@ -775,6 +775,18 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', + ); \ No newline at end of file diff --git a/resources/lang/nl/texts.php b/resources/lang/nl/texts.php index 44a649c40e..e9348a1502 100644 --- a/resources/lang/nl/texts.php +++ b/resources/lang/nl/texts.php @@ -770,6 +770,17 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', ); diff --git a/resources/lang/pt_BR/texts.php b/resources/lang/pt_BR/texts.php index 3f2d4ba6aa..aaf06f5ea7 100644 --- a/resources/lang/pt_BR/texts.php +++ b/resources/lang/pt_BR/texts.php @@ -417,7 +417,7 @@ return array( 'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_quote' => 'Are you sure you want to email this quote?', - 'confirm_recurring_email_invoice' => 'Recurring is enabled, are you sure you want this invoice emailed?', + 'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', 'cancel_account' => 'Cancel Account', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', @@ -770,6 +770,17 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', ); diff --git a/resources/lang/sv/texts.php b/resources/lang/sv/texts.php index b406e17fb3..4f0ec1c43b 100644 --- a/resources/lang/sv/texts.php +++ b/resources/lang/sv/texts.php @@ -773,6 +773,17 @@ return array( 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', + 'reminder_emails' => 'Reminder Emails', + 'templates_and_reminders' => 'Templates & Reminders', + 'subject' => 'Subject', + 'body' => 'Body', + 'first_reminder' => 'First Reminder', + 'second_reminder' => 'Second Reminder', + 'third_reminder' => 'Third Reminder', + 'num_days_reminder' => 'Days after due date', + 'reminder_subject' => 'Reminder: Invoice :invoice from :account', + 'reset' => 'Reset', + 'invoice_not_found' => 'The requested invoice is not available', diff --git a/resources/views/accounts/email_templates.blade.php b/resources/views/accounts/email_templates.blade.php index 4e29172b6d..71c5a2fcd0 100644 --- a/resources/views/accounts/email_templates.blade.php +++ b/resources/views/accounts/email_templates.blade.php @@ -21,49 +21,107 @@ {!! Former::populateField('email_template_payment', $paymentEmail) !!}
-
-

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

-
-
-
-
- {!! Former::textarea('email_template_invoice')->raw() !!} -
-
-
-
-
- - -
-
-

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

-
-
-
-
- {!! Former::textarea('email_template_quote')->raw() !!} -
-
+
+

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

+
+
+ +
+ + +
+
+
+
+ {!! Former::textarea('email_template_invoice')->raw() !!} +
+
+
+
+ +
+
+
+ {!! Former::textarea('email_template_quote')->raw() !!} +
+
+
+
+ + +
+
+
+ {!! Former::textarea('email_template_payment')->raw() !!} +
+
+
+
+
+ +
+
+

 

-
-

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

-
-
-
-
- {!! Former::textarea('email_template_payment')->raw() !!} -
-
+
+

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

+
+
+ +
+ + +
+
+
+
+ {!! Former::textarea('email_template_invoice')->raw() !!} +
+
+
+
+ +
+
+
+ {!! Former::textarea('email_template_quote')->raw() !!} +
+
+
+
+ + +
+
+
+ {!! Former::textarea('email_template_payment')->raw() !!} +
+
+
+
+
+ +
+
+ + @if (Auth::user()->isPro())
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!} diff --git a/resources/views/accounts/nav_advanced.blade.php b/resources/views/accounts/nav_advanced.blade.php index 6726837af0..87c85049e9 100644 --- a/resources/views/accounts/nav_advanced.blade.php +++ b/resources/views/accounts/nav_advanced.blade.php @@ -1,7 +1,7 @@ diff --git a/resources/views/accounts/template.blade.php b/resources/views/accounts/template.blade.php new file mode 100644 index 0000000000..5bf91f91e9 --- /dev/null +++ b/resources/views/accounts/template.blade.php @@ -0,0 +1,32 @@ +
+
+ @if (isset($isReminder) && $isReminder) +
+
+ {!! Former::checkbox('enable_' . $field)->text(trans('texts.enable'))->label('') !!} + {!! Former::input('num_days_' . $field)->label(trans('texts.num_days_reminder')) !!} +
+
+ @endif +
+
+ {!! Former::text('email_subject_' . $field)->label(trans('texts.subject')) !!} + +
+
+

 

+

+
+
+
+
+ {!! Former::textarea('email_template_' . $field)->label(trans('texts.body')) !!} + +
+
+

 

+

+
+
+
+
\ No newline at end of file diff --git a/resources/views/accounts/templates_and_reminders.blade.php b/resources/views/accounts/templates_and_reminders.blade.php new file mode 100644 index 0000000000..386343cb33 --- /dev/null +++ b/resources/views/accounts/templates_and_reminders.blade.php @@ -0,0 +1,178 @@ +@extends('accounts.nav') + +@section('head') + @parent + + + +@stop + +@section('content') + @parent + @include('accounts.nav_advanced') + + {!! Former::vertical_open()->addClass('col-md-10 col-md-offset-1 warn-on-exit') !!} + {!! Former::populate($account) !!} + + @foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) + @foreach (['subject', 'template'] as $field) + {!! Former::populateField("email_{$field}_{$type}", $templates[$type][$field]) !!} + @endforeach + @endforeach + + {!! Former::populateField("enable_reminder1", intval($account->enable_reminder1)) !!} + {!! Former::populateField("enable_reminder2", intval($account->enable_reminder2)) !!} + {!! Former::populateField("enable_reminder3", intval($account->enable_reminder3)) !!} + +
+
+

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

+
+
+
+
+ +
+ @include('accounts.template', ['field' => 'invoice', 'active' => true]) + @include('accounts.template', ['field' => 'quote']) + @include('accounts.template', ['field' => 'payment']) +
+
+
+
+
+ +

 

+ +
+
+

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

+
+
+
+
+ +
+ @include('accounts.template', ['field' => 'reminder1', 'isReminder' => true, 'active' => true]) + @include('accounts.template', ['field' => 'reminder2', 'isReminder' => true]) + @include('accounts.template', ['field' => 'reminder3', 'isReminder' => true]) +
+
+
+
+
+ + + @if (Auth::user()->isPro()) +
+ {!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!} +
+ @else + + @endif + + {!! Former::close() !!} + + + +@stop diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index 91e9736499..de492e3e94 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -250,7 +250,7 @@ window.setTimeout(function() { $(".alert-hide").fadeOut(500); - }, 2000); + }, 3000); $('#search').blur(function(){ $('#search').css('width', '{{ Utils::isEnglish() ? 150 : 110 }}px'); diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 3773b30555..6a3cf698cb 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -106,7 +106,7 @@
{{ Utils::dateToString($invoice->last_sent_date) }} - - {!! link_to('/invoices/'.$lastSent->public_id, trans('texts.view_invoice'), ['id' => 'lastInvoiceSent', 'target' => '_blank']) !!} + {!! link_to('/invoices/'.$lastSent->public_id, trans('texts.view_invoice'), ['id' => 'lastInvoiceSent']) !!}
@@ -783,14 +783,18 @@ } function resetTerms() { - model.invoice().terms(model.invoice().default_terms()); - refreshPDF(); + if (confirm('{!! trans("texts.are_you_sure") !!}')) { + model.invoice().terms(model.invoice().default_terms()); + refreshPDF(); + } return false; } function resetFooter() { - model.invoice().invoice_footer(model.invoice().default_footer()); - refreshPDF(); + if (confirm('{!! trans("texts.are_you_sure") !!}')) { + model.invoice().invoice_footer(model.invoice().default_footer()); + refreshPDF(); + } return false; } @@ -806,7 +810,7 @@ function onEmailClick() { if (!NINJA.isRegistered) { - alert("{{ trans('texts.registration_required') }}"); + alert("{!! trans('texts.registration_required') !!}"); return; } @@ -817,11 +821,6 @@ function onSaveClick() { if (model.invoice().is_recurring()) { - if (!NINJA.isRegistered) { - alert("{{ trans('texts.registration_required') }}"); - return; - } - if (confirm("{!! trans("texts.confirm_recurring_email_$entityType") !!}" + '\n\n' + getSendToEmails() + '\n' + "{!! trans("texts.confirm_recurring_timing") !!}")) { submitAction(''); } @@ -851,9 +850,9 @@ doc = generatePDF(invoice, design, true); doc.getDataUrl( function(pdfString){ - $('#pdfupload').val(pdfString); - submitAction(action); - }); + $('#pdfupload').val(pdfString); + submitAction(action); + }); } function submitAction(value) { diff --git a/resources/views/invoices/pdf.blade.php b/resources/views/invoices/pdf.blade.php index 53dcfbee7b..3113462747 100644 --- a/resources/views/invoices/pdf.blade.php +++ b/resources/views/invoices/pdf.blade.php @@ -88,7 +88,7 @@ var needsRefresh = false; function refreshPDF(force) { - getPDFString(refreshPDFCB, force); + return getPDFString(refreshPDFCB, force); } function refreshPDFCB(string) { diff --git a/resources/views/invoices/view.blade.php b/resources/views/invoices/view.blade.php index 873c06cb82..cf91e99084 100644 --- a/resources/views/invoices/view.blade.php +++ b/resources/views/invoices/view.blade.php @@ -50,11 +50,19 @@ invoice.contact = {!! $contact->toJson() !!}; function getPDFString(cb) { - generatePDF(invoice, invoice.invoice_design.javascript, true, cb); + return generatePDF(invoice, invoice.invoice_design.javascript, true, cb); } $(function() { - refreshPDF(); + @if (Input::has('phantomjs')) + doc = getPDFString(); + doc.getDataUrl(function(pdfString) { + document.write(pdfString); + document.close(); + }); + @else + refreshPDF(); + @endif }); function onDownloadClick() { @@ -63,7 +71,6 @@ doc.save(fileName + '-' + invoice.invoice_number + '.pdf'); } - @include('invoices.pdf', ['account' => $invoice->client->account]) diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php index e7a6d9363b..1683c7176f 100644 --- a/resources/views/list.blade.php +++ b/resources/views/list.blade.php @@ -133,13 +133,12 @@ }) window.onDatatableReady = function() { - $(':checkbox').unbind('click').click(function() { + $(':checkbox').click(function() { setBulkActionsEnabled(); }); - $('tbody tr').unbind('click').click(function(event) { + $('tbody tr').unbind('click').click(function(event) { if (event.target.type !== 'checkbox' && event.target.type !== 'button' && event.target.tagName.toLowerCase() !== 'a') { - console.log('click'); $checkbox = $(this).closest('tr').find(':checkbox:not(:disabled)'); var checked = $checkbox.prop('checked'); $checkbox.prop('checked', !checked); diff --git a/resources/views/tasks/edit.blade.php b/resources/views/tasks/edit.blade.php index 9307af859c..df51c0d28b 100644 --- a/resources/views/tasks/edit.blade.php +++ b/resources/views/tasks/edit.blade.php @@ -182,7 +182,7 @@ } @if ($task && !$task->is_running) if (!timeLog.isStartValid() || !timeLog.isEndValid()) { - alert("{{ trans('texts.task_errors') }}"); + alert("{!! trans('texts.task_errors') !!}"); showTimeDetails(); return; } diff --git a/tests/acceptance/AllPagesCept.php b/tests/acceptance/AllPagesCept.php index 869115bb35..81db6ce06e 100644 --- a/tests/acceptance/AllPagesCept.php +++ b/tests/acceptance/AllPagesCept.php @@ -70,7 +70,7 @@ $I->see('Invoice Fields'); $I->amOnPage('/company/advanced_settings/invoice_design'); $I->see('Invoice Design'); -$I->amOnPage('/company/advanced_settings/email_templates'); +$I->amOnPage('/company/advanced_settings/templates_and_reminders'); $I->see('Invoice Email'); $I->amOnPage('/company/advanced_settings/charts_and_reports'); diff --git a/tests/acceptance/OnlinePaymentCest.php b/tests/acceptance/OnlinePaymentCest.php index 3417bbd119..df5241f910 100644 --- a/tests/acceptance/OnlinePaymentCest.php +++ b/tests/acceptance/OnlinePaymentCest.php @@ -26,7 +26,7 @@ class OnlinePaymentCest $I->wantTo('create a gateway'); $I->amOnPage('/company/payments'); - if (strpos($I->grabFromCurrentUrl(), 'create') > 0) { + if (strpos($I->grabFromCurrentUrl(), 'create') !== false) { $I->fillField(['name' =>'23_apiKey'], Fixtures::get('gateway_key')); $I->selectOption('#token_billing_type_id', 4); $I->click('Save'); @@ -86,6 +86,7 @@ class OnlinePaymentCest $I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey); $I->checkOption('#auto_bill'); $I->executeJS('preparePdfData(\'email\')'); + $I->wait(2); $I->see("$0.00"); } diff --git a/tests/functional/SettingsCest.php b/tests/functional/SettingsCest.php index ddf3dbf2a8..626f0d2882 100644 --- a/tests/functional/SettingsCest.php +++ b/tests/functional/SettingsCest.php @@ -170,16 +170,16 @@ class SettingsCest public function updateEmailTemplates(FunctionalTester $I) { $I->wantTo('update email templates'); - $I->amOnPage('/company/advanced_settings/email_templates'); + $I->amOnPage('/company/advanced_settings/templates_and_reminders'); $string = $this->faker->text(100); - $I->fillField(['name' => 'email_template_payment'], $string); + $I->fillField(['name' => 'email_template_invoice'], $string); $I->click('Save'); $I->seeResponseCodeIs(200); $I->see('Successfully updated settings'); - $I->seeRecord('accounts', array('email_template_payment' => $string)); + $I->seeRecord('accounts', array('email_template_invoice' => $string)); } public function runReport(FunctionalTester $I)