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 @@
{!! HTML::nav_link('company/advanced_settings/invoice_design', 'invoice_design') !!}
{!! HTML::nav_link('company/advanced_settings/invoice_settings', 'invoice_settings') !!}
- {!! HTML::nav_link('company/advanced_settings/email_templates', 'email_templates') !!}
+ {!! HTML::nav_link('company/advanced_settings/templates_and_reminders', 'templates_and_reminders') !!}
{!! HTML::nav_link('company/advanced_settings/charts_and_reports', 'charts_and_reports') !!}
{!! HTML::nav_link('company/advanced_settings/user_management', 'users_and_tokens') !!}
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)