diff --git a/Gruntfile.js b/Gruntfile.js
index 2eedc311faa2..9213e01d255a 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -66,10 +66,14 @@ module.exports = function(grunt) {
},
js_public: {
src: [
+ /*
'public/js/simpleexpand.js',
'public/js/valign.js',
'public/js/bootstrap.min.js',
'public/js/simpleexpand.js',
+ */
+ 'public/vendor/bootstrap/dist/js/bootstrap.min.js',
+
],
dest: 'public/js/built.public.js',
nonull: true
@@ -97,8 +101,10 @@ module.exports = function(grunt) {
css_public: {
src: [
'public/vendor/bootstrap/dist/css/bootstrap.min.css',
+ /*
'public/css/bootstrap.splash.css',
'public/css/splash.css',
+ */
'public/vendor/datatables/media/css/jquery.dataTables.css',
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
],
diff --git a/LICENSE b/LICENSE
index 81e4a1266c5c..eaa9f1e3672c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -13,7 +13,8 @@ open-source software.
1. Redistributions of source code, in whole or part and with or without
modification requires the express permission of the author and must prominently
-display "Powered by InvoiceNinja" in verifiable form with hyperlink to said site.
+display "Powered by InvoiceNinja" or the Invoice Ninja logo in verifiable form
+with hyperlink to said site.
2. Neither the name nor any trademark of the Author may be used to
endorse or promote products derived from this software without specific
prior written permission.
diff --git a/README.md b/README.md
index 0d88ab5a55ba..be9da430c5a8 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ Create database user and a database for ninja
Add public/ to your web server root then load / to configure the application.
-### Deleveloper Notes
+### Developer Notes
* The application requires PHP >= 5.4.0
* If you make any changes to the JavaScript files you need to run grunt to create the built files. See Gruntfile.js for more details.
@@ -115,4 +115,4 @@ Add public/ to your web server root then load / to configure the application.
* [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) - List of languages for Laravel4
* [calvinfroedge/PHP-Payments](https://github.com/calvinfroedge/PHP-Payments) - A uniform payments interface for PHP
* [bgrins/spectrum](https://github.com/bgrins/spectrum) - The No Hassle JavaScript Colorpicker
-* [lokesh/lightbox2](https://github.com/lokesh/lightbox2/) - The original lightbox script
\ No newline at end of file
+* [lokesh/lightbox2](https://github.com/lokesh/lightbox2/) - The original lightbox script
diff --git a/app/commands/CheckData.php b/app/commands/CheckData.php
new file mode 100644
index 000000000000..f2a0610f632a
--- /dev/null
+++ b/app/commands/CheckData.php
@@ -0,0 +1,266 @@
+
+
+ Limits the script to a single client
+
+--fix=true
+
+ By default the script only checks for errors, adding this option
+ makes the script apply the fixes.
+
+*/
+
+
+class CheckData extends Command {
+
+ protected $name = 'ninja:check-data';
+ protected $description = 'Check/fix data';
+
+ public function fire()
+ {
+ $this->info(date('Y-m-d') . ' Running CheckData...');
+ $today = new DateTime();
+
+ if (!$this->option('client_id')) {
+ // update client deletion activities with the client's current balance
+ $activities = DB::table('activities')
+ ->join('clients', 'clients.id', '=', 'activities.client_id')
+ ->where('activities.activity_type_id', '=', ACTIVITY_TYPE_DELETE_CLIENT)
+ ->where('activities.balance', '=', 0)
+ ->where('clients.balance', '!=', 0)
+ ->get(['activities.id', 'clients.balance']);
+
+ $this->info(count($activities) . ' delete client activities with zero balance');
+
+ if ($this->option('fix') == 'true') {
+ foreach ($activities as $activity) {
+ DB::table('activities')
+ ->where('id', $activity->id)
+ ->update(['balance' => $activity->balance]);
+ }
+ }
+
+ // 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]);
+ }
+ }
+ }
+
+ // 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')
+ ->join('accounts', 'accounts.id', '=', 'clients.account_id');
+
+ if ($this->option('client_id')) {
+ $clients->where('clients.id', '=', $this->option('client_id'));
+ } else {
+ $clients->where('invoices.is_deleted', '=', 0)
+ ->where('invoices.is_quote', '=', 0)
+ ->where('invoices.is_recurring', '=', 0)
+ ->havingRaw('abs(clients.balance - sum(invoices.balance)) > .01 and clients.balance != 999999999.9999');
+ }
+
+ $clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at')
+ ->orderBy('clients.id', 'DESC')
+ ->get(['clients.id', 'clients.balance', 'clients.paid_to_date']);
+ $this->info(count($clients) . ' clients with incorrect balance/activities');
+
+ foreach ($clients as $client) {
+ $this->info("=== Client:{$client->id} Balance:{$client->balance} ===");
+ $foundProblem = false;
+ $lastBalance = 0;
+ $clientFix = false;
+ $activities = DB::table('activities')
+ ->where('client_id', '=', $client->id)
+ ->orderBy('activities.id')
+ ->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.message', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']);
+ //$this->info(var_dump($activities));
+
+ foreach ($activities as $activity) {
+
+ $activityFix = false;
+
+ if ($activity->invoice_id) {
+ $invoice = DB::table('invoices')
+ ->where('id', '=', $activity->invoice_id)
+ ->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')
+ ->where('recurring_invoice_id', '=', $activity->invoice_id)
+ ->first(['invoices.id'])) {
+ $invoice->is_recurring = 1;
+
+ // **Fix for enabling a recurring invoice to be set as non-recurring**
+ if ($this->option('fix') == 'true') {
+ DB::table('invoices')
+ ->where('id', $invoice->id)
+ ->update(['is_recurring' => 1]);
+ }
+ }
+ }
+
+
+ if ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_INVOICE
+ || $activity->activity_type_id == ACTIVITY_TYPE_CREATE_QUOTE) {
+
+ // Get original invoice amount
+ $update = DB::table('activities')
+ ->where('invoice_id', '=', $activity->invoice_id)
+ ->where('activity_type_id', '=', ACTIVITY_TYPE_UPDATE_INVOICE)
+ ->orderBy('id')
+ ->first(['json_backup']);
+ if ($update) {
+ $backup = json_decode($update->json_backup);
+ $invoice->amount = floatval($backup->amount);
+ }
+
+ $noAdjustment = $activity->activity_type_id == ACTIVITY_TYPE_CREATE_INVOICE
+ && $activity->adjustment == 0
+ && $invoice->amount > 0;
+
+ // **Fix for allowing converting a recurring invoice to a normal one without updating the balance**
+ if ($noAdjustment && !$invoice->is_quote && !$invoice->is_recurring) {
+ $this->info("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} isQuote:{$invoice->is_quote} isRecurring:{$invoice->is_recurring}");
+ $foundProblem = true;
+ $clientFix += $invoice->amount;
+ $activityFix = $invoice->amount;
+ // **Fix for updating balance when creating a quote or recurring invoice**
+ } elseif ($activity->adjustment != 0 && ($invoice->is_quote || $invoice->is_recurring)) {
+ $this->info("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} isQuote:{$invoice->is_quote} isRecurring:{$invoice->is_recurring}");
+ $foundProblem = true;
+ $clientFix -= $activity->adjustment;
+ $activityFix = 0;
+ }
+ } elseif ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_INVOICE) {
+ // **Fix for updating balance when deleting a recurring invoice**
+ if ($activity->adjustment != 0 && $invoice->is_recurring) {
+ $this->info("Incorrect adjustment for deleted invoice adjustment:{$activity->adjustment}");
+ $foundProblem = true;
+ if ($activity->balance != $lastBalance) {
+ $clientFix -= $activity->adjustment;
+ }
+ $activityFix = 0;
+ }
+ } elseif ($activity->activity_type_id == ACTIVITY_TYPE_ARCHIVE_INVOICE) {
+ // **Fix for updating balance when archiving an invoice**
+ if ($activity->adjustment != 0 && !$invoice->is_recurring) {
+ $this->info("Incorrect adjustment for archiving invoice adjustment:{$activity->adjustment}");
+ $foundProblem = true;
+ $activityFix = 0;
+ $clientFix += $activity->adjustment;
+ }
+ } elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_INVOICE) {
+ // **Fix for updating balance when updating recurring invoice**
+ if ($activity->adjustment != 0 && $invoice->is_recurring) {
+ $this->info("Incorrect adjustment for updated recurring invoice adjustment:{$activity->adjustment}");
+ $foundProblem = true;
+ $clientFix -= $activity->adjustment;
+ $activityFix = 0;
+ }
+ } elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_QUOTE) {
+ // **Fix for updating balance when updating a quote**
+ if ($activity->balance != $lastBalance) {
+ $this->info("Incorrect adjustment for updated quote adjustment:{$activity->adjustment}");
+ $foundProblem = true;
+ $clientFix += $lastBalance - $activity->balance;
+ $activityFix = 0;
+ }
+ } else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) {
+ // **Fix for delting payment after deleting invoice**
+ if ($activity->adjustment != 0 && $invoice->is_deleted && $activity->created_at > $invoice->deleted_at) {
+ $this->info("Incorrect adjustment for deleted payment adjustment:{$activity->adjustment}");
+ $foundProblem = true;
+ $activityFix = 0;
+ $clientFix -= $activity->adjustment;
+ }
+ }
+
+ if ($activityFix !== false || $clientFix !== false) {
+ $data = [
+ 'balance' => $activity->balance + $clientFix
+ ];
+
+ if ($activityFix !== false) {
+ $data['adjustment'] = $activityFix;
+ }
+
+ if ($this->option('fix') == 'true') {
+ DB::table('activities')
+ ->where('id', $activity->id)
+ ->update($data);
+ }
+ }
+
+ $lastBalance = $activity->balance;
+ }
+
+ if ($clientFix !== false) {
+ $balance = $activity->balance + $clientFix;
+ $data = ['balance' => $balance];
+ $this->info("Corrected balance:{$balance}");
+ if ($this->option('fix') == 'true') {
+ DB::table('clients')
+ ->where('id', $client->id)
+ ->update($data);
+ }
+ }
+ }
+
+ $this->info('Done');
+ }
+
+ protected function getArguments()
+ {
+ return array(
+ //array('example', InputArgument::REQUIRED, 'An example argument.'),
+ );
+ }
+
+ protected function getOptions()
+ {
+ return array(
+ array('fix', null, InputOption::VALUE_OPTIONAL, 'Fix data', null),
+ array('client_id', null, InputOption::VALUE_OPTIONAL, 'Client id', null),
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/app/commands/SendRecurringInvoices.php b/app/commands/SendRecurringInvoices.php
index a3e86af027c9..b527e7ae48bd 100755
--- a/app/commands/SendRecurringInvoices.php
+++ b/app/commands/SendRecurringInvoices.php
@@ -5,113 +5,109 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use ninja\mailers\ContactMailer as Mailer;
-class SendRecurringInvoices extends Command {
+class SendRecurringInvoices extends Command
+{
+ protected $name = 'ninja:send-invoices';
+ protected $description = 'Send recurring invoices';
+ protected $mailer;
- protected $name = 'ninja:send-invoices';
- protected $description = 'Send recurring invoices';
- protected $mailer;
+ public function __construct(Mailer $mailer)
+ {
+ parent::__construct();
- public function __construct(Mailer $mailer)
- {
- parent::__construct();
+ $this->mailer = $mailer;
+ }
- $this->mailer = $mailer;
- }
+ public function fire()
+ {
+ $this->info(date('Y-m-d').' Running SendRecurringInvoices...');
+ $today = new DateTime();
- public function fire()
- {
- $this->info(date('Y-m-d') . ' Running SendRecurringInvoices...');
- $today = new DateTime();
-
- $invoices = Invoice::with('account.timezone', 'invoice_items', 'client')
- ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))->get();
- $this->info(count($invoices) . ' recurring invoice(s) found');
+ $invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user')
+ ->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))->get();
+ $this->info(count($invoices).' recurring invoice(s) found');
- foreach ($invoices as $recurInvoice)
- {
- if ($recurInvoice->client->deleted_at)
- {
- continue;
- }
+ foreach ($invoices as $recurInvoice) {
+ if ($recurInvoice->client->deleted_at) {
+ continue;
+ }
- date_default_timezone_set($recurInvoice->account->getTimezone());
-
- $this->info('Processing Invoice ' . $recurInvoice->id . ' - Should send ' . ($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
-
- if (!$recurInvoice->shouldSendToday())
- {
- continue;
- }
-
- $invoice = Invoice::createNew($recurInvoice);
- $invoice->client_id = $recurInvoice->client_id;
- $invoice->recurring_invoice_id = $recurInvoice->id;
- $invoice->invoice_number = 'R' . $recurInvoice->account->getNextInvoiceNumber();
- $invoice->amount = $recurInvoice->amount;
- $invoice->balance = $recurInvoice->amount;
- $invoice->invoice_date = date_create()->format('Y-m-d');
- $invoice->discount = $recurInvoice->discount;
- $invoice->po_number = $recurInvoice->po_number;
- $invoice->public_notes = $recurInvoice->public_notes;
- $invoice->terms = $recurInvoice->terms;
- $invoice->tax_name = $recurInvoice->tax_name;
- $invoice->tax_rate = $recurInvoice->tax_rate;
- $invoice->invoice_design_id = $recurInvoice->invoice_design_id;
- $invoice->custom_value1 = $recurInvoice->custom_value1;
- $invoice->custom_value2 = $recurInvoice->custom_value2;
- $invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
- $invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
- $invoice->is_amount_discount = $recurInvoice->is_amount_discount;
+ if (!$recurInvoice->user->confirmed) {
+ continue;
+ }
- if ($invoice->client->payment_terms)
- {
- $invoice->due_date = date_create()->modify($invoice->client->payment_terms . ' day')->format('Y-m-d');
- }
-
- $invoice->save();
-
- foreach ($recurInvoice->invoice_items as $recurItem)
- {
- $item = InvoiceItem::createNew($recurItem);
- $item->product_id = $recurItem->product_id;
- $item->qty = $recurItem->qty;
- $item->cost = $recurItem->cost;
- $item->notes = Utils::processVariables($recurItem->notes);
- $item->product_key = Utils::processVariables($recurItem->product_key);
- $item->tax_name = $recurItem->tax_name;
- $item->tax_rate = $recurItem->tax_rate;
- $invoice->invoice_items()->save($item);
- }
+ $this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
- foreach ($recurInvoice->invitations as $recurInvitation)
- {
- $invitation = Invitation::createNew($recurInvitation);
- $invitation->contact_id = $recurInvitation->contact_id;
- $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
- $invoice->invitations()->save($invitation);
- }
+ if (!$recurInvoice->shouldSendToday()) {
+ continue;
+ }
- $this->mailer->sendInvoice($invoice);
+ $invoice = Invoice::createNew($recurInvoice);
+ $invoice->client_id = $recurInvoice->client_id;
+ $invoice->recurring_invoice_id = $recurInvoice->id;
+ $invoice->invoice_number = 'R'.$recurInvoice->account->getNextInvoiceNumber();
+ $invoice->amount = $recurInvoice->amount;
+ $invoice->balance = $recurInvoice->amount;
+ $invoice->invoice_date = date_create()->format('Y-m-d');
+ $invoice->discount = $recurInvoice->discount;
+ $invoice->po_number = $recurInvoice->po_number;
+ $invoice->public_notes = $recurInvoice->public_notes;
+ $invoice->terms = $recurInvoice->terms;
+ $invoice->invoice_footer = $recurInvoice->invoice_footer;
+ $invoice->tax_name = $recurInvoice->tax_name;
+ $invoice->tax_rate = $recurInvoice->tax_rate;
+ $invoice->invoice_design_id = $recurInvoice->invoice_design_id;
+ $invoice->custom_value1 = $recurInvoice->custom_value1;
+ $invoice->custom_value2 = $recurInvoice->custom_value2;
+ $invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
+ $invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
+ $invoice->is_amount_discount = $recurInvoice->is_amount_discount;
- $recurInvoice->last_sent_date = Carbon::now()->toDateTimeString();
- $recurInvoice->save();
- }
+ if ($invoice->client->payment_terms) {
+ $invoice->due_date = date_create()->modify($invoice->client->payment_terms.' day')->format('Y-m-d');
+ }
- $this->info('Done');
- }
+ $invoice->save();
- protected function getArguments()
- {
- return array(
- //array('example', InputArgument::REQUIRED, 'An example argument.'),
- );
- }
+ foreach ($recurInvoice->invoice_items as $recurItem) {
+ $item = InvoiceItem::createNew($recurItem);
+ $item->product_id = $recurItem->product_id;
+ $item->qty = $recurItem->qty;
+ $item->cost = $recurItem->cost;
+ $item->notes = Utils::processVariables($recurItem->notes);
+ $item->product_key = Utils::processVariables($recurItem->product_key);
+ $item->tax_name = $recurItem->tax_name;
+ $item->tax_rate = $recurItem->tax_rate;
+ $invoice->invoice_items()->save($item);
+ }
- protected function getOptions()
- {
- return array(
- //array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
- );
- }
+ foreach ($recurInvoice->invitations as $recurInvitation) {
+ $invitation = Invitation::createNew($recurInvitation);
+ $invitation->contact_id = $recurInvitation->contact_id;
+ $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
+ $invoice->invitations()->save($invitation);
+ }
-}
\ No newline at end of file
+ $this->mailer->sendInvoice($invoice);
+
+ $recurInvoice->last_sent_date = Carbon::now()->toDateTimeString();
+ $recurInvoice->save();
+ }
+
+ $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/config/mail.php b/app/config/mail.php
index 9840e66e9967..62dac9c3a572 100755
--- a/app/config/mail.php
+++ b/app/config/mail.php
@@ -28,7 +28,7 @@ return array(
|
*/
- //'host' => '',
+ 'host' => '',
/*
|--------------------------------------------------------------------------
diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php
index cceb1853bbb4..2fb4e0e39dc7 100755
--- a/app/controllers/AccountController.php
+++ b/app/controllers/AccountController.php
@@ -87,6 +87,8 @@ class AccountController extends \BaseController
if ($entityType == 'user') {
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_USER_MANAGEMENT);
+ } elseif ($entityType == 'token') {
+ return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_TOKEN_MANAGEMENT);
} else {
return Redirect::to("{$entityType}s");
}
@@ -184,6 +186,11 @@ class AccountController extends \BaseController
}
}
+ $tokenBillingOptions = [];
+ for ($i=1; $i<=4; $i++) {
+ $tokenBillingOptions[$i] = trans("texts.token_billing_{$i}");
+ }
+
$data = [
'account' => $account,
'accountGateway' => $accountGateway,
@@ -195,7 +202,8 @@ class AccountController extends \BaseController
->orderBy('name')
->get(),
'recommendedGateways' => $recommendedGatewayArray,
- 'creditCardTypes' => $creditCards,
+ 'creditCardTypes' => $creditCards,
+ 'tokenBillingOptions' => $tokenBillingOptions,
];
return View::make('accounts.payments', $data);
@@ -591,6 +599,7 @@ class AccountController extends \BaseController
{
$account = Auth::user()->account;
$account->invoice_terms = Input::get('invoice_terms');
+ $account->invoice_footer = Input::get('invoice_footer');
$account->email_footer = Input::get('email_footer');
$account->save();
@@ -663,10 +672,22 @@ class AccountController extends \BaseController
}
}
- if ($isMasked && count($account->account_gateways)) {
+ // check if a gateway is already configured
+ $currentGateway = false;
+ if (count($account->account_gateways)) {
$currentGateway = $account->account_gateways[0];
+ }
+
+ // if the values haven't changed don't update the config
+ if ($isMasked && $currentGateway) {
$currentGateway->accepted_credit_cards = $cardCount;
$currentGateway->save();
+ // if there's an existing config for this gateway update it
+ } elseif (!$isMasked && $currentGateway && $currentGateway->gateway_id == $gatewayId) {
+ $currentGateway->accepted_credit_cards = $cardCount;
+ $currentGateway->config = json_encode($config);
+ $currentGateway->save();
+ // otherwise, create a new gateway config
} else {
$accountGateway->config = json_encode($config);
$accountGateway->accepted_credit_cards = $cardCount;
@@ -675,6 +696,11 @@ class AccountController extends \BaseController
$account->account_gateways()->save($accountGateway);
}
+ if (Input::get('token_billing_type_id')) {
+ $account->token_billing_type_id = Input::get('token_billing_type_id');
+ $account->save();
+ }
+
Session::flash('message', trans('texts.updated_settings'));
} else {
Session::flash('error', trans('validation.required', ['attribute' => 'gateway']));
diff --git a/app/controllers/ClientApiController.php b/app/controllers/ClientApiController.php
index 2d76dfab108b..138a92aabcdb 100644
--- a/app/controllers/ClientApiController.php
+++ b/app/controllers/ClientApiController.php
@@ -20,10 +20,6 @@ class ClientApiController extends Controller
public function index()
{
- if (!Utils::isPro()) {
- return Redirect::to('/');
- }
-
$clients = Client::scope()->with('contacts')->orderBy('created_at', 'desc')->get();
$clients = Utils::remapPublicIds($clients->toArray());
@@ -35,10 +31,6 @@ class ClientApiController extends Controller
public function store()
{
- if (!Utils::isPro()) {
- return Redirect::to('/');
- }
-
$data = Input::all();
$error = $this->clientRepo->getErrors($data);
@@ -48,6 +40,8 @@ class ClientApiController extends Controller
return Response::make($error, 500, $headers);
} else {
$client = $this->clientRepo->save(false, $data, false);
+ $client->load('contacts');
+ $client = Utils::remapPublicIds($client->toArray());
$response = json_encode($client, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
diff --git a/app/controllers/ClientController.php b/app/controllers/ClientController.php
index ea697d185320..a06b6c5bdc8e 100755
--- a/app/controllers/ClientController.php
+++ b/app/controllers/ClientController.php
@@ -106,6 +106,7 @@ class ClientController extends \BaseController
'credit' => $client->getTotalCredit(),
'title' => trans('texts.view_client'),
'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0,
+ 'gatewayLink' => $client->getGatewayLink(),
);
return View::make('clients.show', $data);
diff --git a/app/controllers/HomeController.php b/app/controllers/HomeController.php
index 419aeb41d8b7..27e8a9983fd7 100755
--- a/app/controllers/HomeController.php
+++ b/app/controllers/HomeController.php
@@ -15,101 +15,20 @@ class HomeController extends BaseController
public function showIndex()
{
- if (Utils::isNinja()) {
- return View::make('public.splash');
+ if (!Utils::isDatabaseSetup()) {
+ return Redirect::to('/setup');
+ } elseif (Account::count() == 0) {
+ return Redirect::to('/invoice_now');
} else {
- if (!Utils::isDatabaseSetup()) {
- return Redirect::to('/setup');
- } elseif (Account::count() == 0) {
- return Redirect::to('/invoice_now');
- } else {
- return Redirect::to('/login');
- }
+ return Redirect::to('/login');
}
}
- public function showAboutUs()
- {
- $data = [
- 'title' => 'About Us',
- 'description' => 'Invoice Ninja is an an open-source solution where you can create, customize, and generate invoices online for free using our templates!',
- ];
-
- return View::make('public.about_us', $data);
- }
-
- public function showContactUs()
- {
- $data = [
- 'title' => 'Contact Us',
- 'description' => 'Contact us today and try out our free or premium hassle-free plans. Start your online invoicing today with Invoice Ninja!',
- ];
-
- return View::make('public.contact_us', $data);
- }
-
public function showTerms()
{
- return View::make('public.terms');
+ return View::make('public.terms', ['hideHeader' => true]);
}
-
- public function showFaq()
- {
- return View::make('public.faq');
- }
-
- public function showFeatures()
- {
- return View::make('public.features');
- }
-
- public function showPlans()
- {
- $data = [
- 'title' => 'Professional Invoicing Software & Templates',
- 'description' => 'Invoice Ninja allows you to create and generate your own custom invoices. Choose from our professional invoice templates or customize your own with our pro plan.',
- ];
-
- return View::make('public.plans', $data);
- }
- public function showTestimonials()
- {
- return View::make('public.testimonials');
- }
-
- public function doContactUs()
- {
- $email = Input::get('email');
- $name = Input::get('name');
- $message = Input::get('message');
-
- $data = [
- 'text' => $message,
- ];
-
- $this->mailer->sendTo(CONTACT_EMAIL, $email, $name, 'Invoice Ninja Feedback', 'contact', $data);
-
- $message = trans('texts.sent_message');
- Session::flash('message', $message);
-
- return View::make('public.contact_us');
- }
-
- public function showComingSoon()
- {
- return View::make('coming_soon');
- }
-
- public function showSecurePayment()
- {
- return View::make('secure_payment');
- }
-
- public function showCompare()
- {
- return View::make('public.compare');
- }
-
+
public function invoiceNow()
{
if (Auth::check()) {
diff --git a/app/controllers/InvoiceApiController.php b/app/controllers/InvoiceApiController.php
index 10081da8ca37..40c63ea30d9c 100644
--- a/app/controllers/InvoiceApiController.php
+++ b/app/controllers/InvoiceApiController.php
@@ -1,22 +1,20 @@
invoiceRepo = $invoiceRepo;
+ $this->mailer = $mailer;
}
public function index()
{
- if (!Utils::isPro()) {
- return Redirect::to('/');
- }
-
$invoices = Invoice::scope()->where('invoices.is_quote', '=', false)->orderBy('created_at', 'desc')->get();
$invoices = Utils::remapPublicIds($invoices->toArray());
@@ -26,15 +24,169 @@ class InvoiceApiController extends Controller
return Response::make($response, 200, $headers);
}
- /*
- public function store()
- {
- $data = Input::all();
- $invoice = $this->invoiceRepo->save(false, $data, false);
+ public function store()
+ {
+ $data = Input::all();
+ $error = null;
+
+ // check if the invoice number is set and unique
+ if (!isset($data['invoice_number'])) {
+ $data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber();
+ } else {
+ $invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first();
+ if ($invoice) {
+ $error = trans('validation.unique', ['attribute' => 'texts.invoice_number']);
+ }
+ }
- $response = json_encode($invoice, JSON_PRETTY_PRINT);
- $headers = Utils::getApiHeaders();
- return Response::make($response, 200, $headers);
- }
- */
+ // check the client id is set and exists
+ if (!isset($data['client_id'])) {
+ $error = trans('validation.required', ['attribute' => 'client_id']);
+ } else {
+ $client = Client::scope($data['client_id'])->first();
+ if (!$client) {
+ $error = trans('validation.not_in', ['attribute' => 'client_id']);
+ }
+ }
+
+ if ($error) {
+ $response = json_encode($error, JSON_PRETTY_PRINT);
+ } else {
+ $data = self::prepareData($data);
+ $invoice = $this->invoiceRepo->save(false, $data, false);
+
+ $invitation = Invitation::createNew();
+ $invitation->invoice_id = $invoice->id;
+ $invitation->contact_id = $client->contacts[0]->id;
+ $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
+ $invitation->save();
+
+ // prepare the return data
+ $invoice->load('invoice_items');
+ $invoice = $invoice->toArray();
+ $invoice['link'] = $invitation->getLink();
+ unset($invoice['account']);
+ unset($invoice['client']);
+ $invoice = Utils::remapPublicIds($invoice);
+ $invoice['client_id'] = $client->public_id;
+
+ $response = json_encode($invoice, JSON_PRETTY_PRINT);
+ }
+
+ $headers = Utils::getApiHeaders();
+
+ return Response::make($response, $error ? 400 : 200, $headers);
+ }
+
+ private function prepareData($data)
+ {
+ $account = Auth::user()->account;
+ $account->loadLocalizationSettings();
+
+ // set defaults for optional fields
+ $fields = [
+ 'discount' => 0,
+ 'is_amount_discount' => false,
+ 'terms' => '',
+ 'invoice_footer' => '',
+ 'public_notes' => '',
+ 'po_number' => '',
+ 'invoice_design_id' => $account->invoice_design_id,
+ 'invoice_items' => [],
+ 'custom_value1' => 0,
+ 'custom_value2' => 0,
+ 'custom_taxes1' => false,
+ 'custom_taxes2' => false,
+ ];
+
+ if (!isset($data['invoice_date'])) {
+ $fields['invoice_date_sql'] = date_create()->format('Y-m-d');
+ }
+ if (!isset($data['due_date'])) {
+ $fields['due_date_sql'] = false;
+ }
+
+ foreach ($fields as $key => $val) {
+ if (!isset($data[$key])) {
+ $data[$key] = $val;
+ }
+ }
+
+ // hardcode some fields
+ $fields = [
+ 'is_recurring' => false
+ ];
+
+ foreach ($fields as $key => $val) {
+ $data[$key] = $val;
+ }
+
+ // initialize the line items
+ if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
+ $data['invoice_items'] = [self::prepareItem($data)];
+ } else {
+ foreach ($data['invoice_items'] as $index => $item) {
+ $data['invoice_items'][$index] = self::prepareItem($item);
+ }
+ }
+
+ return $data;
+ }
+
+ private function prepareItem($item)
+ {
+ $fields = [
+ 'cost' => 0,
+ 'product_key' => '',
+ 'notes' => '',
+ 'qty' => 1
+ ];
+
+ foreach ($fields as $key => $val) {
+ if (!isset($item[$key])) {
+ $item[$key] = $val;
+ }
+ }
+
+ // if only the product key is set we'll load the cost and notes
+ if ($item['product_key'] && (!$item['cost'] || !$item['notes'])) {
+ $product = Product::findProductByKey($item['product_key']);
+ if ($product) {
+ if (!$item['cost']) {
+ $item['cost'] = $product->cost;
+ }
+ if (!$item['notes']) {
+ $item['notes'] = $product->notes;
+ }
+ }
+ }
+
+ return $item;
+ }
+
+ public function emailInvoice()
+ {
+ $data = Input::all();
+ $error = null;
+
+ if (!isset($data['id'])) {
+ $error = trans('validation.required', ['attribute' => 'id']);
+ } else {
+ $invoice = Invoice::scope($data['id'])->first();
+ if (!$invoice) {
+ $error = trans('validation.not_in', ['attribute' => 'id']);
+ } else {
+ $this->mailer->sendInvoice($invoice);
+ }
+ }
+
+ if ($error) {
+ $response = json_encode($error, JSON_PRETTY_PRINT);
+ } else {
+ $response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
+ }
+
+ $headers = Utils::getApiHeaders();
+ return Response::make($response, $error ? 400 : 200, $headers);
+ }
}
diff --git a/app/controllers/InvoiceController.php b/app/controllers/InvoiceController.php
index a46df2de6012..e41dfd98775f 100755
--- a/app/controllers/InvoiceController.php
+++ b/app/controllers/InvoiceController.php
@@ -46,8 +46,16 @@ class InvoiceController extends \BaseController
public function clientIndex()
{
+ $invitationKey = Session::get('invitation_key');
+ if (!$invitationKey) {
+ return Redirect::to('/setup');
+ }
+
+ $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
+ $color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
+
$data = [
- 'showClientHeader' => true,
+ 'color' => $color,
'hideLogo' => Session::get('white_label'),
'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE,
@@ -68,7 +76,7 @@ class InvoiceController extends \BaseController
public function getClientDatatable()
{
//$accountId = Auth::user()->account_id;
- $search = Input::get('sSearch');
+ $search = Input::get('sSearch');
$invitationKey = Session::get('invitation_key');
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
@@ -133,23 +141,13 @@ class InvoiceController extends \BaseController
public function view($invitationKey)
{
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->firstOrFail();
-
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return View::make('invoices.deleted');
}
- if ($invoice->is_quote && $invoice->quote_invoice_id) {
- $invoice = Invoice::scope($invoice->quote_invoice_id, $invoice->account_id)->firstOrFail();
-
- if (!$invoice || $invoice->is_deleted) {
- return View::make('invoices.deleted');
- }
- }
-
$invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country');
-
$client = $invoice->client;
if (!$client || $client->is_deleted) {
@@ -169,7 +167,7 @@ class InvoiceController extends \BaseController
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
- $invoice->is_pro = $client->account->isPro();
+ $invoice->is_pro = $client->account->isPro();
$contact = $invitation->contact;
$contact->setVisible([
@@ -179,13 +177,14 @@ class InvoiceController extends \BaseController
'phone', ]);
$data = array(
- 'showClientHeader' => true,
+ 'isConverted' => $invoice->quote_invoice_id ? true : false,
'showBreadcrumbs' => false,
'hideLogo' => $client->account->isWhiteLabel(),
'invoice' => $invoice->hidePrivateFields(),
'invitation' => $invitation,
'invoiceLabels' => $client->account->getInvoiceLabels(),
'contact' => $contact,
+ 'hasToken' => $client->getGatewayToken()
);
return View::make('invoices.view', $data);
diff --git a/app/controllers/PaymentApiController.php b/app/controllers/PaymentApiController.php
index 410069f09c66..46db0d353d2b 100644
--- a/app/controllers/PaymentApiController.php
+++ b/app/controllers/PaymentApiController.php
@@ -13,10 +13,6 @@ class PaymentApiController extends Controller
public function index()
{
- if (!Utils::isPro()) {
- return Redirect::to('/');
- }
-
$payments = Payment::scope()->orderBy('created_at', 'desc')->get();
$payments = Utils::remapPublicIds($payments->toArray());
diff --git a/app/controllers/PaymentController.php b/app/controllers/PaymentController.php
index 5dc5a2f93662..f94a93a3fcf6 100755
--- a/app/controllers/PaymentController.php
+++ b/app/controllers/PaymentController.php
@@ -30,13 +30,23 @@ class PaymentController extends \BaseController
public function clientIndex()
{
- return View::make('public_list', array(
- 'showClientHeader' => true,
+ $invitationKey = Session::get('invitation_key');
+ if (!$invitationKey) {
+ return Redirect::to('/setup');
+ }
+
+ $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
+ $color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
+
+ $data = [
+ 'color' => $color,
'hideLogo' => Session::get('white_label'),
'entityType' => ENTITY_PAYMENT,
'title' => trans('texts.payments'),
- 'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date']),
- ));
+ 'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
+ ];
+
+ return View::make('public_list', $data);
}
public function getDatatable($clientPublicId = null)
@@ -60,7 +70,7 @@ class PaymentController extends \BaseController
return $table->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
->addColumn('dropdown', function ($model) {
- if ($model->is_deleted) {
+ if ($model->is_deleted || $model->invoice_is_deleted) {
return '
';
}
@@ -237,7 +247,7 @@ class PaymentController extends \BaseController
'city' => $input['city'],
'state' => $input['state'],
'postal_code' => $input['postal_code'],
- 'amt' => $invoice->amount,
+ 'amt' => $invoice->balance,
'ship_to_street' => $input['address1'],
'ship_to_city' => $input['city'],
'ship_to_state' => $input['state'],
@@ -274,7 +284,7 @@ class PaymentController extends \BaseController
$card = new CreditCard($data);
return [
- 'amount' => $invoice->amount,
+ 'amount' => $invoice->balance,
'card' => $card,
'currency' => $currencyCode,
'returnUrl' => URL::to('complete'),
@@ -287,14 +297,16 @@ class PaymentController extends \BaseController
public function show_payment($invitationKey)
{
+ // Handle token billing
+ if (Input::get('use_token') == 'true') {
+ return self::do_payment($invitationKey, false, true);
+ }
// For PayPal Express we redirect straight to their site
$invitation = Invitation::with('invoice.client.account', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$account = $invitation->invoice->client->account;
-
if ($account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
if (Session::has('error')) {
Session::reflash();
-
return Redirect::to('view/'.$invitationKey);
} else {
return self::do_payment($invitationKey, false);
@@ -311,9 +323,9 @@ class PaymentController extends \BaseController
$data = [
'showBreadcrumbs' => false,
- 'hideHeader' => true,
'url' => 'payment/'.$invitationKey,
- 'amount' => $invoice->amount,
+ 'amount' => $invoice->balance,
+ 'invoiceNumber' => $invoice->invoice_number,
'client' => $client,
'contact' => $invitation->contact,
'paymentLibrary' => $paymentLibrary,
@@ -321,6 +333,7 @@ class PaymentController extends \BaseController
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'currencyId' => $client->currency_id,
+ 'account' => $client->account
];
return View::make('payments.payment', $data);
@@ -486,7 +499,7 @@ class PaymentController extends \BaseController
}
}
- public function do_payment($invitationKey, $onSite = true)
+ public function do_payment($invitationKey, $onSite = true, $useToken = false)
{
$rules = array(
'first_name' => 'required',
@@ -512,11 +525,12 @@ class PaymentController extends \BaseController
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
- $accountGateway = $invoice->client->account->account_gateways[0];
+ $client = $invoice->client;
+ $account = $client->account;
+ $accountGateway = $account->account_gateways[0];
$paymentLibrary = $accountGateway->gateway->paymentlibrary;
if ($onSite) {
- $client = $invoice->client;
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city'));
@@ -528,7 +542,32 @@ class PaymentController extends \BaseController
try {
if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
$gateway = self::createGateway($accountGateway);
- $details = self::getPaymentDetails($invoice, Input::all());
+ $details = self::getPaymentDetails($invoice, $useToken ? false : Input::all());
+
+ if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
+ if ($useToken) {
+ $details['cardReference'] = $client->getGatewayToken();
+ } elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) {
+ $tokenResponse = $gateway->createCard($details)->send();
+ $cardReference = $tokenResponse->getCardReference();
+ $details['cardReference'] = $cardReference;
+
+ $token = AccountGatewayToken::where('client_id', '=', $client->id)
+ ->where('account_gateway_id', '=', $accountGateway->id)->first();
+
+ if (!$token) {
+ $token = new AccountGatewayToken();
+ $token->account_id = $account->id;
+ $token->contact_id = $invitation->contact_id;
+ $token->account_gateway_id = $accountGateway->id;
+ $token->client_id = $client->id;
+ }
+
+ $token->token = $cardReference;
+ $token->save();
+ }
+ }
+
$response = $gateway->purchase($details)->send();
$ref = $response->getTransactionReference();
@@ -541,7 +580,6 @@ class PaymentController extends \BaseController
if ($response->isSuccessful()) {
$payment = self::createPayment($invitation, $ref);
-
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/'.$payment->invitation->invitation_key);
@@ -595,7 +633,7 @@ class PaymentController extends \BaseController
}
} catch (\Exception $e) {
$errorMessage = trans('texts.payment_error');
- Session::flash('error', $errorMessage);
+ Session::flash('error', $errorMessage."".$e->getMessage());
Utils::logError(Utils::getErrorString($e));
return Redirect::to('payment/'.$invitationKey)
@@ -614,15 +652,11 @@ class PaymentController extends \BaseController
$account->save();
}
- if ($invoice->is_quote) {
- $invoice = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
- }
-
$payment = Payment::createNew($invitation);
$payment->invitation_id = $invitation->id;
$payment->account_gateway_id = $accountGateway->id;
$payment->invoice_id = $invoice->id;
- $payment->amount = $invoice->amount;
+ $payment->amount = $invoice->balance;
$payment->client_id = $invoice->client_id;
$payment->contact_id = $invitation->contact_id;
$payment->transaction_reference = $ref;
@@ -697,12 +731,14 @@ class PaymentController extends \BaseController
->withInput();
} else {
$this->paymentRepo->save($publicId, Input::all());
-
+
if ($publicId) {
Session::flash('message', trans('texts.updated_payment'));
+
return Redirect::to('payments/');
} else {
Session::flash('message', trans('texts.created_payment'));
+
return Redirect::to('clients/'.Input::get('client'));
}
}
diff --git a/app/controllers/QuoteApiController.php b/app/controllers/QuoteApiController.php
index 5b442516d088..713f92997cb6 100644
--- a/app/controllers/QuoteApiController.php
+++ b/app/controllers/QuoteApiController.php
@@ -13,10 +13,6 @@ class QuoteApiController extends Controller
public function index()
{
- if (!Utils::isPro()) {
- return Redirect::to('/');
- }
-
$invoices = Invoice::scope()->where('invoices.is_quote', '=', true)->orderBy('created_at', 'desc')->get();
$invoices = Utils::remapPublicIds($invoices->toArray());
diff --git a/app/controllers/QuoteController.php b/app/controllers/QuoteController.php
index 9a12dc7fa54d..199d6b1130a9 100644
--- a/app/controllers/QuoteController.php
+++ b/app/controllers/QuoteController.php
@@ -29,10 +29,10 @@ class QuoteController extends \BaseController
}
$data = [
- 'title' => trans('texts.quotes'),
- 'entityType' => ENTITY_QUOTE,
- 'columns' => Utils::trans(['checkbox', 'quote_number', 'client', 'quote_date', 'quote_total', 'due_date', 'status', 'action']),
- ];
+ 'title' => trans('texts.quotes'),
+ 'entityType' => ENTITY_QUOTE,
+ 'columns' => Utils::trans(['checkbox', 'quote_number', 'client', 'quote_date', 'quote_total', 'due_date', 'status', 'action']),
+ ];
/*
if (Invoice::scope()->where('is_recurring', '=', true)->count() > 0)
@@ -47,13 +47,21 @@ class QuoteController extends \BaseController
public function clientIndex()
{
+ $invitationKey = Session::get('invitation_key');
+ if (!$invitationKey) {
+ return Redirect::to('/setup');
+ }
+
+ $invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
+ $color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
+
$data = [
- 'showClientHeader' => true,
- 'hideLogo' => Session::get('white_label'),
- 'title' => trans('texts.quotes'),
- 'entityType' => ENTITY_QUOTE,
- 'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
- ];
+ 'color' => $color,
+ 'hideLogo' => Session::get('white_label'),
+ 'title' => trans('texts.quotes'),
+ 'entityType' => ENTITY_QUOTE,
+ 'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
+ ];
return View::make('public_list', $data);
}
@@ -141,7 +149,7 @@ class QuoteController extends \BaseController
$clone = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
Session::flash('message', trans('texts.converted_to_invoice'));
- return Redirect::to('invoices/'.$clone->public_id);
+ return Redirect::to('invoices/'.$clone->public_id);
}
$statusId = Input::get('statusId');
@@ -156,4 +164,24 @@ class QuoteController extends \BaseController
return Redirect::to('quotes');
}
+
+ public function approve($invitationKey)
+ {
+ $invitation = Invitation::with('invoice.invoice_items', 'invoice.invitations')->where('invitation_key', '=', $invitationKey)->firstOrFail();
+ $invoice = $invitation->invoice;
+
+ if ($invoice->is_quote && !$invoice->quote_invoice_id) {
+ Activity::approveQuote($invitation);
+ $invoice = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
+ Session::flash('message', trans('texts.converted_to_invoice'));
+
+ foreach ($invoice->invitations as $invitationClone) {
+ if ($invitation->contact_id == $invitationClone->contact_id) {
+ $invitationKey = $invitationClone->invitation_key;
+ }
+ }
+ }
+
+ return Redirect::to("view/{$invitationKey}");
+ }
}
diff --git a/app/controllers/ReportController.php b/app/controllers/ReportController.php
index 0dd36d1a0316..9000b8165444 100755
--- a/app/controllers/ReportController.php
+++ b/app/controllers/ReportController.php
@@ -52,7 +52,7 @@ class ReportController extends \BaseController
$records = DB::table($entityType.'s')
->select(DB::raw('sum(amount) as total, '.$groupBy.'('.$entityType.'_date) as '.$groupBy))
->where('account_id', '=', Auth::user()->account_id)
- ->where($entityType.'s.deleted_at', '=', null)
+ ->where($entityType.'s.is_deleted', '=', false)
->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d'))
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d'))
->groupBy($groupBy);
diff --git a/app/controllers/TokenController.php b/app/controllers/TokenController.php
new file mode 100755
index 000000000000..ba7924d49479
--- /dev/null
+++ b/app/controllers/TokenController.php
@@ -0,0 +1,161 @@
+where('account_tokens.account_id', '=', Auth::user()->account_id);
+
+ if (!Session::get('show_trash:token')) {
+ $query->where('account_tokens.deleted_at', '=', null);
+ }
+
+ $query->select('account_tokens.public_id', 'account_tokens.name', 'account_tokens.token', 'account_tokens.public_id', 'account_tokens.deleted_at');
+
+ return Datatable::query($query)
+ ->addColumn('name', function ($model) { return link_to('tokens/'.$model->public_id.'/edit', $model->name); })
+ ->addColumn('token', function ($model) { return $model->token; })
+ ->addColumn('dropdown', function ($model) {
+ $actions = '
+
+
+
';
+
+ return $actions;
+ })
+ ->orderColumns(['name', 'token'])
+ ->make();
+ }
+
+ public function edit($publicId)
+ {
+ $token = AccountToken::where('account_id', '=', Auth::user()->account_id)
+ ->where('public_id', '=', $publicId)->firstOrFail();
+
+ $data = [
+ 'showBreadcrumbs' => false,
+ 'token' => $token,
+ 'method' => 'PUT',
+ 'url' => 'tokens/'.$publicId,
+ 'title' => trans('texts.edit_token'),
+ ];
+
+ return View::make('accounts.token', $data);
+ }
+
+ public function update($publicId)
+ {
+ return $this->save($publicId);
+ }
+
+ public function store()
+ {
+ return $this->save();
+ }
+
+ /**
+ * Displays the form for account creation
+ *
+ */
+ public function create()
+ {
+ if (!Auth::user()->confirmed) {
+ Session::flash('error', trans('texts.register_to_add_user'));
+ return Redirect::to('company/advanced_settings/user_management');
+ }
+
+ $data = [
+ 'showBreadcrumbs' => false,
+ 'token' => null,
+ 'method' => 'POST',
+ 'url' => 'tokens',
+ 'title' => trans('texts.add_token'),
+ ];
+
+ return View::make('accounts.token', $data);
+ }
+
+ public function delete()
+ {
+ $tokenPublicId = Input::get('tokenPublicId');
+ $token = AccountToken::where('account_id', '=', Auth::user()->account_id)
+ ->where('public_id', '=', $tokenPublicId)->firstOrFail();
+
+ $token->delete();
+
+ Session::flash('message', trans('texts.deleted_token'));
+
+ return Redirect::to('company/advanced_settings/token_management');
+ }
+
+ /**
+ * Stores new account
+ *
+ */
+ public function save($tokenPublicId = false)
+ {
+ if (Auth::user()->account->isPro()) {
+ $rules = [
+ 'name' => 'required',
+ ];
+
+ if ($tokenPublicId) {
+ $token = AccountToken::where('account_id', '=', Auth::user()->account_id)
+ ->where('public_id', '=', $tokenPublicId)->firstOrFail();
+ }
+
+ $validator = Validator::make(Input::all(), $rules);
+
+ if ($validator->fails()) {
+ return Redirect::to($tokenPublicId ? 'tokens/edit' : 'tokens/create')->withInput()->withErrors($validator);
+ }
+
+ if ($tokenPublicId) {
+ $token->name = trim(Input::get('name'));
+ } else {
+ $lastToken = AccountToken::withTrashed()->where('account_id', '=', Auth::user()->account_id)
+ ->orderBy('public_id', 'DESC')->first();
+
+ $token = AccountToken::createNew();
+ $token->name = trim(Input::get('name'));
+ $token->token = str_random(RANDOM_KEY_LENGTH);
+ $token->public_id = $lastToken ? $lastToken->public_id + 1 : 1;
+ }
+
+ $token->save();
+
+ if ($tokenPublicId) {
+ $message = trans('texts.updated_token');
+ } else {
+ $message = trans('texts.created_token');
+ }
+
+ Session::flash('message', $message);
+ }
+
+ return Redirect::to('company/advanced_settings/token_management');
+ }
+
+}
diff --git a/app/controllers/UserController.php b/app/controllers/UserController.php
index ec6b701c29e3..931eae529052 100755
--- a/app/controllers/UserController.php
+++ b/app/controllers/UserController.php
@@ -183,58 +183,60 @@ class UserController extends BaseController
*/
public function save($userPublicId = false)
{
- $rules = [
- 'first_name' => 'required',
- 'last_name' => 'required',
- ];
+ if (Auth::user()->account->isPro()) {
+ $rules = [
+ 'first_name' => 'required',
+ 'last_name' => 'required',
+ ];
- if ($userPublicId) {
- $user = User::where('account_id', '=', Auth::user()->account_id)
- ->where('public_id', '=', $userPublicId)->firstOrFail();
+ if ($userPublicId) {
+ $user = User::where('account_id', '=', Auth::user()->account_id)
+ ->where('public_id', '=', $userPublicId)->firstOrFail();
- $rules['email'] = 'required|email|unique:users,email,'.$user->id.',id';
- } else {
- $rules['email'] = 'required|email|unique:users';
+ $rules['email'] = 'required|email|unique:users,email,'.$user->id.',id';
+ } else {
+ $rules['email'] = 'required|email|unique:users';
+ }
+
+ $validator = Validator::make(Input::all(), $rules);
+
+ if ($validator->fails()) {
+ return Redirect::to($userPublicId ? 'users/edit' : 'users/create')->withInput()->withErrors($validator);
+ }
+
+ if ($userPublicId) {
+ $user->first_name = trim(Input::get('first_name'));
+ $user->last_name = trim(Input::get('last_name'));
+ $user->username = trim(Input::get('email'));
+ $user->email = trim(Input::get('email'));
+ } else {
+ $lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
+ ->orderBy('public_id', 'DESC')->first();
+
+ $user = new User();
+ $user->account_id = Auth::user()->account_id;
+ $user->first_name = trim(Input::get('first_name'));
+ $user->last_name = trim(Input::get('last_name'));
+ $user->username = trim(Input::get('email'));
+ $user->email = trim(Input::get('email'));
+ $user->registered = true;
+ $user->password = str_random(RANDOM_KEY_LENGTH);
+ $user->password_confirmation = $user->password;
+ $user->public_id = $lastUser->public_id + 1;
+ }
+
+ $user->save();
+
+ if (!$user->confirmed) {
+ $this->userMailer->sendConfirmation($user, Auth::user());
+ $message = trans('texts.sent_invite');
+ } else {
+ $message = trans('texts.updated_user');
+ }
+
+ Session::flash('message', $message);
}
-
- $validator = Validator::make(Input::all(), $rules);
-
- if ($validator->fails()) {
- return Redirect::to($userPublicId ? 'users/edit' : 'users/create')->withInput()->withErrors($validator);
- }
-
- if ($userPublicId) {
- $user->first_name = trim(Input::get('first_name'));
- $user->last_name = trim(Input::get('last_name'));
- $user->username = trim(Input::get('email'));
- $user->email = trim(Input::get('email'));
- } else {
- $lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
- ->orderBy('public_id', 'DESC')->first();
-
- $user = new User();
- $user->account_id = Auth::user()->account_id;
- $user->first_name = trim(Input::get('first_name'));
- $user->last_name = trim(Input::get('last_name'));
- $user->username = trim(Input::get('email'));
- $user->email = trim(Input::get('email'));
- $user->registered = true;
- $user->password = str_random(RANDOM_KEY_LENGTH);
- $user->password_confirmation = $user->password;
- $user->public_id = $lastUser->public_id + 1;
- }
-
- $user->save();
-
- if (!$user->confirmed) {
- $this->userMailer->sendConfirmation($user, Auth::user());
- $message = trans('texts.sent_invite');
- } else {
- $message = trans('texts.updated_user');
- }
-
- Session::flash('message', $message);
-
+
return Redirect::to('company/advanced_settings/user_management');
}
diff --git a/app/database/migrations/2015_02_17_131714_support_token_billing.php b/app/database/migrations/2015_02_17_131714_support_token_billing.php
new file mode 100644
index 000000000000..0022d44cf2ef
--- /dev/null
+++ b/app/database/migrations/2015_02_17_131714_support_token_billing.php
@@ -0,0 +1,56 @@
+smallInteger('token_billing_type_id')->default(TOKEN_BILLING_OPT_IN);
+ });
+
+ Schema::create('account_gateway_tokens', function($table)
+ {
+ $table->increments('id');
+ $table->unsignedInteger('account_id');
+ $table->unsignedInteger('contact_id');
+ $table->unsignedInteger('account_gateway_id');
+ $table->unsignedInteger('client_id');
+ $table->string('token');
+
+ $table->timestamps();
+ $table->softDeletes();
+
+ $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
+ $table->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
+ $table->foreign('account_gateway_id')->references('id')->on('account_gateways')->onDelete('cascade');
+ $table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
+ });
+
+ DB::table('accounts')->update(['token_billing_type_id' => TOKEN_BILLING_OPT_IN]);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('accounts', function($table)
+ {
+ $table->dropColumn('token_billing_type_id');
+ });
+
+ Schema::drop('account_gateway_tokens');
+ }
+
+}
\ No newline at end of file
diff --git a/app/database/migrations/2015_02_27_081836_add_invoice_footer.php b/app/database/migrations/2015_02_27_081836_add_invoice_footer.php
new file mode 100644
index 000000000000..6d1cfb7a9efa
--- /dev/null
+++ b/app/database/migrations/2015_02_27_081836_add_invoice_footer.php
@@ -0,0 +1,44 @@
+text('invoice_footer')->nullable();
+ });
+
+ Schema::table('invoices', function($table)
+ {
+ $table->text('invoice_footer')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('accounts', function($table)
+ {
+ $table->dropColumn('invoice_footer');
+ });
+
+ Schema::table('invoices', function($table)
+ {
+ $table->dropColumn('invoice_footer');
+ });
+ }
+
+}
diff --git a/app/database/migrations/2015_03_03_140259_add_tokens.php b/app/database/migrations/2015_03_03_140259_add_tokens.php
new file mode 100644
index 000000000000..45b54e3ca4f8
--- /dev/null
+++ b/app/database/migrations/2015_03_03_140259_add_tokens.php
@@ -0,0 +1,54 @@
+increments('id');
+ $table->unsignedInteger('account_id')->index();
+ $table->unsignedInteger('user_id');
+ $table->timestamps();
+ $table->softDeletes();
+
+ $table->string('name')->nullable();
+ $table->string('token')->unique();
+
+ $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
+ $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
+
+ $table->unsignedInteger('public_id')->nullable();
+ $table->unique(['account_id', 'public_id']);
+ });
+
+ Schema::table('activities', function($table)
+ {
+ $table->unsignedInteger('token_id')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('account_tokens');
+
+ Schema::table('activities', function($table)
+ {
+ $table->dropColumn('token_id');
+ });
+ }
+
+}
diff --git a/app/database/seeds/ConstantsSeeder.php b/app/database/seeds/ConstantsSeeder.php
index b519c7349f54..53e3a26cf6d8 100755
--- a/app/database/seeds/ConstantsSeeder.php
+++ b/app/database/seeds/ConstantsSeeder.php
@@ -138,6 +138,8 @@ class ConstantsSeeder extends Seeder
Currency::create(array('name' => 'Norske Kroner', 'code' => 'NOK', 'symbol' => 'kr ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'New Zealand Dollar', 'code' => 'NZD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'));
Currency::create(array('name' => 'Vietnamese Dong', 'code' => 'VND', 'symbol' => 'VND ', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'));
+ Currency::create(array('name' => 'Swiss Franc', 'code' => 'CHF', 'symbol' => 'CHF ', 'precision' => '2', 'thousand_separator' => '\'', 'decimal_separator' => '.'));
+
DatetimeFormat::create(array('format' => 'd/M/Y g:i a', 'label' => '10/Mar/2013'));
DatetimeFormat::create(array('format' => 'd-M-Yk g:i a', 'label' => '10-Mar-2013'));
diff --git a/app/filters.php b/app/filters.php
index 74ee30ac001c..db20fa951eb8 100755
--- a/app/filters.php
+++ b/app/filters.php
@@ -173,6 +173,57 @@ Route::filter('auth.basic', function()
return Auth::basic();
});
+Route::filter('api.access', function()
+{
+ $headers = Utils::getApiHeaders();
+
+ // check for a valid token
+ $token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
+
+ if ($token) {
+ Auth::loginUsingId($token->user_id);
+ Session::set('token_id', $token->id);
+ } else {
+ return Response::make('Invalid token', 403, $headers);
+ }
+
+ if (!Utils::isPro()) {
+ return Response::make('API requires pro plan', 403, $headers);
+ } else {
+ $accountId = Auth::user()->account->id;
+
+ // http://stackoverflow.com/questions/1375501/how-do-i-throttle-my-sites-api-users
+ $hour = 60 * 60;
+ $hour_limit = 100; # users are limited to 100 requests/hour
+ $hour_throttle = Cache::get("hour_throttle:{$accountId}", null);
+ $last_api_request = Cache::get("last_api_request:{$accountId}", 0);
+ $last_api_diff = time() - $last_api_request;
+
+ if (is_null($hour_throttle)) {
+ $new_hour_throttle = 0;
+ } else {
+ $new_hour_throttle = $hour_throttle - $last_api_diff;
+ $new_hour_throttle = $new_hour_throttle < 0 ? 0 : $new_hour_throttle;
+ $new_hour_throttle += $hour / $hour_limit;
+ $hour_hits_remaining = floor(( $hour - $new_hour_throttle ) * $hour_limit / $hour);
+ $hour_hits_remaining = $hour_hits_remaining >= 0 ? $hour_hits_remaining : 0;
+ }
+
+ if ($new_hour_throttle > $hour) {
+ $wait = ceil($new_hour_throttle - $hour);
+ sleep(1);
+ return Response::make("Please wait {$wait} second(s)", 403, $headers);
+ }
+
+ Cache::put("hour_throttle:{$accountId}", $new_hour_throttle, 10);
+ Cache::put("last_api_request:{$accountId}", time(), 10);
+ }
+
+ return null;
+});
+
+
+
/*
|--------------------------------------------------------------------------
| Guest Filter
diff --git a/app/handlers/InvoiceEventHandler.php b/app/handlers/InvoiceEventHandler.php
index 243d096dcd0b..7b6f4e9cc52b 100755
--- a/app/handlers/InvoiceEventHandler.php
+++ b/app/handlers/InvoiceEventHandler.php
@@ -44,7 +44,7 @@ class InvoiceEventHandler
{
if ($user->{'notify_' . $type})
{
- $this->userMailer->sendNotification($user, $invoice, $type, $payment);
+ $this->userMailer->sendNotification($user, $invoice, $type, $payment);
}
}
}
diff --git a/app/handlers/UserEventHandler.php b/app/handlers/UserEventHandler.php
index 5e6a24955287..19ee6f5de936 100755
--- a/app/handlers/UserEventHandler.php
+++ b/app/handlers/UserEventHandler.php
@@ -2,30 +2,29 @@
class UserEventHandler
{
- public function subscribe($events)
- {
- $events->listen('user.signup', 'UserEventHandler@onSignup');
- $events->listen('user.login', 'UserEventHandler@onLogin');
+ public function subscribe($events)
+ {
+ $events->listen('user.signup', 'UserEventHandler@onSignup');
+ $events->listen('user.login', 'UserEventHandler@onLogin');
- $events->listen('user.refresh', 'UserEventHandler@onRefresh');
- }
+ $events->listen('user.refresh', 'UserEventHandler@onRefresh');
+ }
- public function onSignup()
- {
-
- }
+ public function onSignup()
+ {
+ }
- public function onLogin()
- {
- $account = Auth::user()->account;
- $account->last_login = Carbon::now()->toDateTimeString();
- $account->save();
+ public function onLogin()
+ {
+ $account = Auth::user()->account;
+ $account->last_login = Carbon::now()->toDateTimeString();
+ $account->save();
- Event::fire('user.refresh');
- }
+ Event::fire('user.refresh');
+ }
- public function onRefresh()
- {
- Auth::user()->account->loadLocalizationSettings();
- }
-}
\ No newline at end of file
+ public function onRefresh()
+ {
+ Auth::user()->account->loadLocalizationSettings();
+ }
+}
diff --git a/app/lang/da/texts.php b/app/lang/da/texts.php
index 747a5c4eda6d..c9bb24e2dbf7 100644
--- a/app/lang/da/texts.php
+++ b/app/lang/da/texts.php
@@ -456,7 +456,7 @@ return array(
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $20',
+ 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
@@ -471,7 +471,7 @@ return array(
'id_number' => 'ID Number',
'white_label_link' => 'White label',
- 'white_label_text' => 'Purchase a white label license for $10.00 to remove the Invoice Ninja branding from the top of the client pages.',
+ 'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -510,6 +510,45 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
+ 'approve' => 'Approve',
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
+
);
diff --git a/app/lang/de/texts.php b/app/lang/de/texts.php
index 44ccfa5f910e..9b625600c51f 100644
--- a/app/lang/de/texts.php
+++ b/app/lang/de/texts.php
@@ -1,4 +1,4 @@
- 'Zwischensumme',
'paid_to_date' => 'Bereits gezahlt',
'balance_due' => 'Rechnungsbetrag',
- 'invoice_design_id' => 'Vorlage',
+ 'invoice_design_id' => 'Design',
'terms' => 'Bedingungen',
'your_invoice' => 'Ihre Rechnung',
-
+
'remove_contact' => 'Kontakt löschen',
'add_contact' => 'Kontakt hinzufügen',
'create_new_client' => 'Einen neuen Kunden erstellen',
@@ -72,8 +72,8 @@ return array(
'tax_rates' => 'Steuersätze',
'rate' => 'Satz',
'settings' => 'Einstellungen',
- 'enable_invoice_tax' => 'Ermögliche das Bestimmen einer Rechnungssteuer',
- 'enable_line_item_tax' => 'Ermögliche das Bestimmen von Steuern für Belegpositionen',
+ 'enable_invoice_tax' => 'Ermögliche das bestimmen einer Rechnungssteuer',
+ 'enable_line_item_tax' => 'Ermögliche das bestimmen von Steuern für Belegpositionen',
// navigation
'dashboard' => 'Dashboard',
@@ -96,13 +96,13 @@ return array(
'import' => 'Importieren',
'download' => 'Downloaden',
'cancel' => 'Abbrechen',
- 'provide_email' => 'Bitte gib eine gültige E-Mail Adresse an',
+ 'provide_email' => 'Bitte gebe eine gültige E-Mail Adresse an',
'powered_by' => 'Powered by',
'no_items' => 'Keine Objekte',
// recurring invoices
'recurring_invoices' => 'Wiederkehrende Rechnungen',
- 'recurring_help' => 'Sende deinem Kunden wöchentlich, zwei mal im Monat, monatlich, vierteljährlich oder jährlich automatisch die gleiche Rechnung.
+ 'recurring_help' => 'Sende deinen Kunden automatisch die selbe Rechnung wöchentlich, zwei-monatlich, monatlich, vierteljährlich oder jährlich.
Benutze :MONTH, :QUARTER oder :YEAR für ein dynamisches Datum. Grundlegende Mathematik funktioniert genauso gut, zum Beispiel :MONTH-1.
Beispiel zu dynamischen Rechnungs-Variabeln:
@@ -115,12 +115,12 @@ return array(
'in_total_revenue' => 'Gesamtumsatz',
'billed_client' => 'abgerechneter Kunde',
'billed_clients' => 'abgerechnete Kunden',
- 'active_client' => 'aktiver Kunde',
- 'active_clients' => 'aktive Kunden',
+ 'active_client' => 'aktive Kunden',
+ 'active_clients' => 'aktive Kunden',
'invoices_past_due' => 'Fällige Rechnungen',
'upcoming_invoices' => 'Kommende Rechnungen',
'average_invoice' => 'Durchschnittlicher Rechnungsbetrag',
-
+
// list pages
'archive' => 'archivieren',
'delete' => 'löschen',
@@ -159,7 +159,7 @@ return array(
'edit_invoice' => 'Rechnung bearbeiten',
// client view page
- 'create_invoice' => 'Rechnung bearbeiten',
+ 'create_invoice' => 'Rechnung erstellen',
'enter_credit' => 'Guthaben eingeben',
'last_logged_in' => 'Zuletzt eingeloggt',
'details' => 'Details',
@@ -230,10 +230,10 @@ return array(
'cloned_invoice' => 'Rechnung erfolgreich dupliziert',
'emailed_invoice' => 'Rechnung erfolgreich versendet',
'and_created_client' => 'und Kunde erstellt',
- 'archived_invoice' => 'Rechnung erfolgreich archiviert',
- 'archived_invoices' => ':count Rechnungen erfolgreich archiviert',
- 'deleted_invoice' => 'Rechnung erfolgreich gelöscht',
- 'deleted_invoices' => ':count Rechnungen erfolgreich gelöscht',
+ 'archived_invoice' => 'Guthaben erfolgreich archiviert',
+ 'archived_invoices' => ':count Guthaben erfolgreich archiviert',
+ 'deleted_invoice' => 'Guthaben erfolgreich gelöscht',
+ 'deleted_invoices' => ':count Guthaben erfolgreich gelöscht',
'created_payment' => 'Zahlung erfolgreich erstellt',
'archived_payment' => 'Zahlung erfolgreich archiviert',
@@ -249,8 +249,8 @@ return array(
'deleted_credits' => ':count Guthaben erfolgreich gelöscht',
// Emails
- 'confirmation_subject' => 'Invoice Ninja Kontobestätigung',
- 'confirmation_header' => 'Kontobestätigung',
+ 'confirmation_subject' => 'Invoice Ninja Konto Bestätigung',
+ 'confirmation_header' => 'Konto Bestätigung',
'confirmation_message' => 'Bitte klicke auf den folgenden Link um dein Konto zu bestätigen.',
'invoice_message' => 'Um Ihre Rechnung über :amount einzusehen, klicken Sie bitte auf den folgenden Link.',
'payment_subject' => 'Zahlungseingang',
@@ -266,19 +266,19 @@ return array(
'notification_invoice_paid' => 'Eine Zahlung von :amount wurde von :client bezüglich Rechnung :invoice getätigt.',
'notification_invoice_sent' => 'Dem folgenden Kunden :client wurde die Rechnung :invoice über :amount zugesendet.',
'notification_invoice_viewed' => 'Der folgende Kunde :client hat sich Rechnung :invoice über :amount angesehen.',
- 'reset_password' => 'Du kannst dein Passwort zurücksetzen, indem du auf den folgenden Link klickst:',
+ 'reset_password' => 'Du kannst dein Passwort zurücksetzen indem du auf den folgenden Link klickst:',
'reset_password_footer' => 'Wenn du das Zurücksetzen des Passworts nicht beantragt hast benachrichtige bitte unseren Support: ' . CONTACT_EMAIL,
// Payment page
'secure_payment' => 'Sichere Zahlung',
'card_number' => 'Kartennummer',
- 'expiration_month' => 'Ablaufmonat',
+ 'expiration_month' => 'Ablaufmonat',
'expiration_year' => 'Ablaufjahr',
'cvv' => 'Kartenprüfziffer',
-
+
// Security alerts
'confide' => array(
- 'too_many_attempts' => 'Zu viele Versuche. Bitte probiere es in ein paar Minuten erneut.',
+ 'too_many_attempts' => 'Zu viele versuche. Bitte versuche es in ein paar Minuten erneut.',
'wrong_credentials' => 'Falsche E-Mail Adresse oder falsches Passwort.',
'confirmation' => 'Dein Konto wurde bestätigt!',
'wrong_confirmation' => 'Falscher Bestätigungscode.',
@@ -289,26 +289,26 @@ return array(
// Pro Plan
'pro_plan' => [
- 'remove_logo' => ':link, um das Invoice Ninja Logo zu entfernen, indem du dem Pro Plan beitrittst',
+ 'remove_logo' => ':link um das Invoice Ninja Logo zu entfernen, indem du dem Pro Plan beitrittst',
'remove_logo_link' => 'Klicke hier',
],
- 'logout' => 'Ausloggen',
+ 'logout' => 'Ausloggen',
'sign_up_to_save' => 'Melde dich an, um deine Arbeit zu speichern',
'agree_to_terms' =>'Ich akzeptiere die Invoice Ninja :terms',
'terms_of_service' => 'Service-Bedingungen',
'email_taken' => 'Diese E-Mail Adresse ist bereits registriert',
'working' => 'Wird bearbeitet',
'success' => 'Erfolg',
- 'success_message' => 'Du hast dich erfolgreich registriert. Bitte besuche den Link in deiner Bestätigungsmail um deine E-Mail Adresse zu verifizieren.',
- 'erase_data' => 'Diese Aktion wird deine Daten dauerhaft löschen.',
+ 'success_message' => 'Du hast dich erfolgreich registriert. Bitte besuche den Link in deiner Bestätigungsmail um deine E-Mail Adresse zu verfizieren.',
+ 'erase_data' => 'Diese Aktion wird deine Daten dauerhaft entfernen.',
'password' => 'Passwort',
'invoice_subject' => 'Neue Rechnung von :account',
- 'close' => 'Schließen',
+ 'close' => 'Schließen',
'pro_plan_product' => 'Pro Plan',
'pro_plan_description' => 'Jahresmitgliedschaft beim Invoice Ninja Pro Plan.',
- 'pro_plan_success' => 'Danke für den Beitritt! Sobald die Rechnung bezahlt wurde, beginnt deine Pro Plan Mitgliedschaft.',
+ 'pro_plan_success' => 'Danke für den Beitritt! Sobald die Rechnung bezahlt wurde beginnt deine Pro Plan Mitgliedschaft.',
'unsaved_changes' => 'Es liegen ungespeicherte Änderungen vor',
'custom_fields' => 'Benutzerdefinierte Felder',
@@ -323,7 +323,7 @@ return array(
'product' => 'Produkt',
'products' => 'Produkte',
'fill_products' => 'Produkte automatisch ausfüllen',
- 'fill_products_help' => 'Beim Auswählen eines Produktes werden automatisch Beschreibung und Kosten ausgefüllt',
+ 'fill_products_help' => 'Beim Auswählen eines Produktes werden automatisch eine Beschreibung und die Kosten ausgefüllt',
'update_products' => 'Produkte automatisch aktualisieren',
'update_products_help' => 'Beim Aktualisieren einer Rechnung werden die Produkte automatisch aktualisiert',
'create_product' => 'Produkt erstellen',
@@ -332,17 +332,17 @@ return array(
'updated_product' => 'Produkt erfolgreich aktualisiert',
'created_product' => 'Produkt erfolgreich erstellt',
'archived_product' => 'Produkt erfolgreich archiviert',
- 'product_library' => 'Produktbibliothek',
+ 'product_library' => 'Produktbibliothek',
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
- 'advanced_settings' => 'Erweiterte Einstellungen',
- 'pro_plan_advanced_settings' => ':link um durch eine Pro-Mitgliedschaft erweiterte Einstellungen zu aktivieren',
- 'invoice_design' => 'Rechnungsvorlage',
- 'specify_colors' => 'Farben wählen',
- 'specify_colors_label' => 'Wähle die in der Rechnung verwendeten Farben',
+ 'advanced_settings' => 'Advanced Settings',
+ 'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan',
+ 'invoice_design' => 'Invoice Design',
+ 'specify_colors' => 'Specify colors',
+ 'specify_colors_label' => 'Select the colors used in the invoice',
'chart_builder' => 'Diagrammersteller',
- 'ninja_email_footer' => 'Nutze :site um Kunden Rechnungen zu stellen und online bezahlt zu werden, kostenlos!',
+ 'ninja_email_footer' => 'Nutze :site um Kunden eine Rechnung zu stellen und online bezahlt zu werden, kostenlos!',
'go_pro' => 'Werde Pro-Mitglied',
// Quotes
@@ -352,7 +352,7 @@ return array(
'quote_number_short' => 'Angebot #',
'quote_date' => 'Angebotsdatum',
'quote_total' => 'Gesamtanzahl Angebote',
- 'your_quote' => 'Ihr Angebot',
+ 'your_quote' => 'Dein Angebot',
'total' => 'Gesamt',
'clone' => 'Duplizieren',
@@ -380,77 +380,77 @@ return array(
'converted_to_invoice' => 'Angebot erfolgreich in Rechnung umgewandelt',
'quote_subject' => 'Neues Angebot von :account',
- 'quote_message' => 'Klicken Sie auf den folgenden Link um das Angebot über :amount anzuschauen.',
+ 'quote_message' => 'Klicke auf den folgenden Link um das Angebot über :amount anzuschauen.',
'quote_link_message' => 'Klicke auf den folgenden Link um das Angebot deines Kunden anzuschauen:',
'notification_quote_sent_subject' => 'Angebot :invoice wurde an :client versendet',
'notification_quote_viewed_subject' => 'Angebot :invoice wurde von :client angeschaut',
'notification_quote_sent' => 'Der folgende Kunde :client erhielt das Angebot :invoice über :amount.',
- 'notification_quote_viewed' => 'Der folgende Kunde :client hat sich das Angebot :client über :amount angesehen.',
+ 'notification_quote_viewed' => 'Der folgende Kunde :client schaute das Angebot :client über :amount an.',
'session_expired' => 'Deine Sitzung ist abgelaufen.',
- 'invoice_fields' => 'Rechnungsfelder',
- 'invoice_options' => 'Rechnungsoptionen',
- 'hide_quantity' => 'Anzahl verbergen',
- 'hide_quantity_help' => 'Wenn deine Menge immer 1 beträgt, kannst du deine Rechnung einfach halten, indem du dieses Feld entfernst.',
- 'hide_paid_to_date' => 'Bereits gezahlt ausblenden',
- 'hide_paid_to_date_help' => '"Bereits gezahlt" nur anzeigen, wenn eine Zahlung eingegangen ist.',
+ 'invoice_fields' => 'Invoice Fields',
+ 'invoice_options' => 'Invoice Options',
+ 'hide_quantity' => 'Hide quantity',
+ 'hide_quantity_help' => 'If your line items quantities are always 1, then you can declutter invoices by no longer displaying this field.',
+ 'hide_paid_to_date' => 'Hide paid to date',
+ 'hide_paid_to_date_help' => 'Only display the "Paid to Date" area on your invoices once a payment has been received.',
- 'charge_taxes' => 'Steuern erheben',
- 'user_management' => 'Benutzerverwaltung',
- 'add_user' => 'Benutzer hinzufügen',
- 'send_invite' => 'Einladung senden',
- 'sent_invite' => 'Einladung erfolgreich gesendet',
- 'updated_user' => 'Benutzer erfolgreich aktualisiert',
- 'invitation_message' => 'Du wurdest von :invitor eingeladen.',
- 'register_to_add_user' => 'Bitte registrieren um einen Benutzer hinzuzufügen',
- 'user_state' => 'Status',
- 'edit_user' => 'Benutzer bearbeiten',
- 'delete_user' => 'Benutzer löschen',
- 'active' => 'Aktiv',
- 'pending' => 'Ausstehend',
- 'deleted_user' => 'Benutzer erfolgreich gelöscht',
- 'limit_users' => 'Entschuldige, das würde das Limit von ' . MAX_NUM_USERS . ' Benutzern überschreiten',
+ 'charge_taxes' => 'Charge taxes',
+ 'user_management' => 'User Management',
+ 'add_user' => 'Add User',
+ 'send_invite' => 'Send invitation',
+ 'sent_invite' => 'Successfully sent invitation',
+ 'updated_user' => 'Successfully updated user',
+ 'invitation_message' => 'You\'ve been invited by :invitor. ',
+ 'register_to_add_user' => 'Please sign up to add a user',
+ 'user_state' => 'State',
+ 'edit_user' => 'Edit User',
+ 'delete_user' => 'Delete User',
+ 'active' => 'Active',
+ 'pending' => 'Pending',
+ 'deleted_user' => 'Successfully deleted user',
+ 'limit_users' => 'Sorry, this will exceed the limit of ' . MAX_NUM_USERS . ' users',
- 'confirm_email_invoice' => 'Bist du sicher, dass du diese Rechnung per E-Mail versenden möchtest?',
- 'confirm_email_quote' => 'Bist du sicher, dass du dieses Angebot per E-Mail versenden möchtest',
- 'confirm_recurring_email_invoice' => 'Wiederkehrende Rechnung ist aktiv. Bis du sicher, dass du diese Rechnung weiterhin als E-Mail verschicken möchtest?',
+ '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?',
- 'cancel_account' => 'Account Kündigen',
- 'cancel_account_message' => 'Warnung: Alle Daten werden unwiderruflich und vollständig gelöscht, es gibt kein zurück.',
+ 'cancel_account' => 'Cancel Account',
+ 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.',
'go_back' => 'Go Back',
- 'data_visualizations' => 'Datenvisualisierungen',
- 'sample_data' => 'Beispieldaten werden angezeigt',
- 'hide' => 'Verbergen',
- 'new_version_available' => 'Eine neue Version von :releases_link ist verfügbar. Du benutzt v:user_version, die aktuelle ist v:latest_version',
+ 'data_visualizations' => 'Data Visualizations',
+ 'sample_data' => 'Sample data shown',
+ 'hide' => 'Hide',
+ 'new_version_available' => 'A new version of :releases_link is available. You\'re running v:user_version, the latest is v:latest_version',
- 'invoice_settings' => 'Rechnungseinstellungen',
- 'invoice_number_prefix' => 'Präfix für Rechnungsnummer',
- 'invoice_number_counter' => 'Zähler für Rechnungsnummer',
- 'quote_number_prefix' => 'Präfix für Angebotsnummer',
- 'quote_number_counter' => 'Zähler für Angebotsnummer',
- 'share_invoice_counter' => 'Zähler der Rechnung teilen',
- 'invoice_issued_to' => 'Rechnung ausgestellt für',
- 'invalid_counter' => 'Bitte setze, um Probleme zu vermeiden, entweder ein Rechnungs-oder Angebotspräfix.',
- 'mark_sent' => 'Als gesendet markieren',
+ 'invoice_settings' => 'Invoice Settings',
+ 'invoice_number_prefix' => 'Invoice Number Prefix',
+ 'invoice_number_counter' => 'Invoice Number Counter',
+ 'quote_number_prefix' => 'Quote Number Prefix',
+ 'quote_number_counter' => 'Quote Number Counter',
+ 'share_invoice_counter' => 'Share invoice counter',
+ 'invoice_issued_to' => 'Invoice issued to',
+ 'invalid_counter' => 'To prevent a possible conflict please set either an invoice or quote number prefix',
+ 'mark_sent' => 'Mark sent',
- 'gateway_help_1' => ':link um sich bei Authorize.net anzumelden.',
- 'gateway_help_2' => ':link um sich bei Authorize.net anzumelden.',
- 'gateway_help_17' => ':link um deine PayPal API-Signatur zu erhalten.',
- 'gateway_help_23' => 'Anmerkung: benutze deinen secret API key, nicht deinen publishable API key.',
- 'gateway_help_27' => ':link um sich bei TwoCheckout anzumelden.',
+ 'gateway_help_1' => ':link to sign up for Authorize.net.',
+ 'gateway_help_2' => ':link to sign up for Authorize.net.',
+ 'gateway_help_17' => ':link to get your PayPal API signature.',
+ 'gateway_help_23' => 'Note: use your secret API key, not your publishable API key.',
+ 'gateway_help_27' => ':link to sign up for TwoCheckout.',
- 'more_designs' => 'Weitere Vorlagen',
- 'more_designs_title' => 'Zusätzliche Rechnungsvorlagen',
- 'more_designs_cloud_header' => 'Werde Pro-Mitglied für zusätzliche Rechnungsvorlagen',
+ 'more_designs' => 'More designs',
+ 'more_designs_title' => 'Additional Invoice Designs',
+ 'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Erhalte 6 zusätzliche Rechnungsvorlagen für nur $20',
+ 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
- 'buy' => 'Kaufen',
- 'bought_designs' => 'Die zusätzliche Rechnungsvorlagen wurden erfolgreich hinzugefügt',
+ 'buy' => 'Buy',
+ 'bought_designs' => 'Successfully added additional invoice designs',
- 'sent' => 'gesendet',
+ 'sent' => 'sent',
'timesheets' => 'Timesheets',
'payment_title' => 'Geben Sie Ihre Rechnungsadresse und Ihre Kreditkarteninformationen ein',
@@ -461,7 +461,7 @@ return array(
'id_number' => 'ID-Nummer',
'white_label_link' => 'Branding entfernen',
- 'white_label_text' => 'Um das Invoice Ninja Logo auf der Kundenseite zu entfernen, kaufe bitte eine Lizenz für $10.00.',
+ 'white_label_text' => 'Um das Invoice Ninja Logo auf der Kundenseite zu entfernen, kaufe bitte eine Lizenz für $'.WHITE_LABEL_PRICE,
'white_label_header' => 'Branding entfernen',
'bought_white_label' => 'Branding-freie Lizenz erfolgreich aktiviert',
'white_labeled' => 'Branding entfernt',
@@ -500,6 +500,45 @@ return array(
'payment_email' => 'Zahlungsmail',
'quote_email' => 'Angebotsmail',
'reset_all' => 'Alle zurücksetzen',
+ 'approve' => 'Approve',
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
+
);
diff --git a/app/lang/en/texts.php b/app/lang/en/texts.php
index 39908b5e78e4..f15f14e5e85c 100644
--- a/app/lang/en/texts.php
+++ b/app/lang/en/texts.php
@@ -194,8 +194,8 @@ return array(
'email_paid' => 'Email me when an invoice is paid',
'site_updates' => 'Site Updates',
'custom_messages' => 'Custom Messages',
- 'default_invoice_terms' => 'Set default invoice terms',
- 'default_email_footer' => 'Set default email signature',
+ 'default_invoice_terms' => 'Set default invoice terms',
+ 'default_email_footer' => 'Set default email signature',
'import_clients' => 'Import Client Data',
'csv_file' => 'Select CSV file',
'export_clients' => 'Export Client Data',
@@ -453,7 +453,7 @@ return array(
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $20',
+ 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
@@ -469,7 +469,7 @@ return array(
'id_number' => 'ID Number',
'white_label_link' => 'White label',
- 'white_label_text' => 'Purchase a white label license for $10.00 to remove the Invoice Ninja branding from the top of the client pages.',
+ 'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -508,5 +508,44 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
+ 'approve' => 'Approve',
+
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
);
diff --git a/app/lang/es/texts.php b/app/lang/es/texts.php
index e15ed8c80ebb..76aac6f70da1 100644
--- a/app/lang/es/texts.php
+++ b/app/lang/es/texts.php
@@ -425,7 +425,7 @@ return array(
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $20',
+ 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
@@ -441,7 +441,7 @@ return array(
'id_number' => 'ID Number',
'white_label_link' => 'White label',
- 'white_label_text' => 'Purchase a white label license for $10.00 to remove the Invoice Ninja branding from the top of the client pages.',
+ 'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -480,6 +480,44 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
+ 'approve' => 'Approve',
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
);
\ No newline at end of file
diff --git a/app/lang/fr/texts.php b/app/lang/fr/texts.php
index 7abc939fff16..3ca233cf043c 100644
--- a/app/lang/fr/texts.php
+++ b/app/lang/fr/texts.php
@@ -446,7 +446,7 @@ return array(
'more_designs_title' => 'Modèles de factures additionnels',
'more_designs_cloud_header' => 'Passez au Plan Pro pour plus de modèles',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Obtenez 6 modèles de factures additionnels pour seulement 20$',
+ 'more_designs_self_host_header' => 'Obtenez 6 modèles de factures additionnels pour seulement '.INVOICE_DESIGNS_PRICE.'$',
'more_designs_self_host_text' => '',
'buy' => 'Acheter',
'bought_designs' => 'Les nouveaux modèles ont été ajoutés avec succès',
@@ -462,7 +462,7 @@ return array(
'id_number' => 'Numéro ID',
'white_label_link' => 'Marque blanche',
- 'white_label_text' => 'Pour retirer la marque Invoice Ninja en haut de la page client, achetez un licence en marque blanche de 10,00$.',
+ 'white_label_text' => 'Pour retirer la marque Invoice Ninja en haut de la page client, achetez un licence en marque blanche de '.WHITE_LABEL_PRICE.'$.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -501,6 +501,44 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
+ 'approve' => 'Approve',
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
+
);
\ No newline at end of file
diff --git a/app/lang/it/texts.php b/app/lang/it/texts.php
index b21af7c3dcd5..7fa241629f23 100644
--- a/app/lang/it/texts.php
+++ b/app/lang/it/texts.php
@@ -446,7 +446,7 @@ return array(
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $20',
+ 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
@@ -463,7 +463,7 @@ return array(
'id_number' => 'ID Number',
'white_label_link' => 'White label',
- 'white_label_text' => 'Purchase a white label license for $10.00 to remove the Invoice Ninja branding from the top of the client pages.',
+ 'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -503,6 +503,44 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
+ 'approve' => 'Approve',
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
);
diff --git a/app/lang/lt/texts.php b/app/lang/lt/texts.php
index 4e673fc5229f..d0985358a938 100644
--- a/app/lang/lt/texts.php
+++ b/app/lang/lt/texts.php
@@ -454,7 +454,7 @@ return array(
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $20',
+ 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
@@ -472,7 +472,7 @@ return array(
'id_number' => 'ID Number',
'white_label_link' => 'White label',
- 'white_label_text' => 'Purchase a white label license for $10.00 to remove the Invoice Ninja branding from the top of the client pages.',
+ 'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -511,6 +511,45 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
-
+ 'approve' => 'Approve',
+
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
+
);
diff --git a/app/lang/nb_NO/texts.php b/app/lang/nb_NO/texts.php
index 5e19acc1fc06..25a3109460db 100644
--- a/app/lang/nb_NO/texts.php
+++ b/app/lang/nb_NO/texts.php
@@ -470,7 +470,7 @@ return array(
'id_number' => 'ID Number',
'white_label_link' => 'White label',
- 'white_label_text' => 'Purchase a white label license for $10.00 to remove the Invoice Ninja branding from the top of the client pages.',
+ 'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -509,7 +509,45 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
-
+ 'approve' => 'Approve',
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
+
);
\ No newline at end of file
diff --git a/app/lang/nl/texts.php b/app/lang/nl/texts.php
index 793566e479a6..e9f4e7d5abc8 100644
--- a/app/lang/nl/texts.php
+++ b/app/lang/nl/texts.php
@@ -447,7 +447,7 @@ return array(
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $20',
+ 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
@@ -464,7 +464,7 @@ return array(
'id_number' => 'ID Number',
'white_label_link' => 'White label',
- 'white_label_text' => 'Purchase a white label license for $10.00 to remove the Invoice Ninja branding from the top of the client pages.',
+ 'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -504,7 +504,45 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
+ 'approve' => 'Approve',
-
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
+
);
\ No newline at end of file
diff --git a/app/lang/pt_BR/texts.php b/app/lang/pt_BR/texts.php
index 4673a0d6d111..079721e4d925 100644
--- a/app/lang/pt_BR/texts.php
+++ b/app/lang/pt_BR/texts.php
@@ -434,7 +434,7 @@ return array(
'more_designs_title' => 'Additional Invoice Designs',
'more_designs_cloud_header' => 'Go Pro for more invoice designs',
'more_designs_cloud_text' => '',
- 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $20',
+ 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE,
'more_designs_self_host_text' => '',
'buy' => 'Buy',
'bought_designs' => 'Successfully added additional invoice designs',
@@ -452,7 +452,7 @@ return array(
'id_number' => 'ID Number',
'white_label_link' => 'White label',
- 'white_label_text' => 'Purchase a white label license for $10.00 to remove the Invoice Ninja branding from the top of the client pages.',
+ 'white_label_text' => 'Purchase a white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the top of the client pages.',
'white_label_header' => 'White Label',
'bought_white_label' => 'Successfully enabled white label license',
'white_labeled' => 'White labeled',
@@ -491,7 +491,45 @@ return array(
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
+ 'approve' => 'Approve',
+ 'token_billing_type_id' => 'Token Billing',
+ 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.',
+ 'token_billing_1' => 'Disabled',
+ 'token_billing_2' => 'Opt-in - checkbox is shown but not selected',
+ 'token_billing_3' => 'Opt-out - checkbox is shown and selected',
+ 'token_billing_4' => 'Always',
+ 'token_billing_checkbox' => 'Store credit card details',
+ 'view_in_stripe' => 'View in Stripe',
+ 'use_card_on_file' => 'Use card on file',
+ 'edit_payment_details' => 'Edit payment details',
+ 'token_billing' => 'Save card details',
+ 'token_billing_secure' => 'The data is stored securely by :stripe_link',
+ 'support' => 'Support',
+ 'contact_information' => 'Contact information',
+ '256_encryption' => '256-Bit Encryption',
+ 'amount_due' => 'Amount due',
+ 'billing_address' => 'Billing address',
+ 'billing_method' => 'Billing method',
+ 'order_overview' => 'Order overview',
+ 'match_address' => '*Address must match address accociated with credit card.',
+ 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
+
+ 'default_invoice_footer' => 'Set default invoice footer',
+ 'invoice_footer' => 'Invoice footer',
+ 'save_as_default_footer' => 'Save as default footer',
+
+ 'token_management' => 'Token Management',
+ 'tokens' => 'Tokens',
+ 'add_token' => 'Add Token',
+ 'show_deleted_tokens' => 'Show deleted tokens',
+ 'deleted_token' => 'Successfully deleted token',
+ 'created_token' => 'Successfully created token',
+ 'updated_token' => 'Successfully updated token',
+ 'edit_token' => 'Edit Token',
+ 'delete_token' => 'Delete Token',
+ 'token' => 'Token',
+
);
diff --git a/app/libraries/Utils.php b/app/libraries/Utils.php
new file mode 100755
index 000000000000..99d5a7052e7c
--- /dev/null
+++ b/app/libraries/Utils.php
@@ -0,0 +1,604 @@
+registered;
+ }
+
+ public static function isConfirmed()
+ {
+ return Auth::check() && Auth::user()->confirmed;
+ }
+
+ public static function isDatabaseSetup()
+ {
+ try {
+ if (Schema::hasTable('accounts')) {
+ return true;
+ }
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ public static function isProd()
+ {
+ return App::environment() == ENV_PRODUCTION;
+ }
+
+ public static function isNinja()
+ {
+ return self::isNinjaProd() || self::isNinjaDev();
+ }
+
+ public static function isNinjaProd()
+ {
+ return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'];
+ }
+
+ public static function isNinjaDev()
+ {
+ return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'];
+ }
+
+ public static function isPro()
+ {
+ return Auth::check() && Auth::user()->isPro();
+ }
+
+ public static function getUserType()
+ {
+ if (Utils::isNinja()) {
+ return USER_TYPE_CLOUD_HOST;
+ } else {
+ return USER_TYPE_SELF_HOST;
+ }
+ }
+
+ public static function getDemoAccountId()
+ {
+ return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false;
+ }
+
+ public static function isDemo()
+ {
+ return Auth::check() && Auth::user()->isDemo();
+ }
+
+ public static function getNewsFeedResponse($userType = false)
+ {
+ if (!$userType) {
+ $userType = Utils::getUserType();
+ }
+
+ $response = new stdClass();
+ $response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : '';
+ $response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : '';
+ $response->version = NINJA_VERSION;
+
+ return $response;
+ }
+
+ public static function getProLabel($feature)
+ {
+ if (Auth::check()
+ && !Auth::user()->isPro()
+ && $feature == ACCOUNT_ADVANCED_SETTINGS) {
+ return ' PRO';
+ } else {
+ return '';
+ }
+ }
+
+ public static function basePath()
+ {
+ return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1);
+ }
+
+ public static function trans($input)
+ {
+ $data = [];
+
+ foreach ($input as $field) {
+ if ($field == "checkbox") {
+ $data[] = $field;
+ } else {
+ $data[] = trans("texts.$field");
+ }
+ }
+
+ return $data;
+ }
+
+ public static function fatalError($message = false, $exception = false)
+ {
+ if (!$message) {
+ $message = "An error occurred, please try again later.";
+ }
+
+ static::logError($message.' '.$exception);
+
+ $data = [
+ 'showBreadcrumbs' => false,
+ 'hideHeader' => true,
+ ];
+
+ return View::make('error', $data)->with('error', $message);
+ }
+
+ public static function getErrorString($exception)
+ {
+ return "{$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
+ }
+
+ public static function logError($error, $context = 'PHP')
+ {
+ $count = Session::get('error_count', 0);
+ Session::put('error_count', ++$count);
+ if ($count > 100) {
+ return 'logged';
+ }
+
+ $data = [
+ 'context' => $context,
+ 'user_id' => Auth::check() ? Auth::user()->id : 0,
+ 'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '',
+ 'url' => Input::get('url', Request::url()),
+ 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
+ 'ip' => Request::getClientIp(),
+ 'count' => Session::get('error_count', 0),
+ ];
+
+ Log::error($error."\n", $data);
+
+ /*
+ Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
+ {
+ $message->to($email)->subject($subject);
+ });
+ */
+ }
+
+ public static function parseFloat($value)
+ {
+ $value = preg_replace('/[^0-9\.\-]/', '', $value);
+
+ return floatval($value);
+ }
+
+ public static function formatPhoneNumber($phoneNumber)
+ {
+ $phoneNumber = preg_replace('/[^0-9a-zA-Z]/', '', $phoneNumber);
+
+ if (!$phoneNumber) {
+ return '';
+ }
+
+ if (strlen($phoneNumber) > 10) {
+ $countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10);
+ $areaCode = substr($phoneNumber, -10, 3);
+ $nextThree = substr($phoneNumber, -7, 3);
+ $lastFour = substr($phoneNumber, -4, 4);
+
+ $phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour;
+ } elseif (strlen($phoneNumber) == 10 && in_array(substr($phoneNumber, 0, 3), array(653, 656, 658, 659))) {
+ /**
+ * SG country code are 653, 656, 658, 659
+ * US area code consist of 650, 651 and 657
+ * @see http://en.wikipedia.org/wiki/Telephone_numbers_in_Singapore#Numbering_plan
+ * @see http://www.bennetyee.org/ucsd-pages/area.html
+ */
+ $countryCode = substr($phoneNumber, 0, 2);
+ $nextFour = substr($phoneNumber, 2, 4);
+ $lastFour = substr($phoneNumber, 6, 4);
+
+ $phoneNumber = '+'.$countryCode.' '.$nextFour.' '.$lastFour;
+ } elseif (strlen($phoneNumber) == 10) {
+ $areaCode = substr($phoneNumber, 0, 3);
+ $nextThree = substr($phoneNumber, 3, 3);
+ $lastFour = substr($phoneNumber, 6, 4);
+
+ $phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour;
+ } elseif (strlen($phoneNumber) == 7) {
+ $nextThree = substr($phoneNumber, 0, 3);
+ $lastFour = substr($phoneNumber, 3, 4);
+
+ $phoneNumber = $nextThree.'-'.$lastFour;
+ }
+
+ return $phoneNumber;
+ }
+
+ public static function formatMoney($value, $currencyId = false)
+ {
+ if (!$currencyId) {
+ $currencyId = Session::get(SESSION_CURRENCY);
+ }
+
+ $currency = Currency::remember(DEFAULT_QUERY_CACHE)->find($currencyId);
+
+ if (!$currency) {
+ $currency = Currency::remember(DEFAULT_QUERY_CACHE)->find(1);
+ }
+
+ return $currency->symbol.number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
+ }
+
+ public static function pluralize($string, $count)
+ {
+ $field = $count == 1 ? $string : $string.'s';
+ $string = trans("texts.$field", ['count' => $count]);
+
+ return $string;
+ }
+
+ public static function toArray($data)
+ {
+ return json_decode(json_encode((array) $data), true);
+ }
+
+ public static function toSpaceCase($camelStr)
+ {
+ return preg_replace('/([a-z])([A-Z])/s', '$1 $2', $camelStr);
+ }
+
+ public static function timestampToDateTimeString($timestamp)
+ {
+ $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
+ $format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
+
+ return Utils::timestampToString($timestamp, $timezone, $format);
+ }
+
+ public static function timestampToDateString($timestamp)
+ {
+ $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
+ $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
+
+ return Utils::timestampToString($timestamp, $timezone, $format);
+ }
+
+ public static function dateToString($date)
+ {
+ $dateTime = new DateTime($date);
+ $timestamp = $dateTime->getTimestamp();
+ $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
+
+ return Utils::timestampToString($timestamp, false, $format);
+ }
+
+ public static function timestampToString($timestamp, $timezone = false, $format)
+ {
+ if (!$timestamp) {
+ return '';
+ }
+ $date = Carbon::createFromTimeStamp($timestamp);
+ if ($timezone) {
+ $date->tz = $timezone;
+ }
+ if ($date->year < 1900) {
+ return '';
+ }
+
+ return $date->format($format);
+ }
+
+ public static function toSqlDate($date, $formatResult = true)
+ {
+ if (!$date) {
+ return;
+ }
+
+ $timezone = Session::get(SESSION_TIMEZONE);
+ $format = Session::get(SESSION_DATE_FORMAT);
+
+ $dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone));
+
+ return $formatResult ? $dateTime->format('Y-m-d') : $dateTime;
+ }
+
+ public static function fromSqlDate($date, $formatResult = true)
+ {
+ if (!$date || $date == '0000-00-00') {
+ return '';
+ }
+
+ $timezone = Session::get(SESSION_TIMEZONE);
+ $format = Session::get(SESSION_DATE_FORMAT);
+
+ $dateTime = DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone));
+
+ return $formatResult ? $dateTime->format($format) : $dateTime;
+ }
+
+ public static function today($formatResult = true)
+ {
+ $timezone = Session::get(SESSION_TIMEZONE);
+ $format = Session::get(SESSION_DATE_FORMAT);
+ $date = date_create(null, new DateTimeZone($timezone));
+
+ if ($formatResult) {
+ return $date->format($format);
+ } else {
+ return $date;
+ }
+ }
+
+ public static function trackViewed($name, $type, $url = false)
+ {
+ if (!$url) {
+ $url = Request::url();
+ }
+
+ $viewed = Session::get(RECENTLY_VIEWED);
+
+ if (!$viewed) {
+ $viewed = [];
+ }
+
+ $object = new stdClass();
+ $object->url = $url;
+ $object->name = ucwords($type).': '.$name;
+
+ $data = [];
+
+ for ($i = 0; $iurl == $item->url || $object->name == $item->name) {
+ continue;
+ }
+
+ array_unshift($data, $item);
+ }
+
+ array_unshift($data, $object);
+
+ if (count($data) > RECENTLY_VIEWED_LIMIT) {
+ array_pop($data);
+ }
+
+ Session::put(RECENTLY_VIEWED, $data);
+ }
+
+ public static function processVariables($str)
+ {
+ if (!$str) {
+ return '';
+ }
+
+ $variables = ['MONTH', 'QUARTER', 'YEAR'];
+ for ($i = 0; $i 1) {
+ $offset = intval($addArray[1]);
+ } elseif (count($minArray) > 1) {
+ $offset = intval($minArray[1]) * -1;
+ }
+
+ $val = Utils::getDatePart($variable, $offset);
+ $str = str_replace($match, $val, $str);
+ }
+ }
+
+ return $str;
+ }
+
+ private static function getDatePart($part, $offset)
+ {
+ $offset = intval($offset);
+ if ($part == 'MONTH') {
+ return Utils::getMonth($offset);
+ } elseif ($part == 'QUARTER') {
+ return Utils::getQuarter($offset);
+ } elseif ($part == 'YEAR') {
+ return Utils::getYear($offset);
+ }
+ }
+
+ private static function getMonth($offset)
+ {
+ $months = [ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December", ];
+
+ $month = intval(date('n')) - 1;
+
+ $month += $offset;
+ $month = $month % 12;
+
+ if ($month < 0) {
+ $month += 12;
+ }
+
+ return $months[$month];
+ }
+
+ private static function getQuarter($offset)
+ {
+ $month = intval(date('n')) - 1;
+ $quarter = floor(($month + 3) / 3);
+ $quarter += $offset;
+ $quarter = $quarter % 4;
+ if ($quarter == 0) {
+ $quarter = 4;
+ }
+
+ return 'Q'.$quarter;
+ }
+
+ private static function getYear($offset)
+ {
+ $year = intval(date('Y'));
+
+ return $year + $offset;
+ }
+
+ public static function getEntityName($entityType)
+ {
+ return ucwords(str_replace('_', ' ', $entityType));
+ }
+
+ public static function getClientDisplayName($model)
+ {
+ if ($model->client_name) {
+ return $model->client_name;
+ } elseif ($model->first_name || $model->last_name) {
+ return $model->first_name.' '.$model->last_name;
+ } else {
+ return $model->email;
+ }
+ }
+
+ public static function encodeActivity($person = null, $action, $entity = null, $otherPerson = null)
+ {
+ $person = $person ? $person->getDisplayName() : 'System';
+ $entity = $entity ? '['.$entity->getActivityKey().']' : '';
+ $otherPerson = $otherPerson ? 'to '.$otherPerson->getDisplayName() : '';
+ $token = Session::get('token_id') ? ' ('.trans('texts.token').')' : '';
+
+ return trim("$person $token $action $entity $otherPerson");
+ }
+
+ public static function decodeActivity($message)
+ {
+ $pattern = '/\[([\w]*):([\d]*):(.*)\]/i';
+ preg_match($pattern, $message, $matches);
+
+ if (count($matches) > 0) {
+ $match = $matches[0];
+ $type = $matches[1];
+ $publicId = $matches[2];
+ $name = $matches[3];
+
+ $link = link_to($type.'s/'.$publicId, $name);
+ $message = str_replace($match, "$type $link", $message);
+ }
+
+ return $message;
+ }
+
+ public static function generateLicense()
+ {
+ $parts = [];
+ for ($i = 0; $i<5; $i++) {
+ $parts[] = strtoupper(str_random(4));
+ }
+
+ return implode('-', $parts);
+ }
+
+ public static function lookupEventId($eventName)
+ {
+ if ($eventName == 'create_client') {
+ return EVENT_CREATE_CLIENT;
+ } elseif ($eventName == 'create_invoice') {
+ return EVENT_CREATE_INVOICE;
+ } elseif ($eventName == 'create_quote') {
+ return EVENT_CREATE_QUOTE;
+ } elseif ($eventName == 'create_payment') {
+ return EVENT_CREATE_PAYMENT;
+ } else {
+ return false;
+ }
+ }
+
+ public static function notifyZapier($subscription, $data)
+ {
+ $curl = curl_init();
+
+ $jsonEncodedData = json_encode($data->toJson());
+ $opts = [
+ CURLOPT_URL => $subscription->target_url,
+ 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);
+
+ $result = curl_exec($curl);
+ $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+
+ curl_close($curl);
+
+ if ($status == 410) {
+ $subscription->delete();
+ }
+ }
+
+
+ public static function remapPublicIds(array $data)
+ {
+ $return = [];
+
+ foreach ($data as $key => $val) {
+ if ($key === 'public_id') {
+ $key = 'id';
+ } elseif (strpos($key, '_id')) {
+ continue;
+ }
+
+ if (is_array($val)) {
+ $val = Utils::remapPublicIds($val);
+ }
+
+ $return[$key] = $val;
+ }
+
+ return $return;
+ }
+
+ public static function getApiHeaders($count = 0)
+ {
+ return [
+ 'Content-Type' => 'application/json',
+ //'Access-Control-Allow-Origin' => '*',
+ //'Access-Control-Allow-Methods' => 'GET',
+ //'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With',
+ //'Access-Control-Allow-Credentials' => 'true',
+ 'X-Total-Count' => $count,
+ //'X-Rate-Limit-Limit' - The number of allowed requests in the current period
+ //'X-Rate-Limit-Remaining' - The number of remaining requests in the current period
+ //'X-Rate-Limit-Reset' - The number of seconds left in the current period,
+ ];
+ }
+
+ public static function startsWith($haystack, $needle)
+ {
+ return $needle === "" || strpos($haystack, $needle) === 0;
+ }
+
+ public static function endsWith($haystack, $needle)
+ {
+ return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
+ }
+
+ public static function getEntityRowClass($model)
+ {
+ $str = $model->is_deleted || ($model->deleted_at && $model->deleted_at != '0000-00-00') ? 'DISABLED ' : '';
+
+ if ($model->is_deleted) {
+ $str .= 'ENTITY_DELETED ';
+ }
+
+ if ($model->deleted_at && $model->deleted_at != '0000-00-00') {
+ $str .= 'ENTITY_ARCHIVED ';
+ }
+
+ return $str;
+ }
+}
diff --git a/app/libraries/utils.php b/app/libraries/utils.php
deleted file mode 100755
index 1884713ee1ff..000000000000
--- a/app/libraries/utils.php
+++ /dev/null
@@ -1,618 +0,0 @@
-registered;
- }
-
- public static function isConfirmed()
- {
- return Auth::check() && Auth::user()->confirmed;
- }
-
- public static function isDatabaseSetup()
- {
- try
- {
- if (Schema::hasTable('accounts'))
- {
- return true;
- }
- }
- catch (Exception $e)
- {
- return false;
- }
- }
-
- public static function isProd()
- {
- return App::environment() == ENV_PRODUCTION;
- }
-
- public static function isNinja()
- {
- return self::isNinjaProd() || self::isNinjaDev();
- }
-
- public static function isNinjaProd()
- {
- return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'];
- }
-
- public static function isNinjaDev()
- {
- return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'];
- }
-
- public static function isPro()
- {
- return Auth::check() && Auth::user()->isPro();
- }
-
- public static function getUserType()
- {
- if (Utils::isNinja()) {
- return USER_TYPE_CLOUD_HOST;
- } else {
- return USER_TYPE_SELF_HOST;
- }
- }
-
- public static function getDemoAccountId()
- {
- return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false;
- }
-
- public static function isDemo()
- {
- return Auth::check() && Auth::user()->isDemo();
- }
-
- public static function getNewsFeedResponse($userType = false)
- {
- if (!$userType) {
- $userType = Utils::getUserType();
- }
-
- $response = new stdClass;
- $response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : '';
- $response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : '';
- $response->version = NINJA_VERSION;
-
- return $response;
- }
-
- public static function getProLabel($feature)
- {
- if (Auth::check()
- && !Auth::user()->isPro()
- && $feature == ACCOUNT_ADVANCED_SETTINGS)
- {
- return ' PRO';
- }
- else
- {
- return '';
- }
- }
-
- public static function basePath()
- {
- return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1);
- }
-
- public static function trans($input)
- {
- $data = [];
-
- foreach ($input as $field)
- {
- if ($field == "checkbox")
- {
- $data[] = $field;
- }
- else
- {
- $data[] = trans("texts.$field");
- }
- }
-
- return $data;
- }
-
- public static function fatalError($message = false, $exception = false)
- {
- if (!$message)
- {
- $message = "An error occurred, please try again later.";
- }
-
- static::logError($message . ' ' . $exception);
-
- $data = [
- 'showBreadcrumbs' => false,
- 'hideHeader' => true
- ];
-
- return View::make('error', $data)->with('error', $message);
- }
-
- public static function getErrorString($exception)
- {
- return "{$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
- }
-
- public static function logError($error, $context = 'PHP')
- {
- $count = Session::get('error_count', 0);
- Session::put('error_count', ++$count);
- if ($count > 100) return 'logged';
-
- $data = [
- 'context' => $context,
- 'user_id' => Auth::check() ? Auth::user()->id : 0,
- 'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '',
- 'url' => Input::get('url', Request::url()),
- 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
- 'ip' => Request::getClientIp(),
- 'count' => Session::get('error_count', 0)
- ];
-
- Log::error($error."\n", $data);
-
- /*
- Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
- {
- $message->to($email)->subject($subject);
- });
- */
- }
-
- public static function parseFloat($value)
- {
- $value = preg_replace('/[^0-9\.\-]/', '', $value);
- return floatval($value);
- }
-
- public static function formatPhoneNumber($phoneNumber)
- {
- $phoneNumber = preg_replace('/[^0-9a-zA-Z]/','',$phoneNumber);
-
- if (!$phoneNumber) {
- return '';
- }
-
- if(strlen($phoneNumber) > 10) {
- $countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10);
- $areaCode = substr($phoneNumber, -10, 3);
- $nextThree = substr($phoneNumber, -7, 3);
- $lastFour = substr($phoneNumber, -4, 4);
-
- $phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour;
- }
- else if(strlen($phoneNumber) == 10 && in_array(substr($phoneNumber, 0, 3), array(653, 656, 658, 659))) {
- /**
- * SG country code are 653, 656, 658, 659
- * US area code consist of 650, 651 and 657
- * @see http://en.wikipedia.org/wiki/Telephone_numbers_in_Singapore#Numbering_plan
- * @see http://www.bennetyee.org/ucsd-pages/area.html
- */
- $countryCode = substr($phoneNumber, 0, 2);
- $nextFour = substr($phoneNumber, 2, 4);
- $lastFour = substr($phoneNumber, 6, 4);
-
- $phoneNumber = '+'.$countryCode.' '.$nextFour.' '.$lastFour;
- }
- else if(strlen($phoneNumber) == 10) {
- $areaCode = substr($phoneNumber, 0, 3);
- $nextThree = substr($phoneNumber, 3, 3);
- $lastFour = substr($phoneNumber, 6, 4);
-
- $phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour;
- }
- else if(strlen($phoneNumber) == 7) {
- $nextThree = substr($phoneNumber, 0, 3);
- $lastFour = substr($phoneNumber, 3, 4);
-
- $phoneNumber = $nextThree.'-'.$lastFour;
- }
-
- return $phoneNumber;
- }
-
- public static function formatMoney($value, $currencyId = false)
- {
- if (!$currencyId)
- {
- $currencyId = Session::get(SESSION_CURRENCY);
- }
-
- $currency = Currency::remember(DEFAULT_QUERY_CACHE)->find($currencyId);
-
- if (!$currency)
- {
- $currency = Currency::remember(DEFAULT_QUERY_CACHE)->find(1);
- }
-
- return $currency->symbol . number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
- }
-
- public static function pluralize($string, $count)
- {
- $field = $count == 1 ? $string : $string . 's';
- $string = trans("texts.$field", ['count' => $count]);
- return $string;
- }
-
- public static function toArray($data)
- {
- return json_decode(json_encode((array) $data), true);
- }
-
- public static function toSpaceCase($camelStr)
- {
- return preg_replace('/([a-z])([A-Z])/s','$1 $2', $camelStr);
- }
-
- public static function timestampToDateTimeString($timestamp) {
- $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
- $format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
- return Utils::timestampToString($timestamp, $timezone, $format);
- }
-
- public static function timestampToDateString($timestamp) {
- $timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
- $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
- return Utils::timestampToString($timestamp, $timezone, $format);
- }
-
- public static function dateToString($date) {
- $dateTime = new DateTime($date);
- $timestamp = $dateTime->getTimestamp();
- $format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
- return Utils::timestampToString($timestamp, false, $format);
- }
-
- public static function timestampToString($timestamp, $timezone = false, $format)
- {
- if (!$timestamp) {
- return '';
- }
- $date = Carbon::createFromTimeStamp($timestamp);
- if ($timezone) {
- $date->tz = $timezone;
- }
- if ($date->year < 1900) {
- return '';
- }
- return $date->format($format);
- }
-
- public static function toSqlDate($date, $formatResult = true)
- {
- if (!$date)
- {
- return null;
- }
-
- $timezone = Session::get(SESSION_TIMEZONE);
- $format = Session::get(SESSION_DATE_FORMAT);
-
-
- $dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone));
- return $formatResult ? $dateTime->format('Y-m-d') : $dateTime;
- }
-
- public static function fromSqlDate($date, $formatResult = true)
- {
- if (!$date || $date == '0000-00-00')
- {
- return '';
- }
-
- $timezone = Session::get(SESSION_TIMEZONE);
- $format = Session::get(SESSION_DATE_FORMAT);
-
- $dateTime = DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone));
- return $formatResult ? $dateTime->format($format) : $dateTime;
- }
-
- public static function today($formatResult = true)
- {
- $timezone = Session::get(SESSION_TIMEZONE);
- $format = Session::get(SESSION_DATE_FORMAT);
- $date = date_create(null, new DateTimeZone($timezone));
-
- if ($formatResult)
- {
- return $date->format($format);
- }
- else
- {
- return $date;
- }
- }
-
- public static function trackViewed($name, $type, $url = false)
- {
- if (!$url)
- {
- $url = Request::url();
- }
-
- $viewed = Session::get(RECENTLY_VIEWED);
-
- if (!$viewed)
- {
- $viewed = [];
- }
-
- $object = new stdClass;
- $object->url = $url;
- $object->name = ucwords($type) . ': ' . $name;
-
- $data = [];
-
- for ($i=0; $iurl == $item->url || $object->name == $item->name)
- {
- continue;
- }
-
- array_unshift($data, $item);
- }
-
- array_unshift($data, $object);
-
- if (count($data) > RECENTLY_VIEWED_LIMIT)
- {
- array_pop($data);
- }
-
- Session::put(RECENTLY_VIEWED, $data);
- }
-
- public static function processVariables($str)
- {
- if (!$str) {
- return '';
- }
-
- $variables = ['MONTH', 'QUARTER', 'YEAR'];
- for ($i=0; $i 1) {
- $offset = intval($addArray[1]);
- } else if (count($minArray) > 1) {
- $offset = intval($minArray[1]) * -1;
- }
-
- $val = Utils::getDatePart($variable, $offset);
- $str = str_replace($match, $val, $str);
- }
- }
-
- return $str;
- }
-
- private static function getDatePart($part, $offset)
- {
- $offset = intval($offset);
- if ($part == 'MONTH') {
- return Utils::getMonth($offset);
- } else if ($part == 'QUARTER') {
- return Utils::getQuarter($offset);
- } else if ($part == 'YEAR') {
- return Utils::getYear($offset);
- }
- }
-
- private static function getMonth($offset)
- {
- $months = [ "January", "February", "March", "April", "May", "June",
- "July", "August", "September", "October", "November", "December" ];
-
- $month = intval(date('n')) - 1;
-
- $month += $offset;
- $month = $month % 12;
-
- if ($month < 0)
- {
- $month += 12;
- }
-
- return $months[$month];
- }
-
- private static function getQuarter($offset)
- {
- $month = intval(date('n')) - 1;
- $quarter = floor(($month + 3) / 3);
- $quarter += $offset;
- $quarter = $quarter % 4;
- if ($quarter == 0) {
- $quarter = 4;
- }
- return 'Q' . $quarter;
- }
-
- private static function getYear($offset)
- {
- $year = intval(date('Y'));
- return $year + $offset;
- }
-
- public static function getEntityName($entityType)
- {
- return ucwords(str_replace('_', ' ', $entityType));
- }
-
- public static function getClientDisplayName($model)
- {
- if ($model->client_name)
- {
- return $model->client_name;
- }
- else if ($model->first_name || $model->last_name)
- {
- return $model->first_name . ' ' . $model->last_name;
- }
- else
- {
- return $model->email;
- }
- }
-
- public static function encodeActivity($person = null, $action, $entity = null, $otherPerson = null)
- {
- $person = $person ? $person->getDisplayName() : 'System';
- $entity = $entity ? '[' . $entity->getActivityKey() . ']' : '';
- $otherPerson = $otherPerson ? 'to ' . $otherPerson->getDisplayName() : '';
-
- return trim("$person $action $entity $otherPerson");
- }
-
- public static function decodeActivity($message)
- {
- $pattern = '/\[([\w]*):([\d]*):(.*)\]/i';
- preg_match($pattern, $message, $matches);
-
- if (count($matches) > 0)
- {
- $match = $matches[0];
- $type = $matches[1];
- $publicId = $matches[2];
- $name = $matches[3];
-
- $link = link_to($type . 's/' . $publicId, $name);
- $message = str_replace($match, "$type $link", $message);
- }
-
- return $message;
- }
-
- public static function generateLicense() {
- $parts = [];
- for ($i=0; $i<5; $i++) {
- $parts[] = strtoupper(str_random(4));
- }
- return join('-', $parts);
- }
-
- public static function lookupEventId($eventName)
- {
- if ($eventName == 'create_client') {
- return EVENT_CREATE_CLIENT;
- } else if ($eventName == 'create_invoice') {
- return EVENT_CREATE_INVOICE;
- } else if ($eventName == 'create_quote') {
- return EVENT_CREATE_QUOTE;
- } else if ($eventName == 'create_payment') {
- return EVENT_CREATE_PAYMENT;
- } else {
- return false;
- }
- }
-
- public static function notifyZapier($subscription, $data) {
- $curl = curl_init();
-
- $jsonEncodedData = json_encode($data->toJson());
- $opts = [
- CURLOPT_URL => $subscription->target_url,
- 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);
-
- $result = curl_exec($curl);
- $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
-
- curl_close($curl);
-
- if ($status == 410)
- {
- $subscription->delete();
- }
- }
-
- public static function remapPublicIds($data) {
- foreach ($data as $index => $record) {
- if (!isset($data[$index]['public_id'])) {
- continue;
- }
- $data[$index]['id'] = $data[$index]['public_id'];
- unset($data[$index]['public_id']);
-
- foreach ($record as $key => $val) {
- if (is_array($val)) {
- $data[$index][$key] = Utils::remapPublicIds($val);
- }
- }
- }
- return $data;
- }
-
- public static function getApiHeaders($count = 0) {
- return [
- 'Content-Type' => 'application/json',
- //'Access-Control-Allow-Origin' => '*',
- //'Access-Control-Allow-Methods' => 'GET',
- //'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With',
- //'Access-Control-Allow-Credentials' => 'true',
- 'X-Total-Count' => $count,
- //'X-Rate-Limit-Limit' - The number of allowed requests in the current period
- //'X-Rate-Limit-Remaining' - The number of remaining requests in the current period
- //'X-Rate-Limit-Reset' - The number of seconds left in the current period,
- ];
- }
-
- public static function startsWith($haystack, $needle)
- {
- return $needle === "" || strpos($haystack, $needle) === 0;
- }
-
- public static function endsWith($haystack, $needle)
- {
- return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
- }
-
- public static function getEntityRowClass($model)
- {
- $str = $model->is_deleted || ($model->deleted_at && $model->deleted_at != '0000-00-00') ? 'DISABLED ' : '';
-
- if ($model->is_deleted)
- {
- $str .= 'ENTITY_DELETED ';
- }
-
- if ($model->deleted_at && $model->deleted_at != '0000-00-00')
- {
- $str .= 'ENTITY_ARCHIVED ';
- }
-
- return $str;
- }
-}
\ No newline at end of file
diff --git a/app/models/Account.php b/app/models/Account.php
index 40ad5e677a81..caaa03c12689 100755
--- a/app/models/Account.php
+++ b/app/models/Account.php
@@ -312,18 +312,18 @@ class Account extends Eloquent
return $template;
}
- $template = "\$client,
\r\n" .
- "" . trans("texts.{$entityType}_message", ['amount' => '$amount']) . "
\r\n";
+ $template = "\$client,\r\n\r\n" .
+ trans("texts.{$entityType}_message", ['amount' => '$amount']) . "\r\n\r\n";
if ($entityType != ENTITY_PAYMENT) {
- $template .= "\$link
\r\n";
+ $template .= "\$link\r\n\r\n";
}
if ($message) {
- $template .= "$message
\r\n";
+ $template .= "$message\r\n\r\n";
}
- return $template . "\$footer
";
+ return $template . "\$footer";
}
public function getEmailFooter()
@@ -334,4 +334,15 @@ class Account extends Eloquent
return "" . trans('texts.email_signature') . "
\$account
";
}
}
+
+ public function showTokenCheckbox()
+ {
+ return $this->token_billing_type_id == TOKEN_BILLING_OPT_IN
+ || $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT;
+ }
+
+ public function selectTokenCheckbox()
+ {
+ return $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT;
+ }
}
diff --git a/app/models/AccountGatewayToken.php b/app/models/AccountGatewayToken.php
new file mode 100644
index 000000000000..ae63d6c2d2cd
--- /dev/null
+++ b/app/models/AccountGatewayToken.php
@@ -0,0 +1,7 @@
+belongsTo('Account');
+ }
+}
diff --git a/app/models/Activity.php b/app/models/Activity.php
index 1eca289b0163..912aa554be7f 100755
--- a/app/models/Activity.php
+++ b/app/models/Activity.php
@@ -34,6 +34,8 @@ class Activity extends Eloquent
Utils::fatalError();
}
+ $activity->token_id = Session::get('token_id', null);
+
return $activity;
}
@@ -57,6 +59,7 @@ class Activity extends Eloquent
$activity->client_id = $client->id;
$activity->activity_type_id = ACTIVITY_TYPE_DELETE_CLIENT;
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted', $client);
+ $activity->balance = $client->balance;
$activity->save();
}
}
@@ -157,7 +160,9 @@ class Activity extends Eloquent
$client = $invoice->client;
if ($invoice->is_deleted && !$invoice->getOriginal('is_deleted')) {
+ $adjustment = 0;
if (!$invoice->is_quote && !$invoice->is_recurring) {
+ $adjustment = $invoice->balance * -1;
$client->balance = $client->balance - $invoice->balance;
$client->paid_to_date = $client->paid_to_date - ($invoice->amount - $invoice->balance);
$client->save();
@@ -169,31 +174,42 @@ class Activity extends Eloquent
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_DELETE_QUOTE : ACTIVITY_TYPE_DELETE_INVOICE;
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted', $invoice);
$activity->balance = $invoice->client->balance;
- $activity->adjustment = $invoice->is_quote ? 0 : $invoice->balance * -1;
+ $activity->adjustment = $adjustment;
$activity->save();
} else {
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
- if ($diff == 0) {
- return;
+ $fieldChanged = false;
+ foreach (['invoice_number', 'po_number', 'invoice_date', 'due_date', 'terms', 'public_notes', 'invoice_footer'] as $field) {
+ if ($invoice->$field != $invoice->getOriginal($field)) {
+ $fieldChanged = true;
+ break;
+ }
}
- $backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($invoice->id);
+ if ($diff > 0 || $fieldChanged) {
+ $backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($invoice->id);
- if (!$invoice->is_quote && !$invoice->is_recurring) {
- $client->balance = $client->balance + $diff;
- $client->save();
+ if ($diff > 0 && !$invoice->is_quote && !$invoice->is_recurring) {
+ $client->balance = $client->balance + $diff;
+ $client->save();
+ }
+
+ $activity = Activity::getBlank($invoice);
+ $activity->client_id = $invoice->client_id;
+ $activity->invoice_id = $invoice->id;
+ $activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
+ $activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
+ $activity->balance = $client->balance;
+ $activity->adjustment = $invoice->is_quote || $invoice->is_recurring ? 0 : $diff;
+ $activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
+ $activity->save();
+
+ if ($invoice->isPaid() && $invoice->balance > 0) {
+ $invoice->invoice_status_id = INVOICE_STATUS_PARTIAL;
+ $invoice->save();
+ }
}
-
- $activity = Activity::getBlank($invoice);
- $activity->client_id = $invoice->client_id;
- $activity->invoice_id = $invoice->id;
- $activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
- $activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
- $activity->balance = $client->balance;
- $activity->adjustment = $invoice->is_quote || $invoice->is_recurring ? 0 : $diff;
- $activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
- $activity->save();
}
}
@@ -231,6 +247,19 @@ class Activity extends Eloquent
$activity->save();
}
+ public static function approveQuote($invitation) {
+
+ $activity = Activity::getBlank($invitation);
+ $activity->client_id = $invitation->invoice->client_id;
+ $activity->invitation_id = $invitation->id;
+ $activity->contact_id = $invitation->contact_id;
+ $activity->invoice_id = $invitation->invoice_id;
+ $activity->activity_type_id = ACTIVITY_TYPE_APPROVE_QUOTE;
+ $activity->message = Utils::encodeActivity($invitation->contact, 'approved', $invitation->invoice);
+ $activity->balance = $invitation->invoice->client->balance;
+ $activity->save();
+ }
+
public static function createPayment($payment)
{
$client = $payment->client;
diff --git a/app/models/Client.php b/app/models/Client.php
index efada1ec50ca..70d0c7a3b7df 100755
--- a/app/models/Client.php
+++ b/app/models/Client.php
@@ -219,6 +219,33 @@ class Client extends EntityModel
return $this->created_at->format('m/d/y h:i a');
}
}
+
+
+ public function getGatewayToken()
+ {
+ $this->account->load('account_gateways');
+
+ if (!count($this->account->account_gateways)) {
+ return false;
+ }
+
+ $accountGateway = $this->account->account_gateways[0];
+
+ if ($accountGateway->gateway_id != GATEWAY_STRIPE) {
+ return false;
+ }
+
+ $token = AccountGatewayToken::where('client_id', '=', $this->id)
+ ->where('account_gateway_id', '=', $accountGateway->id)->first();
+
+ return $token ? $token->token : false;
+ }
+
+ public function getGatewayLink()
+ {
+ $token = $this->getGatewayToken();
+ return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
+ }
}
/*
diff --git a/app/models/Invitation.php b/app/models/Invitation.php
index bb9f4db77a04..0100f75d96f7 100755
--- a/app/models/Invitation.php
+++ b/app/models/Invitation.php
@@ -17,6 +17,11 @@ class Invitation extends EntityModel
return $this->belongsTo('User')->withTrashed();
}
+ public function account()
+ {
+ return $this->belongsTo('Account');
+ }
+
public function getLink()
{
return SITE_URL.'/view/'.$this->invitation_key;
diff --git a/app/models/Invoice.php b/app/models/Invoice.php
index b5f7cad5bbc6..0d7687d4a8fe 100755
--- a/app/models/Invoice.php
+++ b/app/models/Invoice.php
@@ -77,6 +77,7 @@ class Invoice extends EntityModel
'invoice_date',
'due_date',
'terms',
+ 'invoice_footer',
'public_notes',
'amount',
'balance',
diff --git a/app/ninja/mailers/UserMailer.php b/app/ninja/mailers/UserMailer.php
index c8bb82cf8c86..12e7bedd60eb 100755
--- a/app/ninja/mailers/UserMailer.php
+++ b/app/ninja/mailers/UserMailer.php
@@ -37,7 +37,7 @@ class UserMailer extends Mailer
if (!$user->email) {
return;
}
-
+
$view = 'invoice_'.$notificationType;
$entityType = $invoice->getEntityType();
@@ -56,7 +56,7 @@ class UserMailer extends Mailer
}
$subject = trans("texts.notification_{$entityType}_{$notificationType}_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->getDisplayName()]);
-
+
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
}
}
diff --git a/app/ninja/repositories/InvoiceRepository.php b/app/ninja/repositories/InvoiceRepository.php
index f59044f578b5..8b78fabc8057 100755
--- a/app/ninja/repositories/InvoiceRepository.php
+++ b/app/ninja/repositories/InvoiceRepository.php
@@ -80,7 +80,6 @@ class InvoiceRepository
$query = \DB::table('invitations')
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
->join('clients', 'clients.id', '=', 'invoices.client_id')
- //->join('contacts', 'contacts.client_id', '=', 'clients.id')
->where('invitations.contact_id', '=', $contactId)
->where('invitations.deleted_at', '=', null)
->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE)
@@ -99,7 +98,6 @@ class InvoiceRepository
}
return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); })
- //->addColumn('invoice_status_name', function($model) { return $model->invoice_status_name; })
->make();
}
@@ -223,12 +221,14 @@ class InvoiceRepository
}
}
+ $account = \Auth::user()->account;
+
$invoice->client_id = $data['client_id'];
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
$invoice->invoice_number = trim($data['invoice_number']);
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
- $invoice->invoice_date = Utils::toSqlDate($data['invoice_date']);
+ $invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
if ($invoice->is_recurring) {
$invoice->frequency_id = $data['frequency_id'] ? $data['frequency_id'] : 0;
@@ -236,13 +236,14 @@ class InvoiceRepository
$invoice->end_date = Utils::toSqlDate($data['end_date']);
$invoice->due_date = null;
} else {
- $invoice->due_date = Utils::toSqlDate($data['due_date']);
+ $invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']);
$invoice->frequency_id = 0;
$invoice->start_date = null;
$invoice->end_date = null;
}
- $invoice->terms = trim($data['terms']);
+ $invoice->terms = trim($data['terms']) ? trim($data['terms']) : ($account->invoice_terms ? $account->invoice_terms : '');
+ $invoice->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : $account->invoice_footer;
$invoice->public_notes = trim($data['public_notes']);
$invoice->po_number = trim($data['po_number']);
$invoice->invoice_design_id = $data['invoice_design_id'];
@@ -258,16 +259,17 @@ class InvoiceRepository
$total = 0;
foreach ($data['invoice_items'] as $item) {
- if (!$item->cost && !$item->product_key && !$item->notes) {
+ $item = (array) $item;
+ if (!$item['cost'] && !$item['product_key'] && !$item['notes']) {
continue;
}
- $invoiceItemCost = Utils::parseFloat($item->cost);
- $invoiceItemQty = Utils::parseFloat($item->qty);
+ $invoiceItemCost = Utils::parseFloat($item['cost']);
+ $invoiceItemQty = Utils::parseFloat($item['qty']);
$invoiceItemTaxRate = 0;
- if (isset($item->tax_rate) && Utils::parseFloat($item->tax_rate) > 0) {
- $invoiceItemTaxRate = Utils::parseFloat($item->tax_rate);
+ if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
+ $invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
}
$lineTotal = $invoiceItemCost * $invoiceItemQty;
@@ -316,25 +318,27 @@ class InvoiceRepository
$invoice->amount = $total;
$invoice->save();
- $invoice->invoice_items()->forceDelete();
+ if ($publicId) {
+ $invoice->invoice_items()->forceDelete();
+ }
foreach ($data['invoice_items'] as $item) {
- if (!$item->cost && !$item->product_key && !$item->notes) {
+ $item = (array) $item;
+ if (!$item['cost'] && !$item['product_key'] && !$item['notes']) {
continue;
}
- if ($item->product_key) {
- $product = Product::findProductByKey(trim($item->product_key));
+ if ($item['product_key']) {
+ $product = Product::findProductByKey(trim($item['product_key']));
if (!$product) {
$product = Product::createNew();
- $product->product_key = trim($item->product_key);
+ $product->product_key = trim($item['product_key']);
}
if (\Auth::user()->account->update_products) {
- $product->notes = $item->notes;
- $product->cost = $item->cost;
- //$product->qty = $item->qty;
+ $product->notes = $item['notes'];
+ $product->cost = $item['cost'];
}
$product->save();
@@ -342,23 +346,28 @@ class InvoiceRepository
$invoiceItem = InvoiceItem::createNew();
$invoiceItem->product_id = isset($product) ? $product->id : null;
- $invoiceItem->product_key = trim($invoice->is_recurring ? $item->product_key : Utils::processVariables($item->product_key));
- $invoiceItem->notes = trim($invoice->is_recurring ? $item->notes : Utils::processVariables($item->notes));
- $invoiceItem->cost = Utils::parseFloat($item->cost);
- $invoiceItem->qty = Utils::parseFloat($item->qty);
+ $invoiceItem->product_key = trim($invoice->is_recurring ? $item->product_key : Utils::processVariables($item['product_key']));
+ $invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes']));
+ $invoiceItem->cost = Utils::parseFloat($item['cost']);
+ $invoiceItem->qty = Utils::parseFloat($item['qty']);
$invoiceItem->tax_rate = 0;
- if (isset($item->tax_rate) && isset($item->tax_name) && $item->tax_name) {
- $invoiceItem->tax_rate = Utils::parseFloat($item->tax_rate);
- $invoiceItem->tax_name = trim($item->tax_name);
+ if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
+ $invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
+ $invoiceItem['tax_name'] = trim($item['tax_name']);
}
$invoice->invoice_items()->save($invoiceItem);
}
- if ($data['set_default_terms']) {
- $account = \Auth::user()->account;
- $account->invoice_terms = $invoice->terms;
+ if ((isset($data['set_default_terms']) && $data['set_default_terms'])
+ || (isset($data['set_default_footer']) && $data['set_default_footer'])) {
+ if (isset($data['set_default_terms']) && $data['set_default_terms']) {
+ $account->invoice_terms = trim($data['terms']);
+ }
+ if (isset($data['set_default_footer']) && $data['set_default_footer']) {
+ $account->invoice_footer = trim($data['invoice_footer']);
+ }
$account->save();
}
@@ -399,6 +408,7 @@ class InvoiceRepository
'start_date',
'end_date',
'terms',
+ 'invoice_footer',
'public_notes',
'invoice_design_id',
'tax_name',
diff --git a/app/ninja/repositories/PaymentRepository.php b/app/ninja/repositories/PaymentRepository.php
index f47019b165e7..e7e986db374b 100755
--- a/app/ninja/repositories/PaymentRepository.php
+++ b/app/ninja/repositories/PaymentRepository.php
@@ -19,10 +19,11 @@ class PaymentRepository
->where('clients.deleted_at', '=', null)
->where('contacts.is_primary', '=', true)
->where('contacts.deleted_at', '=', null)
- ->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted');
+ ->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted', 'invoices.is_deleted as invoice_is_deleted');
if (!\Session::get('show_trash:payment')) {
- $query->where('payments.deleted_at', '=', null);
+ $query->where('payments.deleted_at', '=', null)
+ ->where('invoices.deleted_at', '=', null);
}
if ($clientPublicId) {
@@ -52,6 +53,7 @@ class PaymentRepository
->where('clients.is_deleted', '=', false)
->where('payments.is_deleted', '=', false)
->where('invitations.deleted_at', '=', null)
+ ->where('invoices.deleted_at', '=', null)
->where('invitations.contact_id', '=', $contactId)
->select('invitations.invitation_key', 'payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id');
diff --git a/app/routes.php b/app/routes.php
index 061320de5d04..377407cc6ec0 100755
--- a/app/routes.php
+++ b/app/routes.php
@@ -29,23 +29,14 @@ Route::get('update', 'AppController@update');
// Public pages
Route::get('/', 'HomeController@showIndex');
-Route::get('/rocksteady', 'HomeController@showIndex');
-Route::get('/about', 'HomeController@showAboutUs');
-Route::get('/terms', 'HomeController@showTerms');
-Route::get('/contact', 'HomeController@showContactUs');
-Route::get('/plans', 'HomeController@showPlans');
-Route::post('/contact_submit', 'HomeController@doContactUs');
-Route::get('/faq', 'HomeController@showFaq');
-Route::get('/features', 'HomeController@showFeatures');
-Route::get('/testimonials', 'HomeController@showTestimonials');
-Route::get('/compare-online-invoicing{sites?}', 'HomeController@showCompare');
-
+Route::get('terms', 'HomeController@showTerms');
Route::get('log_error', 'HomeController@logError');
Route::get('invoice_now', 'HomeController@invoiceNow');
Route::post('get_started', 'AccountController@getStarted');
// Client visible pages
Route::get('view/{invitation_key}', 'InvoiceController@view');
+Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}', 'PaymentController@show_payment');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::get('complete', 'PaymentController@offsite_payment');
@@ -91,6 +82,10 @@ Route::group(array('before' => 'auth'), function() {
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
Route::get('restore_user/{user_id}', 'UserController@restoreUser');
+ Route::get('api/tokens', array('as'=>'api.tokens', 'uses'=>'TokenController@getDatatable'));
+ Route::resource('tokens', 'TokenController');
+ Route::post('tokens/delete', 'TokenController@delete');
+
Route::get('api/products', array('as'=>'api.products', 'uses'=>'ProductController@getDatatable'));
Route::resource('products', 'ProductController');
Route::get('products/{product_id}/archive', 'ProductController@archive');
@@ -151,7 +146,7 @@ Route::group(array('before' => 'auth'), function() {
});
// Route group for API
-Route::group(array('prefix' => 'api/v1', 'before' => 'auth.basic'), function()
+Route::group(array('prefix' => 'api/v1', 'before' => ['api.access']), function()
{
Route::resource('ping', 'ClientApiController@ping');
Route::resource('clients', 'ClientApiController');
@@ -159,6 +154,7 @@ Route::group(array('prefix' => 'api/v1', 'before' => 'auth.basic'), function()
Route::resource('quotes', 'QuoteApiController');
Route::resource('payments', 'PaymentApiController');
Route::post('api/hooks', 'IntegrationController@subscribe');
+ Route::post('email_invoice', 'InvoiceApiController@emailInvoice');
});
define('CONTACT_EMAIL', Config::get('mail.from.address'));
@@ -194,40 +190,42 @@ 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_TOKEN_MANAGEMENT', 'token_management');
-define("ACTIVITY_TYPE_CREATE_CLIENT", 1);
-define("ACTIVITY_TYPE_ARCHIVE_CLIENT", 2);
-define("ACTIVITY_TYPE_DELETE_CLIENT", 3);
+define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
+define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
+define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
-define("ACTIVITY_TYPE_CREATE_INVOICE", 4);
-define("ACTIVITY_TYPE_UPDATE_INVOICE", 5);
-define("ACTIVITY_TYPE_EMAIL_INVOICE", 6);
-define("ACTIVITY_TYPE_VIEW_INVOICE", 7);
-define("ACTIVITY_TYPE_ARCHIVE_INVOICE", 8);
-define("ACTIVITY_TYPE_DELETE_INVOICE", 9);
+define('ACTIVITY_TYPE_CREATE_INVOICE', 4);
+define('ACTIVITY_TYPE_UPDATE_INVOICE', 5);
+define('ACTIVITY_TYPE_EMAIL_INVOICE', 6);
+define('ACTIVITY_TYPE_VIEW_INVOICE', 7);
+define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8);
+define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
-define("ACTIVITY_TYPE_CREATE_PAYMENT", 10);
-define("ACTIVITY_TYPE_UPDATE_PAYMENT", 11);
-define("ACTIVITY_TYPE_ARCHIVE_PAYMENT", 12);
-define("ACTIVITY_TYPE_DELETE_PAYMENT", 13);
+define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
+define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
+define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
+define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
-define("ACTIVITY_TYPE_CREATE_CREDIT", 14);
-define("ACTIVITY_TYPE_UPDATE_CREDIT", 15);
-define("ACTIVITY_TYPE_ARCHIVE_CREDIT", 16);
-define("ACTIVITY_TYPE_DELETE_CREDIT", 17);
+define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
+define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
+define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
+define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
-define("ACTIVITY_TYPE_CREATE_QUOTE", 18);
-define("ACTIVITY_TYPE_UPDATE_QUOTE", 19);
-define("ACTIVITY_TYPE_EMAIL_QUOTE", 20);
-define("ACTIVITY_TYPE_VIEW_QUOTE", 21);
-define("ACTIVITY_TYPE_ARCHIVE_QUOTE", 22);
-define("ACTIVITY_TYPE_DELETE_QUOTE", 23);
+define('ACTIVITY_TYPE_CREATE_QUOTE', 18);
+define('ACTIVITY_TYPE_UPDATE_QUOTE', 19);
+define('ACTIVITY_TYPE_EMAIL_QUOTE', 20);
+define('ACTIVITY_TYPE_VIEW_QUOTE', 21);
+define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22);
+define('ACTIVITY_TYPE_DELETE_QUOTE', 23);
-define("ACTIVITY_TYPE_RESTORE_QUOTE", 24);
-define("ACTIVITY_TYPE_RESTORE_INVOICE", 25);
-define("ACTIVITY_TYPE_RESTORE_CLIENT", 26);
-define("ACTIVITY_TYPE_RESTORE_PAYMENT", 27);
-define("ACTIVITY_TYPE_RESTORE_CREDIT", 28);
+define('ACTIVITY_TYPE_RESTORE_QUOTE', 24);
+define('ACTIVITY_TYPE_RESTORE_INVOICE', 25);
+define('ACTIVITY_TYPE_RESTORE_CLIENT', 26);
+define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27);
+define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
+define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 8);
@@ -303,7 +301,6 @@ define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com');
define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/');
define('COUNT_FREE_DESIGNS', 4);
-define('PRO_PLAN_PRICE', 50);
define('PRODUCT_ONE_CLICK_INSTALL', 1);
define('PRODUCT_INVOICE_DESIGNS', 2);
define('PRODUCT_WHITE_LABEL', 3);
@@ -312,10 +309,19 @@ define('WHITE_LABEL_AFFILIATE_KEY', '92D2J5');
define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74');
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
+define('PRO_PLAN_PRICE', 50);
+define('WHITE_LABEL_PRICE', 20);
+define('INVOICE_DESIGNS_PRICE', 10);
+
define('USER_TYPE_SELF_HOST', 'SELF_HOST');
define('USER_TYPE_CLOUD_HOST', 'CLOUD_HOST');
define('NEW_VERSION_AVAILABLE', 'NEW_VERSION_AVAILABLE');
+define('TOKEN_BILLING_DISABLED', 1);
+define('TOKEN_BILLING_OPT_IN', 2);
+define('TOKEN_BILLING_OPT_OUT', 3);
+define('TOKEN_BILLING_ALWAYS', 4);
+
/*
define('GATEWAY_AMAZON', 30);
define('GATEWAY_BLUEPAY', 31);
@@ -474,4 +480,4 @@ if (Auth::check() && Auth::user()->id === 1)
{
Auth::loginUsingId(1);
}
-*/
+*/
\ No newline at end of file
diff --git a/app/start/artisan.php b/app/start/artisan.php
index 9b7f26ea0aff..57ae92253c76 100755
--- a/app/start/artisan.php
+++ b/app/start/artisan.php
@@ -15,3 +15,4 @@ Artisan::resolve('SendRecurringInvoices');
Artisan::resolve('CreateRandomData');
Artisan::resolve('ResetData');
Artisan::resolve('ImportTimesheetData');
+Artisan::resolve('CheckData');
diff --git a/app/start/global.php b/app/start/global.php
index c4a93c78d2e2..616e8fcebb8f 100755
--- a/app/start/global.php
+++ b/app/start/global.php
@@ -58,13 +58,11 @@ $monolog->pushHandler(new Monolog\Handler\SyslogHandler('intranet', 'user', Logg
App::error(function(Exception $exception, $code)
{
- if (Utils::isNinjaProd())
- {
- Utils::logError($code . ' ' . Utils::getErrorString($exception));
- return Response::view('error', ['hideHeader' => true, 'error' => "A {$code} error occurred."], $code);
- }
- else
- {
+ Utils::logError($code . ' ' . Utils::getErrorString($exception));
+
+ if (Utils::isNinjaProd()) {
+ return Response::view('error', ['hideHeader' => true, 'error' => "A {$code} error occurred."], $code);
+ } else {
return null;
}
});
diff --git a/app/views/accounts/email_templates.blade.php b/app/views/accounts/email_templates.blade.php
index f50265e32573..f22abef5039e 100644
--- a/app/views/accounts/email_templates.blade.php
+++ b/app/views/accounts/email_templates.blade.php
@@ -5,7 +5,7 @@
diff --git a/app/views/accounts/nav_advanced.blade.php b/app/views/accounts/nav_advanced.blade.php
index 513c05dffdd2..99ea2abf8eb6 100644
--- a/app/views/accounts/nav_advanced.blade.php
+++ b/app/views/accounts/nav_advanced.blade.php
@@ -4,7 +4,8 @@
{{ HTML::nav_link('company/advanced_settings/email_templates', 'email_templates') }}
{{ HTML::nav_link('company/advanced_settings/data_visualizations', 'data_visualizations') }}
{{ HTML::nav_link('company/advanced_settings/chart_builder', 'chart_builder') }}
- {{ HTML::nav_link('company/advanced_settings/user_management', 'user_management') }}
+ {{ HTML::nav_link('company/advanced_settings/user_management', 'users') }}
+ {{ HTML::nav_link('company/advanced_settings/token_management', 'tokens') }}
diff --git a/app/views/accounts/notifications.blade.php b/app/views/accounts/notifications.blade.php
index 7cebf0efbd39..08e0c97b4d82 100755
--- a/app/views/accounts/notifications.blade.php
+++ b/app/views/accounts/notifications.blade.php
@@ -37,7 +37,8 @@
{{ Former::legend('custom_messages') }}
- {{ Former::textarea('invoice_terms')->label(trans('texts.default_invoice_terms')) }}
+ {{ Former::textarea('invoice_terms')->label(trans('texts.default_invoice_terms')) }}
+ {{ Former::textarea('invoice_footer')->label(trans('texts.default_invoice_footer')) }}
{{ Former::textarea('email_footer')->label(trans('texts.default_email_footer')) }}
{{ Former::actions( Button::lg_success_submit(trans('texts.save'))->append_with_icon('floppy-disk') ) }}
diff --git a/app/views/accounts/payments.blade.php b/app/views/accounts/payments.blade.php
index 9fc04806fd39..a47ba1ce2cab 100755
--- a/app/views/accounts/payments.blade.php
+++ b/app/views/accounts/payments.blade.php
@@ -1,26 +1,5 @@
@extends('accounts.nav')
-@section('head')
- @parent
-
-
-
-@stop
-
@section('content')
@parent
@@ -79,14 +58,18 @@
@endforeach
- @if($gateway->getHelp())
+ @if ($gateway->getHelp())
@endforeach
diff --git a/app/views/accounts/token.blade.php b/app/views/accounts/token.blade.php
new file mode 100644
index 000000000000..ac8649079f92
--- /dev/null
+++ b/app/views/accounts/token.blade.php
@@ -0,0 +1,29 @@
+@extends('accounts.nav')
+
+@section('content')
+ @parent
+
+ {{ Former::open($url)->method($method)->addClass('col-md-8 col-md-offset-2 warn-on-exit')->rules(array(
+ 'name' => 'required',
+ )); }}
+
+ {{ Former::legend($title) }}
+
+
+
+ @if ($token)
+ {{ Former::populate($token) }}
+ @endif
+
+ {{ Former::text('name') }}
+
+
+
+ {{ Former::actions(
+ Button::lg_success_submit(trans('texts.save'))->append_with_icon('floppy-disk'),
+ Button::lg_default_link('company/advanced_settings/token_management', 'Cancel')->append_with_icon('remove-circle')
+ ) }}
+
+ {{ Former::close() }}
+
+@stop
\ No newline at end of file
diff --git a/app/views/accounts/token_management.blade.php b/app/views/accounts/token_management.blade.php
new file mode 100644
index 000000000000..9f1c0ed1c729
--- /dev/null
+++ b/app/views/accounts/token_management.blade.php
@@ -0,0 +1,67 @@
+@extends('accounts.nav')
+
+@section('content')
+ @parent
+ @include('accounts.nav_advanced')
+
+ {{ Former::open('tokens/delete')->addClass('user-form') }}
+ {{ Former::legend('token_management') }}
+
+
+ {{ Former::text('tokenPublicId') }}
+
+ {{ Former::close() }}
+
+
+ @if (Utils::isPro())
+ {{ Button::success_link(URL::to('tokens/create'), trans("texts.add_token"), array('class' => 'pull-right'))->append_with_icon('plus-sign') }}
+ @endif
+
+
+
+ {{ Datatable::table()
+ ->addColumn(
+ trans('texts.name'),
+ trans('texts.token'),
+ trans('texts.action'))
+ ->setUrl(url('api/tokens/'))
+ ->setOptions('sPaginationType', 'bootstrap')
+ ->setOptions('bFilter', false)
+ ->setOptions('bAutoWidth', false)
+ ->setOptions('aoColumns', [[ "sWidth"=> "40%" ], [ "sWidth"=> "40%" ], ["sWidth"=> "20%"]])
+ ->setOptions('aoColumnDefs', [['bSortable'=>false, 'aTargets'=>[2]]])
+ ->render('datatable') }}
+
+
+
+@stop
diff --git a/app/views/accounts/user_management.blade.php b/app/views/accounts/user_management.blade.php
index 6d46a19bf097..8a1a66df9b46 100644
--- a/app/views/accounts/user_management.blade.php
+++ b/app/views/accounts/user_management.blade.php
@@ -7,8 +7,6 @@
{{ Former::open('users/delete')->addClass('user-form') }}
{{ Former::legend('user_management') }}
-
-
{{ Former::text('userPublicId') }}
diff --git a/app/views/clients/show.blade.php b/app/views/clients/show.blade.php
index a7605cee8dc6..eb30c8a3e84a 100755
--- a/app/views/clients/show.blade.php
+++ b/app/views/clients/show.blade.php
@@ -1,8 +1,8 @@
@extends('header')
-@section('content')
-
-
+@section('content')
+
+
{{ Former::open('clients/bulk')->addClass('mainForm') }}
@@ -10,7 +10,7 @@
{{ Former::text('id')->value($client->public_id) }}
- @if ($client->trashed())
+ @if ($client->trashed())
{{ Button::primary(trans('texts.restore_client'), ['onclick' => 'onRestoreClick()']) }}
@else
{{ DropdownButton::normal(trans('texts.edit_client'),
@@ -24,16 +24,16 @@
)
, ['id'=>'normalDropDown'])->split(); }}
- {{ DropdownButton::primary('Create Invoice', Navigation::links($actionLinks), ['id'=>'primaryDropDown'])->split(); }}
+ {{ DropdownButton::primary(trans('texts.create_invoice'), Navigation::links($actionLinks), ['id'=>'primaryDropDown'])->split(); }}
@endif
- {{ Former::close() }}
+ {{ Former::close() }}
-
+
{{ $client->getDisplayName() }}
@if ($client->last_login > 0)
-
+
{{ trans('texts.last_logged_in') }} {{ Utils::timestampToDateTimeString(strtotime($client->last_login)); }}
@endif
@@ -55,9 +55,9 @@
{{ trans('texts.contacts') }}
- @foreach ($client->contacts as $contact)
- {{ $contact->getDetails() }}
- @endforeach
+ @foreach ($client->contacts as $contact)
+ {{ $contact->getDetails() }}
+ @endforeach
@@ -84,28 +84,28 @@
-
+
{{ HTML::tab_link('#activity', trans('texts.activity'), true) }}
@if (Utils::isPro())
{{ HTML::tab_link('#quotes', trans('texts.quotes')) }}
@endif
{{ HTML::tab_link('#invoices', trans('texts.invoices')) }}
- {{ HTML::tab_link('#payments', trans('texts.payments')) }}
- {{ HTML::tab_link('#credits', trans('texts.credits')) }}
+ {{ HTML::tab_link('#payments', trans('texts.payments')) }}
+ {{ HTML::tab_link('#credits', trans('texts.credits')) }}
- {{ Datatable::table()
+ {{ Datatable::table()
->addColumn(
trans('texts.date'),
trans('texts.message'),
trans('texts.balance'),
trans('texts.adjustment'))
- ->setUrl(url('api/activities/'. $client->public_id))
+ ->setUrl(url('api/activities/'. $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('aaSorting', [['0', 'desc']])
@@ -116,14 +116,14 @@
@if (Utils::isPro())
- {{ Datatable::table()
+ {{ Datatable::table()
->addColumn(
trans('texts.quote_number'),
trans('texts.quote_date'),
trans('texts.total'),
trans('texts.due_date'),
trans('texts.status'))
- ->setUrl(url('api/quotes/'. $client->public_id))
+ ->setUrl(url('api/quotes/'. $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('aaSorting', [['0', 'desc']])
@@ -135,20 +135,20 @@
@if ($hasRecurringInvoices)
- {{ Datatable::table()
+ {{ Datatable::table()
->addColumn(
trans('texts.frequency_id'),
trans('texts.start_date'),
trans('texts.end_date'),
- trans('texts.invoice_total'))
- ->setUrl(url('api/recurring_invoices/' . $client->public_id))
+ trans('texts.invoice_total'))
+ ->setUrl(url('api/recurring_invoices/' . $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('aaSorting', [['0', 'asc']])
->render('datatable') }}
@endif
- {{ Datatable::table()
+ {{ Datatable::table()
->addColumn(
trans('texts.invoice_number'),
trans('texts.invoice_date'),
@@ -156,46 +156,46 @@
trans('texts.balance_due'),
trans('texts.due_date'),
trans('texts.status'))
- ->setUrl(url('api/invoices/' . $client->public_id))
+ ->setUrl(url('api/invoices/' . $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('aaSorting', [['0', 'asc']])
->render('datatable') }}
-
+
- {{ Datatable::table()
+ {{ Datatable::table()
->addColumn(
trans('texts.invoice'),
- trans('texts.transaction_reference'),
- trans('texts.method'),
+ trans('texts.transaction_reference'),
+ trans('texts.method'),
trans('texts.payment_amount'),
trans('texts.payment_date'))
- ->setUrl(url('api/payments/' . $client->public_id))
+ ->setUrl(url('api/payments/' . $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('aaSorting', [['0', 'asc']])
->render('datatable') }}
-
+
- {{ Datatable::table()
+ {{ Datatable::table()
->addColumn(
trans('texts.credit_amount'),
trans('texts.credit_balance'),
trans('texts.credit_date'),
trans('texts.private_notes'))
- ->setUrl(url('api/credits/' . $client->public_id))
+ ->setUrl(url('api/credits/' . $client->public_id))
->setOptions('sPaginationType', 'bootstrap')
->setOptions('bFilter', false)
->setOptions('aaSorting', [['0', 'asc']])
->render('datatable') }}
-
+
-
+
diff --git a/app/views/emails/confirm_html.blade.php b/app/views/emails/confirm_html.blade.php
index c7ad995255ba..c482b0fa841a 100755
--- a/app/views/emails/confirm_html.blade.php
+++ b/app/views/emails/confirm_html.blade.php
@@ -20,7 +20,7 @@
{{ trans('texts.confirmation_header') }}
- {{ $invitationMessage . trans('texts.confirmation_message') }}
+ {{ $invitationMessage . trans('texts.confirmation_message') }}
{{{ URL::to("user/confirm/{$user->confirmation_code}") }}}
diff --git a/app/views/invoices/edit.blade.php b/app/views/invoices/edit.blade.php
index f9e489f03e08..1847b394ded6 100755
--- a/app/views/invoices/edit.blade.php
+++ b/app/views/invoices/edit.blade.php
@@ -31,7 +31,7 @@
@if ($invoice && $invoice->id)
@else
- {{ Former::checkbox('recurring')->text(trans('texts.enable').'
'.trans('texts.learn_more').'')->data_bind("checked: is_recurring")
+ {{ Former::checkbox('recurring')->onclick('setEmailEnabled()')->text(trans('texts.enable').'
'.trans('texts.learn_more').'')->data_bind("checked: is_recurring")
->inlineHelp($invoice && $invoice->last_sent_date ? 'Last invoice sent ' . Utils::dateToString($invoice->last_sent_date) : '') }}
@endif
@@ -165,16 +165,36 @@
|
- {{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'")
- ->label(false)->placeholder(trans('texts.note_to_client'))->style('resize: none') }}
- {{ Former::textarea('terms')->data_bind("value: wrapped_terms, valueUpdate: 'afterkeydown'")
- ->label(false)->placeholder(trans('texts.invoice_terms'))->style('resize: none')
- ->addGroupClass('less-space-bottom') }}
-
+
+
+
+
+
+
+ {{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'")
+ ->label(null)->style('resize: none; min-width: 460px;')->rows(3) }}
+
+
+ {{ Former::textarea('terms')->data_bind("value:wrapped_terms, placeholder: default_terms, valueUpdate: 'afterkeydown'")
+ ->label(false)->style('resize: none; min-width: 460px')->rows(3)
+ ->help('') }}
+
+
+
+
+
|
-
|
+
|
{{ trans('texts.subtotal') }} |
|
@@ -243,7 +263,7 @@
|
- |
+ |
{{ trans($entityType == ENTITY_INVOICE ? 'texts.balance_due' : 'texts.total') }} |
|
@@ -291,7 +311,7 @@
public_id}") }}">{{ trans("texts.view_history") }}
- @if ($invoice->invoice_status_id < INVOICE_STATUS_SENT)
+ @if ($invoice->invoice_status_id < INVOICE_STATUS_SENT && !$invoice->is_recurring)
{{ trans("texts.mark_sent") }}
@endif
@@ -317,9 +337,11 @@
{{ Button::success(trans("texts.save_{$entityType}"), array('id' => 'saveButton', 'onclick' => 'onSaveClick()')) }}
@endif
- {{ Button::normal(trans("texts.email_{$entityType}"), array('id' => 'email_button', 'onclick' => 'onEmailClick()'))->append_with_icon('send'); }}
+ @if (!$invoice || ($invoice && !$invoice->is_recurring))
+ {{ Button::normal(trans("texts.email_{$entityType}"), array('id' => 'email_button', 'onclick' => 'onEmailClick()'))->append_with_icon('send'); }}
+ @endif
- @if ($invoice && $invoice->id && $entityType == ENTITY_INVOICE)
+ @if ($invoice && $invoice->id && $entityType == ENTITY_INVOICE && !$invoice->is_recurring)
{{ Button::primary(trans('texts.enter_payment'), array('onclick' => 'onPaymentClick()'))->append_with_icon('usd'); }}
@endif
@elseif ($invoice && $invoice->trashed() && !$invoice->is_deleted == '1')
@@ -566,7 +588,7 @@
});
}
- $('#terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount').change(function() {
+ $('#invoice_footer, #terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount').change(function() {
setTimeout(function() {
refreshPDF();
}, 1);
@@ -616,8 +638,7 @@
var client = model.invoice().client();
setComboboxValue($('.client_select'),
client.public_id(),
- client.name.display());
-
+ client.name.display());
});
function applyComboboxListeners() {
@@ -627,7 +648,7 @@
});
@if (Auth::user()->account->fill_products)
- $('.datalist').on('input', function() {
+ $('.datalist').on('input', function() {
var key = $(this).val();
for (var i=0; i
getLogoPath()))
invoice.image = "{{ HTML::image_data($account->getLogoPath()) }}";
invoice.imageWidth = {{ $account->getLogoWidth() }};
@@ -1021,8 +1051,12 @@
self.is_amount_discount = ko.observable(0);
self.frequency_id = ko.observable('');
//self.currency_id = ko.observable({{ $client && $client->currency_id ? $client->currency_id : Session::get(SESSION_CURRENCY) }});
- self.terms = ko.observable(wordWrapText('{{ str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) }}', 300));
- self.set_default_terms = ko.observable(false);
+ self.terms = ko.observable('');
+ self.default_terms = ko.observable({{ $account->invoice_terms ? 'true' : 'false' }} ? wordWrapText('{{ str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_terms)) }}', 300) : '');
+ self.set_default_terms = ko.observable(false);
+ self.invoice_footer = ko.observable('');
+ self.default_footer = ko.observable({{ $account->invoice_footer ? 'true' : 'false' }} ? wordWrapText('{{ str_replace(["\r\n","\r","\n"], '\n', addslashes($account->invoice_footer)) }}', 600) : '');
+ self.set_default_footer = ko.observable(false);
self.public_notes = ko.observable('');
self.po_number = ko.observable('');
self.invoice_date = ko.observable('{{ Utils::today() }}');
@@ -1098,31 +1132,37 @@
self.wrapped_terms = ko.computed({
read: function() {
- $('#terms').height(this.terms().split('\n').length * 36);
return this.terms();
},
write: function(value) {
value = wordWrapText(value, 300);
self.terms(value);
- $('#terms').height(value.split('\n').length * 36);
},
owner: this
});
- self.wrapped_notes = ko.computed({
- read: function() {
- $('#public_notes').height(this.public_notes().split('\n').length * 36);
- return this.public_notes();
- },
- write: function(value) {
- value = wordWrapText(value, 300);
- self.public_notes(value);
- $('#public_notes').height(value.split('\n').length * 36);
- },
- owner: this
- });
+ self.wrapped_notes = ko.computed({
+ read: function() {
+ return this.public_notes();
+ },
+ write: function(value) {
+ value = wordWrapText(value, 300);
+ self.public_notes(value);
+ },
+ owner: this
+ });
+ self.wrapped_footer = ko.computed({
+ read: function() {
+ return this.invoice_footer();
+ },
+ write: function(value) {
+ value = wordWrapText(value, 600);
+ self.invoice_footer(value);
+ },
+ owner: this
+ });
self.removeItem = function(item) {
self.invoice_items.remove(item);
@@ -1201,60 +1241,60 @@
});
this.totals.total = ko.computed(function() {
- var total = accounting.toFixed(self.totals.rawSubtotal(),2);
- var discount = self.totals.rawDiscounted();
- total -= discount;
+ var total = accounting.toFixed(self.totals.rawSubtotal(),2);
+ var discount = self.totals.rawDiscounted();
+ total -= discount;
- /*
- var discount = parseFloat(self.discount());
- if (discount > 0) {
- total = roundToTwo(total * ((100 - discount)/100));
- }
- */
+ /*
+ var discount = parseFloat(self.discount());
+ if (discount > 0) {
+ total = roundToTwo(total * ((100 - discount)/100));
+ }
+ */
- var customValue1 = roundToTwo(self.custom_value1());
- var customValue2 = roundToTwo(self.custom_value2());
- var customTaxes1 = self.custom_taxes1() == 1;
- var customTaxes2 = self.custom_taxes2() == 1;
-
- if (customValue1 && customTaxes1) {
- total = NINJA.parseFloat(total) + customValue1;
- }
- if (customValue2 && customTaxes2) {
- total = NINJA.parseFloat(total) + customValue2;
- }
+ var customValue1 = roundToTwo(self.custom_value1());
+ var customValue2 = roundToTwo(self.custom_value2());
+ var customTaxes1 = self.custom_taxes1() == 1;
+ var customTaxes2 = self.custom_taxes2() == 1;
+
+ if (customValue1 && customTaxes1) {
+ total = NINJA.parseFloat(total) + customValue1;
+ }
+ if (customValue2 && customTaxes2) {
+ total = NINJA.parseFloat(total) + customValue2;
+ }
- var taxRate = parseFloat(self.tax_rate());
- if (taxRate > 0) {
- total = NINJA.parseFloat(total) + roundToTwo((total * (taxRate/100)));
- }
+ var taxRate = parseFloat(self.tax_rate());
+ if (taxRate > 0) {
+ total = NINJA.parseFloat(total) + roundToTwo((total * (taxRate/100)));
+ }
- if (customValue1 && !customTaxes1) {
- total = NINJA.parseFloat(total) + customValue1;
- }
- if (customValue2 && !customTaxes2) {
- total = NINJA.parseFloat(total) + customValue2;
- }
-
- var paid = self.totals.rawPaidToDate();
- if (paid > 0) {
- total -= paid;
- }
+ if (customValue1 && !customTaxes1) {
+ total = NINJA.parseFloat(total) + customValue1;
+ }
+ if (customValue2 && !customTaxes2) {
+ total = NINJA.parseFloat(total) + customValue2;
+ }
+
+ var paid = self.totals.rawPaidToDate();
+ if (paid > 0) {
+ total -= paid;
+ }
- return formatMoney(total, self.client().currency_id());
- });
+ return formatMoney(total, self.client().currency_id());
+ });
- self.onDragged = function(item) {
- refreshPDF();
- }
+ self.onDragged = function(item) {
+ refreshPDF();
+ }
}
function ClientModel(data) {
var self = this;
self.public_id = ko.observable(0);
self.name = ko.observable('');
- self.id_number = ko.observable('');
- self.vat_number = ko.observable('');
+ self.id_number = ko.observable('');
+ self.vat_number = ko.observable('');
self.work_phone = ko.observable('');
self.custom_value1 = ko.observable('');
self.custom_value2 = ko.observable('');
@@ -1373,7 +1413,7 @@
this.prettyRate = ko.computed({
read: function () {
- return this.rate() ? parseFloat(this.rate()) : '';
+ return this.rate() ? this.rate() : '';
},
write: function (value) {
this.rate(value);
@@ -1547,6 +1587,15 @@
}
}
+ function setEmailEnabled()
+ {
+ if ($('#recurring').prop('checked')) {
+ $('#email_button').attr('disabled', true);
+ } else {
+ $('#email_button').removeAttr('disabled');
+ }
+ }
+
var products = {{ $products }};
var clients = {{ $clients }};
diff --git a/app/views/invoices/pdf.blade.php b/app/views/invoices/pdf.blade.php
index 22f4ab44f73b..feb2e8234b93 100644
--- a/app/views/invoices/pdf.blade.php
+++ b/app/views/invoices/pdf.blade.php
@@ -69,7 +69,7 @@
} else {
window.accountLogo = "{{ HTML::image_data($account->getLogoPath()) }}";
}
- @endif
+ @endif
var NINJA = NINJA || {};
NINJA.primaryColor = "{{ $account->primary_color }}";
@@ -81,6 +81,7 @@
var needsRefresh = false;
function refreshPDF() {
+ PDFJS.workerSrc = '{{ asset('js/pdf_viewer.worker.js') }}';
if ({{ Auth::check() && Auth::user()->force_pdfjs ? 'false' : 'true' }} && (isFirefox || (isChrome && !isChromium))) {
var string = getPDFString();
if (!string) return;
diff --git a/app/views/invoices/view.blade.php b/app/views/invoices/view.blade.php
index e8337001ccdb..84adacbe7316 100755
--- a/app/views/invoices/view.blade.php
+++ b/app/views/invoices/view.blade.php
@@ -5,8 +5,6 @@
@include('script')
-
-
@@ -22,18 +20,27 @@
-
- @if ($invoice->client->account->isGatewayConfigured() && !$invoice->isPaid() && !$invoice->is_recurring)
-
- {{ Button::normal(trans('texts.download_pdf'), array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}
- {{ Button::success_link(URL::to('payment/' . $invitation->invitation_key), trans('texts.pay_now'), array('class' => 'btn-lg')) }}
-
+
+ @if ($invoice->is_quote)
+ {{ Button::normal(trans('texts.download_pdf'), array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}
+ @if (!$isConverted)
+ {{ Button::success_link(URL::to('approve/' . $invitation->invitation_key), trans('texts.approve'), array('class' => 'btn-lg')) }}
+ @endif
+ @elseif ($invoice->client->account->isGatewayConfigured() && !$invoice->isPaid() && !$invoice->is_recurring)
+ {{ Button::normal(trans('texts.download_pdf'), array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}
+ @if ($hasToken)
+ {{ DropdownButton::success_lg(trans('texts.pay_now'), [
+ ['url' => URL::to("payment/{$invitation->invitation_key}?use_token=true"), 'label' => trans('texts.use_card_on_file')],
+ ['url' => URL::to('payment/' . $invitation->invitation_key), 'label' => trans('texts.edit_payment_details')]
+ ])->addClass('btn-lg') }}
+ @else
+ {{ Button::success_link(URL::to('payment/' . $invitation->invitation_key), trans('texts.pay_now'), array('class' => 'btn-lg')) }}
+ @endif
@else
-
- {{ Button::success('Download PDF', array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}
-
+ {{ Button::success('Download PDF', array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}
@endif
-
+
+
-
- @yield('head')
-
+@yield('head')
-
+
+
+
@if (isset($_ENV['TAG_MANAGER_KEY']) && $_ENV['TAG_MANAGER_KEY'])
-
-
-
-
+
+
+
+
-
+ url = '/track' + url.replace('http:/', '');
+ dataLayer.push({'event':url, 'eventLabel':this.src});
+ }
+
@elseif (isset($_ENV['ANALYTICS_KEY']) && $_ENV['ANALYTICS_KEY'])
-
+
@else
-
+
@endif
- @yield('body')
+@yield('body')
-
+ });
+ function openUrl(url, track) {
+ trackUrl(track ? track : url);
+ window.open(url, '_blank');
+ }
-
+
+//$('a[rel!=ext]').click(function() { $(window).off('beforeunload') });
+
+
+