mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-09-19 16:01:34 +02:00
Added HTML email designs
This commit is contained in:
parent
7109b30673
commit
c5a1abf66f
@ -46,29 +46,78 @@ class CheckData extends Command {
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d') . ' Running CheckData...');
|
||||
$today = new DateTime();
|
||||
|
||||
if (!$this->option('client_id')) {
|
||||
// update client paid_to_date value
|
||||
$clients = DB::table('clients')
|
||||
->join('payments', 'payments.client_id', '=', 'clients.id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->where('payments.is_deleted', '=', 0)
|
||||
->where('invoices.is_deleted', '=', 0)
|
||||
->groupBy('clients.id')
|
||||
->havingRaw('clients.paid_to_date != sum(payments.amount) and clients.paid_to_date != 999999999.9999')
|
||||
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]);
|
||||
$this->info(count($clients) . ' clients with incorrect paid to date');
|
||||
$this->checkPaidToDate();
|
||||
}
|
||||
|
||||
$this->checkBalances();
|
||||
|
||||
$this->checkActivityAccount();
|
||||
|
||||
$this->info('Done');
|
||||
}
|
||||
|
||||
private function checkActivityAccount()
|
||||
{
|
||||
$entityTypes = [
|
||||
ENTITY_INVOICE,
|
||||
ENTITY_CLIENT,
|
||||
ENTITY_CONTACT,
|
||||
ENTITY_PAYMENT,
|
||||
ENTITY_INVITATION,
|
||||
];
|
||||
|
||||
foreach ($entityTypes as $entityType) {
|
||||
$activities = DB::table('activities')
|
||||
->join("{$entityType}s", "{$entityType}s.id", '=', "activities.{$entityType}_id");
|
||||
|
||||
if ($entityType != ENTITY_CLIENT) {
|
||||
$activities = $activities->join('clients', 'clients.id', '=', 'activities.client_id');
|
||||
}
|
||||
|
||||
$activities = $activities->where('activities.account_id', '!=', DB::raw("{$entityType}s.account_id"))
|
||||
->get(['activities.id', "clients.account_id", "clients.user_id"]);
|
||||
|
||||
$this->info(count($activities) . " {$entityType} activity with incorrect account id");
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
foreach ($clients as $client) {
|
||||
DB::table('clients')
|
||||
->where('id', $client->id)
|
||||
->update(['paid_to_date' => $client->amount]);
|
||||
foreach ($activities as $activity) {
|
||||
DB::table('activities')
|
||||
->where('id', $activity->id)
|
||||
->update([
|
||||
'account_id' => $activity->account_id,
|
||||
'user_id' => $activity->user_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkPaidToDate()
|
||||
{
|
||||
// update client paid_to_date value
|
||||
$clients = DB::table('clients')
|
||||
->join('payments', 'payments.client_id', '=', 'clients.id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->where('payments.is_deleted', '=', 0)
|
||||
->where('invoices.is_deleted', '=', 0)
|
||||
->groupBy('clients.id')
|
||||
->havingRaw('clients.paid_to_date != sum(payments.amount) and clients.paid_to_date != 999999999.9999')
|
||||
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]);
|
||||
$this->info(count($clients) . ' clients with incorrect paid to date');
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
foreach ($clients as $client) {
|
||||
DB::table('clients')
|
||||
->where('id', $client->id)
|
||||
->update(['paid_to_date' => $client->amount]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkBalances()
|
||||
{
|
||||
// find all clients where the balance doesn't equal the sum of the outstanding invoices
|
||||
$clients = DB::table('clients')
|
||||
->join('invoices', 'invoices.client_id', '=', 'clients.id')
|
||||
@ -249,8 +298,6 @@ class CheckData extends Command {
|
||||
->update($data);
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Done');
|
||||
}
|
||||
|
||||
protected function getArguments()
|
||||
|
@ -432,6 +432,11 @@ class AccountController extends BaseController
|
||||
{
|
||||
if (Auth::user()->account->isPro()) {
|
||||
$account = Auth::user()->account;
|
||||
$account->email_design_id = Input::get('email_design_id');
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
$account->enable_email_markup = Input::get('enable_email_markup') ? true : false;
|
||||
}
|
||||
|
||||
foreach ([ENTITY_INVOICE, ENTITY_QUOTE, ENTITY_PAYMENT, REMINDER1, REMINDER2, REMINDER3] as $type) {
|
||||
$subjectField = "email_subject_{$type}";
|
||||
|
@ -253,6 +253,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('ENTITY_CONTACT', 'contact');
|
||||
define('ENTITY_INVOICE', 'invoice');
|
||||
define('ENTITY_INVOICE_ITEMS', 'invoice_items');
|
||||
define('ENTITY_INVITATION', 'invitation');
|
||||
define('ENTITY_RECURRING_INVOICE', 'recurring_invoice');
|
||||
define('ENTITY_PAYMENT', 'payment');
|
||||
define('ENTITY_CREDIT', 'credit');
|
||||
@ -446,6 +447,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('PHANTOMJS_CLOUD', 'http://api.phantomjscloud.com/api/browser/v2/');
|
||||
define('PHP_DATE_FORMATS', 'http://php.net/manual/en/function.date.php');
|
||||
define('REFERRAL_PROGRAM_URL', 'https://www.invoiceninja.com/referral-program/');
|
||||
define('EMAIL_MARKUP_URL', 'https://developers.google.com/gmail/markup/overview');
|
||||
|
||||
define('COUNT_FREE_DESIGNS', 4);
|
||||
define('COUNT_FREE_DESIGNS_SELF_HOST', 5); // include the custom design
|
||||
@ -498,6 +500,9 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('API_SERIALIZER_ARRAY', 'array');
|
||||
define('API_SERIALIZER_JSON', 'json');
|
||||
|
||||
define('EMAIL_DESIGN_PLAIN', 1);
|
||||
define('FLAT_BUTTON_CSS', 'border:0 none;border-radius:6px;padding:12px 40px;margin:0 6px;cursor:hand;display:inline-block;font-size:14px;color:#fff;text-transform:none');
|
||||
|
||||
$creditCards = [
|
||||
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],
|
||||
2 => ['card' => 'images/credit_cards/Test-MasterCard-Icon.png', 'text' => 'Master Card'],
|
||||
@ -522,7 +527,6 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
'invoiceStatus' => 'App\Models\InvoiceStatus',
|
||||
'frequencies' => 'App\Models\Frequency',
|
||||
'gateways' => 'App\Models\Gateway',
|
||||
'themes' => 'App\Models\Theme',
|
||||
];
|
||||
define('CACHED_TABLES', serialize($cachedTables));
|
||||
|
||||
|
@ -279,9 +279,19 @@ class Utils
|
||||
return json_decode(json_encode((array) $data), true);
|
||||
}
|
||||
|
||||
public static function toSpaceCase($camelStr)
|
||||
public static function toSpaceCase($string)
|
||||
{
|
||||
return preg_replace('/([a-z])([A-Z])/s', '$1 $2', $camelStr);
|
||||
return preg_replace('/([a-z])([A-Z])/s', '$1 $2', $string);
|
||||
}
|
||||
|
||||
public static function toSnakeCase($string)
|
||||
{
|
||||
return preg_replace('/([a-z])([A-Z])/s', '$1_$2', $string);
|
||||
}
|
||||
|
||||
public static function toCamelCase($string)
|
||||
{
|
||||
return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $string))));
|
||||
}
|
||||
|
||||
public static function timestampToDateTimeString($timestamp)
|
||||
|
@ -5,6 +5,7 @@ use Utils;
|
||||
use Session;
|
||||
use DateTime;
|
||||
use Event;
|
||||
use Cache;
|
||||
use App;
|
||||
use File;
|
||||
use App\Events\UserSettingsChanged;
|
||||
@ -204,10 +205,26 @@ class Account extends Eloquent
|
||||
return $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT;
|
||||
}
|
||||
|
||||
public function formatMoney($amount, $client = null)
|
||||
{
|
||||
$currency = ($client && $client->currency) ? $client->currency : $this->currency;
|
||||
|
||||
if ( ! $currency) {
|
||||
$currencies = Cache::get('currencies')->filter(function($item) {
|
||||
return $item->id == DEFAULT_CURRENCY;
|
||||
});
|
||||
$currency = $currencies->first();
|
||||
}
|
||||
|
||||
return $currency->symbol . number_format($amount, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
|
||||
}
|
||||
|
||||
public function formatDate($date)
|
||||
{
|
||||
if (!$date) {
|
||||
if ( ! $date) {
|
||||
return null;
|
||||
} elseif ( ! $date instanceof \DateTime) {
|
||||
$date = new \DateTime($date);
|
||||
}
|
||||
|
||||
return $date->format($this->getCustomDateFormat());
|
||||
@ -677,9 +694,9 @@ class Account extends Eloquent
|
||||
$entityType = ENTITY_INVOICE;
|
||||
}
|
||||
|
||||
$template = "<div>\$client,</div><br/>" .
|
||||
"<div>" . trans("texts.{$entityType}_message", ['amount' => '$amount']) . "</div><br/>" .
|
||||
"<div><a href=\"\$link\">\$link</a></div><br/>";
|
||||
$template = "<div>\$client,</div><br>" .
|
||||
"<div>" . trans("texts.{$entityType}_message", ['amount' => '$amount']) . "</div><br>" .
|
||||
"<div><a href=\"\$viewLink\">\$viewLink</a></div><br>";
|
||||
|
||||
if ($message) {
|
||||
$template .= "$message<p/>\r\n\r\n";
|
||||
@ -693,13 +710,14 @@ class Account extends Eloquent
|
||||
if ($this->isPro()) {
|
||||
$field = "email_template_{$entityType}";
|
||||
$template = $this->$field;
|
||||
|
||||
if ($template) {
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getDefaultEmailTemplate($entityType, $message);
|
||||
if (!$template) {
|
||||
$template = $this->getDefaultEmailTemplate($entityType, $message);
|
||||
}
|
||||
|
||||
// <br/> is causing page breaks with the email designs
|
||||
return str_replace('/>', ' />', $template);
|
||||
}
|
||||
|
||||
public function getEmailFooter()
|
||||
@ -708,7 +726,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 "<p>" . trans('texts.email_signature') . "\n<br>\$account</p>";
|
||||
return "<p>" . trans('texts.email_signature') . "\n<br>\$account</ p>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use Eloquent;
|
||||
use Omnipay;
|
||||
use Utils;
|
||||
|
||||
class Gateway extends Eloquent
|
||||
{
|
||||
@ -44,13 +45,20 @@ class Gateway extends Eloquent
|
||||
return $this->id == $gatewayId;
|
||||
}
|
||||
|
||||
public static function getPaymentTypeName($type)
|
||||
{
|
||||
return Utils::toCamelCase(strtolower(str_replace('PAYMENT_TYPE_', '', $type)));
|
||||
}
|
||||
|
||||
/*
|
||||
public static function getPaymentTypeLinks() {
|
||||
$data = [];
|
||||
foreach (self::$paymentTypes as $type) {
|
||||
$data[] = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
|
||||
$data[] = Utils::toCamelCase(strtolower(str_replace('PAYMENT_TYPE_', '', $type)));
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
*/
|
||||
|
||||
public function getHelp()
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ class Invitation extends EntityModel
|
||||
return $this->belongsTo('App\Models\Account');
|
||||
}
|
||||
|
||||
public function getLink()
|
||||
public function getLink($type = 'view')
|
||||
{
|
||||
if (!$this->account) {
|
||||
$this->load('account');
|
||||
@ -46,7 +46,7 @@ class Invitation extends EntityModel
|
||||
}
|
||||
}
|
||||
|
||||
return "{$url}/view/{$this->invitation_key}";
|
||||
return "{$url}/{$type}/{$this->invitation_key}";
|
||||
}
|
||||
|
||||
public function getStatus()
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php namespace App\Ninja\Mailers;
|
||||
|
||||
use HTML;
|
||||
use Utils;
|
||||
use Event;
|
||||
use URL;
|
||||
@ -15,6 +16,21 @@ use App\Events\QuoteWasEmailed;
|
||||
|
||||
class ContactMailer extends Mailer
|
||||
{
|
||||
public static $variableFields = [
|
||||
'footer',
|
||||
'account',
|
||||
'client',
|
||||
'amount',
|
||||
'contact',
|
||||
'firstName',
|
||||
'invoice',
|
||||
'quote',
|
||||
'viewLink',
|
||||
'viewButton',
|
||||
'paymentLink',
|
||||
'paymentButton',
|
||||
];
|
||||
|
||||
public function sendInvoice(Invoice $invoice, $reminder = false, $pdfString = false)
|
||||
{
|
||||
$invoice->load('invitations', 'client.language', 'account');
|
||||
@ -97,6 +113,7 @@ class ContactMailer extends Mailer
|
||||
'invoiceId' => $invoice->id,
|
||||
'invitation' => $invitation,
|
||||
'account' => $account,
|
||||
'client' => $client,
|
||||
'invoice' => $invoice,
|
||||
];
|
||||
|
||||
@ -107,8 +124,14 @@ class ContactMailer extends Mailer
|
||||
|
||||
$subject = $this->processVariables($subject, $variables);
|
||||
$fromEmail = $user->email;
|
||||
|
||||
if ($account->email_design_id == EMAIL_DESIGN_PLAIN) {
|
||||
$view = ENTITY_INVOICE;
|
||||
} else {
|
||||
$view = 'design' . ($account->email_design_id - 1);
|
||||
}
|
||||
|
||||
$response = $this->sendTo($invitation->contact->email, $fromEmail, $account->getDisplayName(), $subject, ENTITY_INVOICE, $data);
|
||||
$response = $this->sendTo($invitation->contact->email, $fromEmail, $account->getDisplayName(), $subject, $view, $data);
|
||||
|
||||
if ($response === true) {
|
||||
return true;
|
||||
@ -192,22 +215,33 @@ class ContactMailer extends Mailer
|
||||
|
||||
private function processVariables($template, $data)
|
||||
{
|
||||
$account = $data['account'];
|
||||
$client = $data['client'];
|
||||
$invitation = $data['invitation'];
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
$variables = [
|
||||
'$footer' => $data['account']->getEmailFooter(),
|
||||
'$link' => $data['invitation']->getLink(),
|
||||
'$client' => $data['client']->getDisplayName(),
|
||||
'$account' => $data['account']->getDisplayName(),
|
||||
'$contact' => $data['invitation']->contact->getDisplayName(),
|
||||
'$firstName' => $data['invitation']->contact->first_name,
|
||||
'$amount' => Utils::formatMoney($data['amount'], $data['client']->getCurrencyId()),
|
||||
'$invoice' => $data['invitation']->invoice->invoice_number,
|
||||
'$quote' => $data['invitation']->invoice->invoice_number,
|
||||
'$advancedRawInvoice->' => '$'
|
||||
'$footer' => $account->getEmailFooter(),
|
||||
'$client' => $client->getDisplayName(),
|
||||
'$account' => $account->getDisplayName(),
|
||||
'$contact' => $invitation->contact->getDisplayName(),
|
||||
'$firstName' => $invitation->contact->first_name,
|
||||
'$amount' => Utils::formatMoney($data['amount'], $client->getCurrencyId()),
|
||||
'$invoice' => $invoice->invoice_number,
|
||||
'$quote' => $invoice->invoice_number,
|
||||
'$link' => $invitation->getLink(),
|
||||
'$viewLink' => $invitation->getLink(),
|
||||
'$viewButton' => HTML::emailViewButton($invitation->getLink(), $invoice->getEntityType()),
|
||||
'$paymentLink' => $invitation->getLink('payment'),
|
||||
'$paymentButton' => HTML::emailPaymentButton($invitation->getLink('payment')),
|
||||
];
|
||||
|
||||
// Add variables for available payment types
|
||||
foreach (Gateway::getPaymentTypeLinks() as $type) {
|
||||
$variables["\${$type}_link"] = URL::to("/payment/{$data['invitation']->invitation_key}/{$type}");
|
||||
foreach (Gateway::$paymentTypes as $type) {
|
||||
$camelType = Gateway::getPaymentTypeName($type);
|
||||
$type = Utils::toSnakeCase($camelType);
|
||||
$variables["\${$camelType}Link"] = $invitation->getLink() . "/{$type}";
|
||||
$variables["\${$camelType}Button"] = HTML::emailPaymentButton($invitation->getLink('payment') . "/{$type}");
|
||||
}
|
||||
|
||||
$str = str_replace(array_keys($variables), array_values($variables), $template);
|
||||
|
@ -67,7 +67,7 @@ class Mailer
|
||||
|
||||
private function handleFailure($exception)
|
||||
{
|
||||
if (isset($_ENV['POSTMARK_API_TOKEN']) && $exception->getResponse()) {
|
||||
if (isset($_ENV['POSTMARK_API_TOKEN']) && method_exists($exception, 'getResponse')) {
|
||||
$response = $exception->getResponse()->getBody()->getContents();
|
||||
$response = json_decode($response);
|
||||
$emailError = nl2br($response->Message);
|
||||
|
@ -15,6 +15,17 @@ class InvoicePresenter extends Presenter {
|
||||
return $this->entity->user->getDisplayName();
|
||||
}
|
||||
|
||||
public function balanceDueLabel()
|
||||
{
|
||||
if ($this->entity->partial) {
|
||||
return 'amount_due';
|
||||
} elseif ($this->entity->is_quote) {
|
||||
return 'total';
|
||||
} else {
|
||||
return 'balance_due';
|
||||
}
|
||||
}
|
||||
|
||||
public function balance_due()
|
||||
{
|
||||
$amount = $this->entity->getRequestedAmount();
|
||||
|
@ -44,7 +44,7 @@ class ActivityRepository
|
||||
{
|
||||
$activity = new Activity();
|
||||
|
||||
if (Auth::check()) {
|
||||
if (Auth::check() && Auth::user()->account_id == $entity->account_id) {
|
||||
$activity->user_id = Auth::user()->id;
|
||||
$activity->account_id = Auth::user()->account_id;
|
||||
} else {
|
||||
|
@ -65,6 +65,29 @@ class AppServiceProvider extends ServiceProvider {
|
||||
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents(public_path().'/'.$imagePath));
|
||||
});
|
||||
|
||||
HTML::macro('flatButton', function($label, $color) {
|
||||
return '<input type="button" value="' . trans("texts.{$label}") . '" style="background-color:' . $color . ';border:0 none;border-radius:5px;padding:12px 40px;margin:0 6px;cursor:hand;display:inline-block;font-size:14px;color:#fff;text-transform:none;font-weight:bold;"/>';
|
||||
});
|
||||
|
||||
HTML::macro('emailViewButton', function($link = '#', $entityType = ENTITY_INVOICE) {
|
||||
return view('partials.email_button')
|
||||
->with([
|
||||
'link' => $link,
|
||||
'field' => "view_{$entityType}",
|
||||
'color' => '#0b4d78',
|
||||
])
|
||||
->render();
|
||||
});
|
||||
|
||||
HTML::macro('emailPaymentButton', function($link = '#') {
|
||||
return view('partials.email_button')
|
||||
->with([
|
||||
'link' => $link,
|
||||
'field' => 'pay_now',
|
||||
'color' => '#36c157',
|
||||
])
|
||||
->render();
|
||||
});
|
||||
|
||||
HTML::macro('breadcrumbs', function() {
|
||||
$str = '<ol class="breadcrumb">';
|
||||
|
@ -40,7 +40,7 @@ class UserService extends BaseService
|
||||
[
|
||||
'first_name',
|
||||
function ($model) {
|
||||
return link_to('users/'.$model->public_id.'/edit', $model->first_name.' '.$model->last_name);
|
||||
return $model->public_id ? link_to('users/'.$model->public_id.'/edit', $model->first_name.' '.$model->last_name) : ($model->first_name.' '.$model->last_name);
|
||||
}
|
||||
],
|
||||
[
|
||||
|
@ -317,12 +317,12 @@ class ConfideSetupUsersTable extends Migration {
|
||||
$t->date('due_date')->nullable();
|
||||
$t->text('terms');
|
||||
$t->text('public_notes');
|
||||
$t->boolean('is_deleted')->default(false);
|
||||
$t->boolean('is_recurring');
|
||||
$t->boolean('is_deleted')->default(false);
|
||||
$t->boolean('is_recurring')->default(false);
|
||||
$t->unsignedInteger('frequency_id');
|
||||
$t->date('start_date')->nullable();
|
||||
$t->date('end_date')->nullable();
|
||||
$t->timestamp('last_sent_date')->nullable();
|
||||
$t->timestamp('last_sent_date')->nullable();
|
||||
$t->unsignedInteger('recurring_invoice_id')->index()->nullable();
|
||||
|
||||
$t->string('tax_name');
|
||||
|
34
database/migrations/2015_11_30_133206_add_email_designs.php
Normal file
34
database/migrations/2015_11_30_133206_add_email_designs.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddEmailDesigns extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->smallInteger('email_design_id')->default(1);
|
||||
$table->boolean('enable_email_markup')->default(false);
|
||||
$table->string('website')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function ($table) {
|
||||
$table->dropColumn('email_design_id');
|
||||
$table->dropColumn('enable_email_markup');
|
||||
$table->dropColumn('website');
|
||||
});
|
||||
}
|
||||
}
|
15
public/css/built.css
vendored
15
public/css/built.css
vendored
@ -3381,4 +3381,19 @@ ul.user-accounts a:hover div.remove {
|
||||
|
||||
div.panel-body div.panel-body {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.email-button {
|
||||
border: 0 none;
|
||||
border-radius: 36px;
|
||||
|
||||
padding: 12px 45px;
|
||||
margin: 0 10px;
|
||||
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
text-transform: none;
|
||||
}
|
@ -31758,7 +31758,7 @@ NINJA.decodeJavascript = function(invoice, javascript)
|
||||
} else if (invoice.is_quote && field == 'your_invoice') {
|
||||
field = 'your_quote';
|
||||
}
|
||||
var label = invoiceLabels[field];
|
||||
var label = invoiceLabels[field];
|
||||
if (match.indexOf('UC') >= 0) {
|
||||
label = label.toUpperCase();
|
||||
}
|
||||
|
@ -41,6 +41,10 @@ There are two options:
|
||||
* [Zapier](https://zapier.com/) integration
|
||||
* [D3.js](http://d3js.org/) visualizations
|
||||
|
||||
### Recommended Providers
|
||||
* [Stripe](https://stripe.com/)
|
||||
* [Postmark](https://postmarkapp.com/)
|
||||
|
||||
### Documentation
|
||||
* [Ubuntu and Apache](http://blog.technerdservices.com/index.php/2015/04/techpop-how-to-install-invoice-ninja-on-ubuntu-14-04/)
|
||||
* [Debian and Nginx](https://www.rosehosting.com/blog/install-invoice-ninja-on-a-debian-7-vps/)
|
||||
@ -55,10 +59,6 @@ There are two options:
|
||||
* [Jeramy Simpson](https://github.com/JeramyMywork) - [MyWork](https://www.mywork.com.au)
|
||||
* [Sigitas Limontas](https://lt.linkedin.com/in/sigitaslimontas)
|
||||
|
||||
### Recommended Providers
|
||||
* [Stripe](https://stripe.com/)
|
||||
* [Postmark](https://postmarkapp.com/)
|
||||
|
||||
### Frameworks/Libraries
|
||||
* [laravel/laravel](https://github.com/laravel/laravel) - A PHP Framework For Web Artisans
|
||||
* [twbs/bootstrap](https://github.com/twbs/bootstrap) - Sleek, intuitive, and powerful front-end framework for faster and easier web development.
|
||||
|
@ -953,5 +953,16 @@ return array(
|
||||
'secret_key' => 'Secret Key',
|
||||
'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process',
|
||||
|
||||
'email_design' => 'Email Design',
|
||||
'due_by' => 'Due by :date',
|
||||
'enable_email_markup' => 'Enable Email Markup',
|
||||
'enable_email_markup_help' => 'Make it easier for your clients to pay you by adding schema.org markup to your emails.',
|
||||
'template_help_title' => 'Templates Help',
|
||||
'template_help_1' => 'Available variables:',
|
||||
'email_design_id' => 'Email Design',
|
||||
'email_design_help' => 'Make your emails look more professional with HTML layouts',
|
||||
'plain' => 'Plain',
|
||||
'light' => 'Light',
|
||||
'dark' => 'Dark',
|
||||
|
||||
);
|
||||
|
@ -16,6 +16,8 @@
|
||||
<div class="pull-right"><a href="#" onclick="return resetText('{{ 'subject' }}', '{{ $field }}')">{{ trans("texts.reset") }}</a></div>
|
||||
{!! Former::text('email_subject_' . $field)
|
||||
->label(trans('texts.subject'))
|
||||
->appendIcon('question-sign')
|
||||
->addGroupClass('email-subject')
|
||||
->addClass('enable-' . $field) !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
@ -48,25 +50,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
var editor = new Quill('#{{ $field }}Editor', {
|
||||
modules: {
|
||||
'toolbar': { container: '#{{ $field }}Toolbar' },
|
||||
'link-tooltip': true
|
||||
},
|
||||
theme: 'snow'
|
||||
});
|
||||
editor.setHTML($('#email_template_{{ $field }}').val());
|
||||
editor.on('text-change', function(delta, source) {
|
||||
if (source == 'api') {
|
||||
return;
|
||||
}
|
||||
var html = editors['{{ $field }}'].getHTML();
|
||||
$('#email_template_{{ $field }}').val(html);
|
||||
refreshPreview();
|
||||
NINJA.formIsChanged = true;
|
||||
});
|
||||
editors['{{ $field }}'] = editor;
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
var editor = new Quill('#{{ $field }}Editor', {
|
||||
modules: {
|
||||
'toolbar': { container: '#{{ $field }}Toolbar' },
|
||||
'link-tooltip': true
|
||||
},
|
||||
theme: 'snow'
|
||||
});
|
||||
</script>
|
||||
editor.setHTML($('#email_template_{{ $field }}').val());
|
||||
editor.on('text-change', function(delta, source) {
|
||||
if (source == 'api') {
|
||||
return;
|
||||
}
|
||||
var html = editors['{{ $field }}'].getHTML();
|
||||
$('#email_template_{{ $field }}').val(html);
|
||||
refreshPreview();
|
||||
NINJA.formIsChanged = true;
|
||||
});
|
||||
editors['{{ $field }}'] = editor;
|
||||
});
|
||||
|
||||
$('.email-subject .input-group-addon').click(function() {
|
||||
$('#templateHelpModal').modal('show');
|
||||
});
|
||||
|
||||
</script>
|
@ -32,9 +32,35 @@
|
||||
@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)) !!}
|
||||
{!! Former::populateField('enable_reminder1', intval($account->enable_reminder1)) !!}
|
||||
{!! Former::populateField('enable_reminder2', intval($account->enable_reminder2)) !!}
|
||||
{!! Former::populateField('enable_reminder3', intval($account->enable_reminder3)) !!}
|
||||
{!! Former::populateField('enable_email_markup', intval($account->enable_email_markup)) !!}
|
||||
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{!! trans('texts.email_design') !!}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
{!! Former::select('email_design_id')
|
||||
->style('width: 200px')
|
||||
->addOption(trans('texts.plain'), 1)
|
||||
->addOption(trans('texts.light'), 2)
|
||||
->addOption(trans('texts.dark'), 3)
|
||||
->help(trans('texts.email_design_help')) !!}
|
||||
|
||||
@if (Utils::isNinja())
|
||||
|
||||
{!! Former::checkbox('enable_email_markup')
|
||||
->text(trans('texts.enable_email_markup'))
|
||||
->label('')
|
||||
->help(trans('texts.enable_email_markup_help')) !!}
|
||||
{!! link_to(EMAIL_MARKUP_URL, trans('texts.learn_more'), ['target' => '_blank']) !!}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
@ -83,6 +109,39 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="templateHelpModal" tabindex="-1" role="dialog" aria-labelledby="templateHelpModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" style="min-width:150px">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="templateHelpModalLabel">{{ trans('texts.template_help_title') }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>{{ trans('texts.template_help_1') }}</p>
|
||||
<ul>
|
||||
@foreach (\App\Ninja\Mailers\ContactMailer::$variableFields as $field)
|
||||
<li>${{ $field }}</li>
|
||||
@endforeach
|
||||
@if (count($account->account_gateways) > 1)
|
||||
@foreach (\App\Models\Gateway::$paymentTypes as $type)
|
||||
@if ($account->getGatewayByType($type))
|
||||
<li>${{ \App\Models\Gateway::getPaymentTypeName($type) }}Link</li>
|
||||
<li>${{ \App\Models\Gateway::getPaymentTypeName($type) }}Button</li>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" style="margin-top: 0px">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ trans('texts.close') }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Auth::user()->isPro())
|
||||
<center>
|
||||
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}
|
||||
@ -146,34 +205,29 @@
|
||||
return '';
|
||||
}
|
||||
|
||||
keys = [
|
||||
'footer',
|
||||
'account',
|
||||
'client',
|
||||
'amount',
|
||||
'link',
|
||||
'contact',
|
||||
'firstName',
|
||||
'invoice',
|
||||
'quote'
|
||||
];
|
||||
|
||||
vals = [
|
||||
var keys = {!! json_encode(\App\Ninja\Mailers\ContactMailer::$variableFields) !!};
|
||||
var vals = [
|
||||
{!! json_encode($emailFooter) !!},
|
||||
"{{ Auth::user()->account->getDisplayName() }}",
|
||||
"Client Name",
|
||||
formatMoney(100),
|
||||
"{{ Auth::user()->account->getSiteUrl() . '...' }}",
|
||||
"Contact Name",
|
||||
"First Name",
|
||||
"0001",
|
||||
"0001"
|
||||
"0001",
|
||||
"{{ URL::to('/view/...') }}",
|
||||
'{!! HTML::flatButton('view_invoice', '#0b4d78') !!}',
|
||||
"{{ URL::to('/payment/...') }}",
|
||||
'{!! HTML::flatButton('pay_now', '#36c157') !!}',
|
||||
];
|
||||
|
||||
// Add any available payment method links
|
||||
@foreach (\App\Models\Gateway::getPaymentTypeLinks() as $type)
|
||||
{!! "keys.push('" . $type.'_link' . "');" !!}
|
||||
{!! "vals.push('" . URL::to("/payment/xxxxxx/{$type}") . "');" !!}
|
||||
@foreach (\App\Models\Gateway::$paymentTypes as $type)
|
||||
{!! "keys.push('" . \App\Models\Gateway::getPaymentTypeName($type).'Link' . "');" !!}
|
||||
{!! "vals.push('" . URL::to('/payment/...') . "');" !!}
|
||||
|
||||
{!! "keys.push('" . \App\Models\Gateway::getPaymentTypeName($type).'Button' . "');" !!}
|
||||
{!! "vals.push('" . HTML::flatButton('pay_now', '#36c157') . "');" !!}
|
||||
@endforeach
|
||||
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
|
@ -1 +0,0 @@
|
||||
{!! nl2br($text) !!}
|
@ -1 +0,0 @@
|
||||
{!! $text !!}
|
46
resources/views/emails/design1_html.blade.php
Normal file
46
resources/views/emails/design1_html.blade.php
Normal file
@ -0,0 +1,46 @@
|
||||
@extends('emails.master')
|
||||
|
||||
@section('content')
|
||||
<tr>
|
||||
<td bgcolor="#F4F5F5" style="border-collapse: collapse;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border-collapse: collapse;">
|
||||
<table cellpadding="10" cellspacing="0" border="0" bgcolor="#F4F5F5" width="600" align="center"
|
||||
class="header" style="border-top-width: 6px; border-top-color: {{ $account->primary_color ?: '#2E2B2B' }}; border-top-style: solid;">
|
||||
<tr>
|
||||
<td class="logo" width="208" style="border-collapse: collapse; vertical-align: middle;" valign="middle">
|
||||
@include('emails.partials.account_logo')
|
||||
</td>
|
||||
<td width="183" style="border-collapse: collapse; vertical-align: middle;" valign="middle">
|
||||
<p class="left" style="line-height: 22px; margin: 0; padding: 2px 0 0;">
|
||||
<span style="font-size: 11px; color: #8f8d8e;">
|
||||
@if ($invoice->due_date)
|
||||
{{ strtoupper(trans('texts.due_by', ['date' => $account->formatDate($invoice->due_date)])) }}
|
||||
@endif
|
||||
</span><br />
|
||||
<span style="font-size: 18px;">
|
||||
{{ trans("texts.{$entityType}") }} {{ $invoice->invoice_number }}
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
<td style="border-collapse: collapse; vertical-align: middle;" valign="middle">
|
||||
<p class="right" style="line-height: 14px; margin: 0; padding: 0;">
|
||||
<span style="font-size: 15px; color: #231F20;">
|
||||
{{ trans('texts.' . $invoice->present()->balanceDueLabel) }}:
|
||||
</span><br />
|
||||
<span class="total" style="font-size: 26px; display: block;margin-top: 5px;">
|
||||
{{ $account->formatMoney($invoice->getRequestedAmount(), $client) }}
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content" style="border-collapse: collapse;">
|
||||
<div style="font-size: 18px; margin: 42px 40px 42px; padding: 0;">{!! $body !!}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@stop
|
1
resources/views/emails/design1_text.blade.php
Normal file
1
resources/views/emails/design1_text.blade.php
Normal file
@ -0,0 +1 @@
|
||||
{!! strip_tags($body) !!}
|
46
resources/views/emails/design2_html.blade.php
Normal file
46
resources/views/emails/design2_html.blade.php
Normal file
@ -0,0 +1,46 @@
|
||||
@extends('emails.master')
|
||||
|
||||
@section('content')
|
||||
<tr>
|
||||
<td bgcolor="#F4F5F5" style="border-collapse: collapse;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border-collapse: collapse;">
|
||||
<table cellpadding="10" cellspacing="0" border="0" bgcolor="{{ $account->primary_color ?: '#2E2B2B' }}" width="600" align="center" class="header"
|
||||
style="border-bottom-width: 6px; border-bottom-color: {{ $account->primary_color ?: '#2E2B2B' }}; border-bottom-style: solid;">
|
||||
<tr>
|
||||
<td class="logo" width="205" style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
|
||||
@include('emails.partials.account_logo')
|
||||
</td>
|
||||
<td width="183" style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
|
||||
<p class="left" style="line-height: 22px; margin: 3px 0 0; padding: 0;">
|
||||
<span style="font-size: 11px; color: #8f8d8e;">
|
||||
@if ($invoice->due_date)
|
||||
{{ strtoupper(trans('texts.due_by', ['date' => $account->formatDate($invoice->due_date)])) }}
|
||||
@endif
|
||||
</span><br />
|
||||
<span style="font-size: 19px; color: #FFFFFF;">
|
||||
{{ trans("texts.{$entityType}") }} {{ $invoice->invoice_number }}
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
<td style="border-collapse: collapse; vertical-align: middle; line-height: 16px;" valign="middle">
|
||||
<p style="margin: 0; padding: 0;">
|
||||
<span style="font-size: 12px; color: #8f8d8e;">
|
||||
{{ strtoupper(trans('texts.' . $invoice->present()->balanceDueLabel)) }}:
|
||||
</span><br />
|
||||
<span class="total" style="font-size: 27px; color: #FFFFFF; margin-top: 5px;display: block;">
|
||||
{{ $account->formatMoney($invoice->getRequestedAmount(), $client) }}
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content" style="border-collapse: collapse;">
|
||||
<div style="font-size: 18px; margin: 42px 40px 42px; padding: 0;">{!! $body !!}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@stop
|
1
resources/views/emails/design2_text.blade.php
Normal file
1
resources/views/emails/design2_text.blade.php
Normal file
@ -0,0 +1 @@
|
||||
{!! strip_tags($body) !!}
|
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
@if (Utils::isNinjaDev())
|
||||
@if ($account->enable_email_markup)
|
||||
@include('emails.view_action', ['link' => $link, 'entityType' => $entityType])
|
||||
@endif
|
||||
{!! $body !!}
|
||||
|
85
resources/views/emails/master.blade.php
Normal file
85
resources/views/emails/master.blade.php
Normal file
@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" lang="{{ App::getLocale() }}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
</head>
|
||||
|
||||
<body style="min-height: 700px; color: #000000; font-family: Arial, Helvetica, sans-serif; font-size: 12px; -webkit-text-size-adjust: none; -ms-text-size-adjust: none; background: #F4F5F5; margin: 0; padding: 0;"
|
||||
alink="#FF0000" link="#FF0000" bgcolor="#F4F5F5" text="#000000" yahoo="fix">
|
||||
@if ($account->enable_email_markup)
|
||||
@include('emails.view_action', ['link' => $link, 'entityType' => $entityType])
|
||||
@endif
|
||||
|
||||
<style type="text/css">
|
||||
.footer a:visited {
|
||||
font-weight: bold; font-size: 10px; color: #A7A6A6; text-decoration: none;
|
||||
}
|
||||
span.yshortcuts:hover {
|
||||
color: #000; background-color: none; border: none;
|
||||
}
|
||||
span.yshortcuts:active {
|
||||
color: #000; background-color: none; border: none;
|
||||
}
|
||||
span.yshortcuts:focus {
|
||||
color: #000; background-color: none; border: none;
|
||||
}
|
||||
a:visited {
|
||||
color: #19BB40; text-decoration: none;
|
||||
}
|
||||
a:focus {
|
||||
color: #19BB40; text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
color: #19BB40; text-decoration: underline;
|
||||
}
|
||||
@media only screen and (max-device-width: 480px) {
|
||||
body[yahoo] #container1 {
|
||||
display: block !important;
|
||||
}
|
||||
body[yahoo] p {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
body[yahoo] #container1 {
|
||||
display: block !important;
|
||||
}
|
||||
body[yahoo] p {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="body_style" style="min-height: 700px; color: #2E2B2B; font-family: Helvetica, sans-serif; font-size: 16px;
|
||||
background: #F4F5F5; padding: 0px 15px;">
|
||||
|
||||
<table cellpadding="0" cellspacing="0" border="0" bgcolor="#FFFFFF" width="600" align="center">
|
||||
|
||||
@yield('content')
|
||||
|
||||
<tr class="footer" style="text-align: center; color: #a7a6a6;" align="center">
|
||||
<td bgcolor="#F4F5F5"
|
||||
style="border-collapse: collapse; padding-top: 32px;">
|
||||
<p style="color: #A7A6A6; font-size: 13px; line-height: 18px; margin: 0 0 7px; padding: 0;">
|
||||
{{ $account->address1 }}
|
||||
@if ($account->address1 && $account->getCityState())
|
||||
-
|
||||
@endif
|
||||
{{ $account->getCityState() }}
|
||||
@if ($account->address1 || $account->getCityState())
|
||||
<br />
|
||||
@endif
|
||||
|
||||
@if ($account->website)
|
||||
<strong><a href="{{ $account->website }}" style="color: #A7A6A6; text-decoration: none; font-weight: bold; font-size: 10px;">{{ $account->website }}</a></strong>
|
||||
@endif
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
11
resources/views/emails/partials/account_logo.blade.php
Normal file
11
resources/views/emails/partials/account_logo.blade.php
Normal file
@ -0,0 +1,11 @@
|
||||
@if ($account->hasLogo())
|
||||
@if ($account->website)
|
||||
<a href="{{ $account->website }}" style="color: #19BB40; text-decoration: underline;">
|
||||
@endif
|
||||
|
||||
<img src="{{ $message->embed($account->getLogoPath()) }}" style="max-height:50px; max-width:140px; margin-left: 33px;" />
|
||||
|
||||
@if ($account->website)
|
||||
</a>
|
||||
@endif
|
||||
@endif
|
22
resources/views/partials/email_button.blade.php
Normal file
22
resources/views/partials/email_button.blade.php
Normal file
@ -0,0 +1,22 @@
|
||||
<!-- https://gist.github.com/elidickinson/9424116#file-html_email_buttons_1-html -->
|
||||
<div style="display:inline-block;width:190px">
|
||||
<!--[if mso]>
|
||||
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="{{ $link }}" style="height:44px;v-text-anchor:middle;width:180px;" arcsize="10%" stroke="f" fillcolor="{{ $color }}">
|
||||
<w:anchorlock/>
|
||||
<center style="color:#ffffff;font-family:sans-serif;font-size:16px;font-weight:bold;">
|
||||
{{ trans("texts.{$field}") }}
|
||||
</center>
|
||||
</v:roundrect>
|
||||
<![endif]-->
|
||||
<![if !mso]>
|
||||
<table cellspacing="0" cellpadding="0"> <tr>
|
||||
<td align="center" width="180" height="44" bgcolor="{{ $color }}" style="-webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; color: #ffffff; display: block;">
|
||||
<a href="{{ $link }}" style="font-size:16px; font-weight: bold; font-family:sans-serif; text-decoration: none; line-height:44px; width:100%; display:inline-block">
|
||||
<span style="color: #ffffff;">
|
||||
{{ trans("texts.{$field}") }}
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr> </table>
|
||||
<![endif]>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user