1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +01:00

Merge pull request #4 from hillelcoren/master

Update fork
This commit is contained in:
Paul-Vincent Roll 2015-03-03 20:14:52 +01:00
commit dcc885e787
91 changed files with 3563 additions and 4686 deletions

View File

@ -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',
],

View File

@ -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.

View File

@ -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
* [lokesh/lightbox2](https://github.com/lokesh/lightbox2/) - The original lightbox script

266
app/commands/CheckData.php Normal file
View File

@ -0,0 +1,266 @@
<?php
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
/*
##################################################################
WARNING: Please backup your database before running this script
##################################################################
Since the application was released a number of bugs have (inevitably) been found.
Although the bugs have always been fixed in some cases they've caused the client's
balance, paid to date and/or activity records to become inaccurate. This script will
check for errors and correct the data.
If you have any questions please email us at contact@invoiceninja.com
Usage:
php artisan ninja:check-data
Options:
--client_id:<value>
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),
);
}
}

View File

@ -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);
}
}
$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),
);
}
}

View File

@ -28,7 +28,7 @@ return array(
|
*/
//'host' => '',
'host' => '',
/*
|--------------------------------------------------------------------------

View File

@ -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']));

View File

@ -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();

View File

@ -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);

View File

@ -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()) {

View File

@ -1,22 +1,20 @@
<?php
use ninja\repositories\InvoiceRepository;
use ninja\mailers\ContactMailer as Mailer;
class InvoiceApiController extends Controller
{
protected $invoiceRepo;
public function __construct(InvoiceRepository $invoiceRepo)
public function __construct(InvoiceRepository $invoiceRepo, Mailer $mailer)
{
$this->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);
}
}

View File

@ -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);

View File

@ -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());

View File

@ -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 '<div style="height:38px"/>';
}
@ -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."<p>".$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'));
}
}

View File

@ -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());

View File

@ -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}");
}
}

View File

@ -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);

View File

@ -0,0 +1,161 @@
<?php
/*
|--------------------------------------------------------------------------
| Confide Controller Template
|--------------------------------------------------------------------------
|
| This is the default Confide controller template for controlling user
| authentication. Feel free to change to your needs.
|
*/
use ninja\repositories\AccountRepository;
class TokenController extends BaseController
{
public function getDatatable()
{
$query = DB::table('account_tokens')
->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 = '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">';
if (!$model->deleted_at) {
$actions .= '<li><a href="'.URL::to('tokens/'.$model->public_id).'/edit">'.uctrans('texts.edit_token').'</a></li>
<li class="divider"></li>
<li><a href="javascript:deleteToken('.$model->public_id.')">'.uctrans('texts.delete_token').'</a></li>';
}
$actions .= '</ul>
</div>';
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');
}
}

View File

@ -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');
}

View File

@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SupportTokenBilling extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->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');
}
}

View File

@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddInvoiceFooter extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function($table)
{
$table->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');
});
}
}

View File

@ -0,0 +1,54 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddTokens extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('account_tokens', function($table)
{
$table->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');
});
}
}

View File

@ -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'));

View File

@ -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

View File

@ -44,7 +44,7 @@ class InvoiceEventHandler
{
if ($user->{'notify_' . $type})
{
$this->userMailer->sendNotification($user, $invoice, $type, $payment);
$this->userMailer->sendNotification($user, $invoice, $type, $payment);
}
}
}

View File

@ -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();
}
}
public function onRefresh()
{
Auth::user()->account->loadLocalizationSettings();
}
}

View File

@ -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',
);

View File

@ -1,4 +1,4 @@
<?php
<?php
return array(
@ -47,10 +47,10 @@ return array(
'subtotal' => '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 <strong>Rechnungssteuer</strong>',
'enable_line_item_tax' => 'Ermögliche das Bestimmen von <strong>Steuern für Belegpositionen</strong>',
'enable_invoice_tax' => 'Ermögliche das bestimmen einer <strong>Rechnungssteuer</strong>',
'enable_line_item_tax' => 'Ermögliche das bestimmen von <strong>Steuern für Belegpositionen</strong>',
// 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' => '<p>Sende deinem Kunden wöchentlich, zwei mal im Monat, monatlich, vierteljährlich oder jährlich automatisch die gleiche Rechnung.</p>
'recurring_help' => '<p>Sende deinen Kunden automatisch die selbe Rechnung wöchentlich, zwei-monatlich, monatlich, vierteljährlich oder jährlich.</p>
<p>Benutze :MONTH, :QUARTER oder :YEAR für ein dynamisches Datum. Grundlegende Mathematik funktioniert genauso gut, zum Beispiel :MONTH-1.</p>
<p>Beispiel zu dynamischen Rechnungs-Variabeln:</p>
<ul>
@ -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 <strong>Beschreibung und Kosten ausgefüllt</strong>',
'fill_products_help' => 'Beim Auswählen eines Produktes werden automatisch <strong>eine Beschreibung und die Kosten ausgefüllt</strong>',
'update_products' => 'Produkte automatisch aktualisieren',
'update_products_help' => 'Beim Aktualisieren einer Rechnung werden die <strong>Produkte automatisch aktualisiert</strong>',
'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',
);

View File

@ -194,8 +194,8 @@ return array(
'email_paid' => 'Email me when an invoice is <b>paid</b>',
'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 <b>invoice terms</b>',
'default_email_footer' => 'Set default <b>email signature</b>',
'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 <b>invoice footer</b>',
'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',
);

View File

@ -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',
);

View File

@ -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',
);

View File

@ -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',
);

View File

@ -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',
);

View File

@ -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',
);

View File

@ -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',
);

View File

@ -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',
);

604
app/libraries/Utils.php Executable file
View File

@ -0,0 +1,604 @@
<?php
class Utils
{
public static function isRegistered()
{
return Auth::check() && Auth::user()->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 '&nbsp;<sup class="pro-label">PRO</sup>';
} 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; $i<count($viewed); $i++) {
$item = $viewed[$i];
if ($object->url == $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<count($variables); $i++) {
$variable = $variables[$i];
$regExp = '/:'.$variable.'[+-]?[\d]*/';
preg_match_all($regExp, $str, $matches);
$matches = $matches[0];
if (count($matches) == 0) {
continue;
}
foreach ($matches as $match) {
$offset = 0;
$addArray = explode('+', $match);
$minArray = explode('-', $match);
if (count($addArray) > 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() : '<i>System</i>';
$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;
}
}

View File

@ -1,618 +0,0 @@
<?php
class Utils
{
public static function isRegistered()
{
return Auth::check() && Auth::user()->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 '&nbsp;<sup class="pro-label">PRO</sup>';
}
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; $i<count($viewed); $i++)
{
$item = $viewed[$i];
if ($object->url == $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<count($variables); $i++)
{
$variable = $variables[$i];
$regExp = '/:' . $variable . '[+-]?[\d]*/';
preg_match_all($regExp, $str, $matches);
$matches = $matches[0];
if (count($matches) == 0) {
continue;
}
foreach ($matches as $match) {
$offset = 0;
$addArray = explode('+', $match);
$minArray = explode('-', $match);
if (count($addArray) > 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() : '<i>System</i>';
$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;
}
}

View File

@ -312,18 +312,18 @@ class Account extends Eloquent
return $template;
}
$template = "<p>\$client,</p>\r\n" .
"<p>" . trans("texts.{$entityType}_message", ['amount' => '$amount']) . "</p>\r\n";
$template = "\$client,<p/>\r\n\r\n" .
trans("texts.{$entityType}_message", ['amount' => '$amount']) . "<p/>\r\n\r\n";
if ($entityType != ENTITY_PAYMENT) {
$template .= "<p><a href=\"\$link\">\$link</a></p>\r\n";
$template .= "<a href=\"\$link\">\$link</a><p/>\r\n\r\n";
}
if ($message) {
$template .= "<p>$message</p>\r\n";
$template .= "$message<p/>\r\n\r\n";
}
return $template . "<p>\$footer</p>";
return $template . "\$footer";
}
public function getEmailFooter()
@ -334,4 +334,15 @@ class Account extends Eloquent
return "<p>" . trans('texts.email_signature') . "<br>\$account</p>";
}
}
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;
}
}

View File

@ -0,0 +1,7 @@
<?php
class AccountGatewayToken extends Eloquent
{
protected $softDelete = true;
public $timestamps = true;
}

9
app/models/AccountToken.php Executable file
View File

@ -0,0 +1,9 @@
<?php
class AccountToken extends EntityModel
{
public function account()
{
return $this->belongsTo('Account');
}
}

View File

@ -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;

View File

@ -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;
}
}
/*

View File

@ -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;

View File

@ -77,6 +77,7 @@ class Invoice extends EntityModel
'invoice_date',
'due_date',
'terms',
'invoice_footer',
'public_notes',
'amount',
'balance',

View File

@ -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);
}
}

View File

@ -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',

View File

@ -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');

View File

@ -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);
}
*/
*/

View File

@ -15,3 +15,4 @@ Artisan::resolve('SendRecurringInvoices');
Artisan::resolve('CreateRandomData');
Artisan::resolve('ResetData');
Artisan::resolve('ImportTimesheetData');
Artisan::resolve('CheckData');

View File

@ -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;
}
});

View File

@ -5,7 +5,7 @@
<style type="text/css">
textarea {
min-height: 100px !important;
min-height: 150px !important;
}
</style>

View File

@ -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') }}
</ul>
<p>&nbsp;</p>

View File

@ -37,7 +37,8 @@
</div></div>
{{ 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') ) }}

View File

@ -1,26 +1,5 @@
@extends('accounts.nav')
@section('head')
@parent
<style type="text/css">
/* bootstrap 3.2.0 fix */
/* https://github.com/twbs/bootstrap/issues/13984 */
.radio input[type="radio"],
.checkbox input[type="checkbox"] {
margin-left: 0;
margin-right: 5px;
height: inherit;
width: inherit;
float: left;
display: inline-block;
position: relative;
margin-top: 3px;
}
</style>
@stop
@section('content')
@parent
@ -79,14 +58,18 @@
@endforeach
@if($gateway->getHelp())
@if ($gateway->getHelp())
<div class="form-group">
<label class="control-label col-lg-4 col-sm-4"></label>
<div class="col-lg-8 col-sm-8">
<div class="col-lg-8 col-sm-8 help-block">
{{ $gateway->getHelp() }}
</div>
</div>
@endif
@if ($gateway->id == GATEWAY_STRIPE)
{{ Former::select('token_billing_type_id')->options($tokenBillingOptions)->help(trans('texts.token_billing_help')) }}
@endif
</div>
@endforeach

View File

@ -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) }}
<p>&nbsp;</p>
@if ($token)
{{ Former::populate($token) }}
@endif
{{ Former::text('name') }}
<p>&nbsp;</p>
{{ 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

View File

@ -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') }}
<div style="display:none">
{{ Former::text('tokenPublicId') }}
</div>
{{ 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
<!--
<label for="trashed" style="font-weight:normal; margin-left: 10px;">
<input id="trashed" type="checkbox" onclick="setTrashVisible()"
{{ Session::get('show_trash:token') ? 'checked' : ''}}/> {{ trans('texts.show_deleted_tokens')}}
</label>
-->
{{ 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') }}
<script>
window.onDatatableReady = function() {
$('tbody tr').mouseover(function() {
$(this).closest('tr').find('.tr-action').css('visibility','visible');
}).mouseout(function() {
$dropdown = $(this).closest('tr').find('.tr-action');
if (!$dropdown.hasClass('open')) {
$dropdown.css('visibility','hidden');
}
});
}
function setTrashVisible() {
var checked = $('#trashed').is(':checked');
window.location = '{{ URL::to('view_archive/token') }}' + (checked ? '/true' : '/false');
}
function deleteToken(id) {
if (!confirm('Are you sure?')) {
return;
}
$('#tokenPublicId').val(id);
$('form.user-form').submit();
}
</script>
@stop

View File

@ -7,8 +7,6 @@
{{ Former::open('users/delete')->addClass('user-form') }}
{{ Former::legend('user_management') }}
<div style="display:none">
{{ Former::text('userPublicId') }}
</div>

View File

@ -1,8 +1,8 @@
@extends('header')
@section('content')
@section('content')
<div class="pull-right">
{{ Former::open('clients/bulk')->addClass('mainForm') }}
<div style="display:none">
@ -10,7 +10,7 @@
{{ Former::text('id')->value($client->public_id) }}
</div>
@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() }}
</div>
<h2>{{ $client->getDisplayName() }}</h2>
@if ($client->last_login > 0)
<h3 style="margin-top:0px"><small>
<h3 style="margin-top:0px"><small>
{{ trans('texts.last_logged_in') }} {{ Utils::timestampToDateTimeString(strtotime($client->last_login)); }}
</small></h3>
@endif
@ -55,9 +55,9 @@
<div class="col-md-3">
<h3>{{ trans('texts.contacts') }}</h3>
@foreach ($client->contacts as $contact)
{{ $contact->getDetails() }}
@endforeach
@foreach ($client->contacts as $contact)
{{ $contact->getDetails() }}
@endforeach
</div>
<div class="col-md-6">
@ -84,28 +84,28 @@
</div>
<p>&nbsp;</p>
<ul class="nav nav-tabs nav-justified">
{{ 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')) }}
</ul>
<div class="tab-content">
<div class="tab-pane active" id="activity">
{{ 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())
<div class="tab-pane" id="quotes">
{{ 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 @@
<div class="tab-pane" id="invoices">
@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') }}
</div>
<div class="tab-pane" id="payments">
{{ 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') }}
</div>
<div class="tab-pane" id="credits">
{{ 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') }}
</div>
</div>
<script type="text/javascript">
$(function() {
@ -221,7 +221,7 @@
if (confirm("{{ trans('texts.are_you_sure') }}")) {
$('#action').val('delete');
$('.mainForm').submit();
}
}
}
</script>

View File

@ -20,7 +20,7 @@
<h1>{{ trans('texts.confirmation_header') }}</h1>
<p>
{{ $invitationMessage . trans('texts.confirmation_message') }}
{{ $invitationMessage . trans('texts.confirmation_message') }}<br/>
<a href='{{{ URL::to("user/confirm/{$user->confirmation_code}") }}}'>
{{{ URL::to("user/confirm/{$user->confirmation_code}") }}}
</a>

View File

@ -31,7 +31,7 @@
@if ($invoice && $invoice->id)
<div class="form-group">
<label for="client" class="control-label col-lg-4 col-sm-4">Client</label>
<div class="col-lg-8 col-sm-8" style="padding-top: 7px">
<div class="col-lg-8 col-sm-8" style="padding-top: 10px">
<a id="editClientLink" class="pointer" data-bind="click: $root.showClientForm, text: getClientDisplayName(ko.toJS(client()))"></a>
</div>
</div>
@ -83,7 +83,7 @@
</div>
@else
<div data-bind="visible: invoice_status_id() === 0">
{{ Former::checkbox('recurring')->text(trans('texts.enable').' &nbsp;&nbsp; <a href="#" onclick="showLearnMore()"><i class="glyphicon glyphicon-question-sign"></i> '.trans('texts.learn_more').'</a>')->data_bind("checked: is_recurring")
{{ Former::checkbox('recurring')->onclick('setEmailEnabled()')->text(trans('texts.enable').' &nbsp;&nbsp; <a href="#" onclick="showLearnMore()"><i class="glyphicon glyphicon-question-sign"></i> '.trans('texts.learn_more').'</a>')->data_bind("checked: is_recurring")
->inlineHelp($invoice && $invoice->last_sent_date ? 'Last invoice sent ' . Utils::dateToString($invoice->last_sent_date) : '') }}
</div>
@endif
@ -165,16 +165,36 @@
<td class="hide-border"/>
<td colspan="2" rowspan="6" style="vertical-align:top">
<br/>
{{ 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') }}
<label class="checkbox" style="width: 200px">
<input type="checkbox" style="width: 24px" data-bind="checked: set_default_terms"/>{{ trans('texts.save_as_default_terms') }}
</label>
<div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#notes" aria-controls="notes" role="tab" data-toggle="tab">{{ trans('texts.note_to_client') }}</a></li>
<li role="presentation"><a href="#terms" aria-controls="terms" role="tab" data-toggle="tab">{{ trans('texts.invoice_terms') }}</a></li>
<li role="presentation"><a href="#footer" aria-controls="footer" role="tab" data-toggle="tab">{{ trans('texts.invoice_footer') }}</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="notes" style="padding-bottom:44px">
{{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'")
->label(null)->style('resize: none; min-width: 460px;')->rows(3) }}
</div>
<div role="tabpanel" class="tab-pane" id="terms">
{{ Former::textarea('terms')->data_bind("value:wrapped_terms, placeholder: default_terms, valueUpdate: 'afterkeydown'")
->label(false)->style('resize: none; min-width: 460px')->rows(3)
->help('<label class="checkbox" style="width: 200px">
<input type="checkbox" style="width: 24px" data-bind="checked: set_default_terms"/>'.trans('texts.save_as_default_terms').'</label>') }}
</div>
<div role="tabpanel" class="tab-pane" id="footer">
{{ Former::textarea('invoice_footer')->data_bind("value:wrapped_footer, placeholder: default_footer, valueUpdate: 'afterkeydown'")
->label(false)->style('resize: none; min-width: 460px')->rows(3)
->help('<label class="checkbox" style="width: 200px">
<input type="checkbox" style="width: 24px" data-bind="checked: set_default_footer"/>'.trans('texts.save_as_default_footer').'</label>') }}
</div>
</div>
</div>
</td>
<td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td class="hide-border" style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}">{{ trans('texts.subtotal') }}</td>
<td style="text-align: right"><span data-bind="text: totals.subtotal"/></td>
</tr>
@ -243,7 +263,7 @@
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}"><b>{{ trans($entityType == ENTITY_INVOICE ? 'texts.balance_due' : 'texts.total') }}</b></td>
<td style="text-align: right"><span data-bind="text: totals.total"/></td>
</tr>
@ -291,7 +311,7 @@
<li><a href="{{ URL::to("{$entityType}s/{$entityType}_history/{$invoice->public_id}") }}">{{ trans("texts.view_history") }}</a></li>
<li class="divider"></li>
@if ($invoice->invoice_status_id < INVOICE_STATUS_SENT)
@if ($invoice->invoice_status_id < INVOICE_STATUS_SENT && !$invoice->is_recurring)
<li><a href="javascript:onMarkClick()">{{ trans("texts.mark_sent") }}</a></li>
@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<products.length; i++) {
var product = products[i];
@ -635,10 +656,12 @@
var model = ko.dataFor(this);
model.notes(product.notes);
model.cost(accounting.toFixed(product.cost,2));
//model.qty(product.qty);
model.qty(1);
break;
}
}
onItemChange();
refreshPDF();
});
@endif
}
@ -649,6 +672,13 @@
invoice.is_quote = {{ $entityType == ENTITY_QUOTE ? 'true' : 'false' }};
invoice.contact = _.findWhere(invoice.client.contacts, {send_invoice: true});
if (!invoice.terms) {
invoice.terms = "{{ $account->invoice_terms }}";
}
if (!invoice.invoice_footer) {
invoice.invoice_footer = "{{ $account->invoice_footer }}";
}
@if (file_exists($account->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 }};

View File

@ -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;

View File

@ -5,8 +5,6 @@
@include('script')
<!-- <link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet" type="text/css"/> -->
<script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script>
<script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script>
@ -22,18 +20,27 @@
<div class="container">
<p>&nbsp;</p>
@if ($invoice->client->account->isGatewayConfigured() && !$invoice->isPaid() && !$invoice->is_recurring)
<div class="pull-right" style="text-align:right">
{{ Button::normal(trans('texts.download_pdf'), array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}&nbsp;&nbsp;
{{ Button::success_link(URL::to('payment/' . $invitation->invitation_key), trans('texts.pay_now'), array('class' => 'btn-lg')) }}
</div>
<div class="pull-right" style="text-align:right">
@if ($invoice->is_quote)
{{ Button::normal(trans('texts.download_pdf'), array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}&nbsp;&nbsp;
@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')) }}&nbsp;&nbsp;
@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
<div class="pull-right">
{{ Button::success('Download PDF', array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}
</div>
{{ Button::success('Download PDF', array('onclick' => 'onDownloadClick()', 'class' => 'btn-lg')) }}
@endif
</div>
<div class="clearfix"></div><p>&nbsp;</p>
<script type="text/javascript">

View File

@ -20,7 +20,7 @@
&nbsp;<label for="trashed" style="font-weight:normal; margin-left: 10px;">
<input id="trashed" type="checkbox" onclick="setTrashVisible()"
{{ Session::get("show_trash:{$entityType}") ? 'checked' : ''}}/> {{ trans('texts.show_archived_deleted')}} {{ strtolower(trans('texts.'.$entityType.'s')) }}
{{ Session::get("show_trash:{$entityType}") ? 'checked' : ''}}/>&nbsp; {{ trans('texts.show_archived_deleted')}} {{ strtolower(trans('texts.'.$entityType.'s')) }}
</label>
<div id="top_right_buttons" class="pull-right">

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<title>Invoice Ninja | {{ isset($title) ? $title : ' ' . trans('public.title') }}</title>
<meta name="description" content="{{ isset($description) ? $description : trans('public.description') }}"></meta>
@ -22,112 +22,118 @@
<link href='//fonts.googleapis.com/css?family=Roboto+Slab:400,300,700' rel='stylesheet' type='text/css'>
<link href="{{ asset('favicon.ico') }}" rel="icon" type="image/x-icon">
<link rel="canonical" href="{{ NINJA_APP_URL }}/{{ Request::path() }}"></link>
<script src="{{ asset('built.js') }}?no_cache={{ NINJA_VERSION }}" type="text/javascript"></script>
<script type="text/javascript">
var NINJA = NINJA || {};
NINJA.isRegistered = {{ Utils::isRegistered() ? 'true' : 'false' }};
window.onerror = function(e) {
var message = e.message ? (e.message + ' - ' + e.filename + ': ' + e.lineno) : e;
try {
$.ajax({
type: 'GET',
url: '{{ URL::to('log_error') }}',
data: 'error='+encodeURIComponent(message)+'&url='+encodeURIComponent(window.location)
});
} catch(err) {}
return false;
}
var NINJA = NINJA || {};
NINJA.isRegistered = {{ Utils::isRegistered() ? 'true' : 'false' }};
/* Set the defaults for DataTables initialisation */
$.extend( true, $.fn.dataTable.defaults, {
"bSortClasses": false,
"sDom": "t<'row-fluid'<'span6'i><'span6'p>>",
"sPaginationType": "bootstrap",
"bInfo": true,
"oLanguage": {
'sEmptyTable': "{{ trans('texts.empty_table') }}",
'sLengthMenu': '_MENU_',
'sSearch': ''
window.onerror = function(e) {
var message = e.message ? (e.message + ' - ' + e.filename + ': ' + e.lineno) : e;
try {
$.ajax({
type: 'GET',
url: '{{ URL::to('log_error') }}',
data: 'error='+encodeURIComponent(message)+'&url='+encodeURIComponent(window.location)
});
} catch(err) {}
return false;
}
} );
/* Set the defaults for DataTables initialisation */
$.extend( true, $.fn.dataTable.defaults, {
"bSortClasses": false,
"sDom": "t<'row-fluid'<'span6'i><'span6'p>>",
"sPaginationType": "bootstrap",
"bInfo": true,
"oLanguage": {
'sEmptyTable': "{{ trans('texts.empty_table') }}",
'sLengthMenu': '_MENU_',
'sSearch': ''
}
} );
</script>
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
@yield('head')
</head>
@yield('head')
<body>
</head>
<body>
@if (isset($_ENV['TAG_MANAGER_KEY']) && $_ENV['TAG_MANAGER_KEY'])
<!-- Google Tag Manager -->
<noscript><iframe src="//www.googletagmanager.com/ns.html?id={{ $_ENV['TAG_MANAGER_KEY'] }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','{{ $_ENV['TAG_MANAGER_KEY'] }}');</script>
<!-- End Google Tag Manager -->
<!-- Google Tag Manager -->
<noscript><iframe src="//www.googletagmanager.com/ns.html?id={{ $_ENV['TAG_MANAGER_KEY'] }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','{{ $_ENV['TAG_MANAGER_KEY'] }}');</script>
<!-- End Google Tag Manager -->
<script>
<script>
function trackUrl(url) {
url = '/track' + url.replace('http:/', '');
dataLayer.push({'event':url, 'eventLabel':this.src});
}
</script>
url = '/track' + url.replace('http:/', '');
dataLayer.push({'event':url, 'eventLabel':this.src});
}
</script>
@elseif (isset($_ENV['ANALYTICS_KEY']) && $_ENV['ANALYTICS_KEY'])
<script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '{{ $_ENV['ANALYTICS_KEY'] }}');
ga('send', 'pageview');
function trackUrl(url) {
url = '/track' + url.replace('http:/', '');
ga('send', 'pageview', url);
//ga('send', 'event', 'photo', 'hover', this.src);
url = '/track' + url.replace('http:/', '');
ga('send', 'pageview', url);
//ga('send', 'event', 'photo', 'hover', this.src);
}
</script>
</script>
@else
<script>
<script>
function trackUrl(url) {}
</script>
</script>
@endif
@yield('body')
@yield('body')
<script type="text/javascript">
NINJA.formIsChanged = false;
$(function() {
<script type="text/javascript">
NINJA.formIsChanged = false;
$(function() {
$('form.warn-on-exit input, form.warn-on-exit textarea, form.warn-on-exit select').change(function() {
NINJA.formIsChanged = true;
NINJA.formIsChanged = true;
});
});
$('form').submit(function() {
});
$('form').submit(function() {
NINJA.formIsChanged = false;
});
$(window).on('beforeunload', function() {
});
$(window).on('beforeunload', function() {
if (NINJA.formIsChanged) {
return "{{ trans('texts.unsaved_changes') }}";
return "{{ trans('texts.unsaved_changes') }}";
} else {
return undefined;
return undefined;
}
});
//$('a[rel!=ext]').click(function() { $(window).off('beforeunload') });
</script>
});
function openUrl(url, track) {
trackUrl(track ? track : url);
window.open(url, '_blank');
}
</body>
//$('a[rel!=ext]').click(function() { $(window).off('beforeunload') });
</script>
</body>
</html>

View File

@ -3,20 +3,117 @@
@section('content')
<style type="text/css">
div > label.control-label
{
font-weight: bold !important;
/* text-transform:uppercase; */
}
.alignCenterText{
text-align: center;
font-weight: bold;
font-size: 20px;
margin-bottom: 5%;
}
.boldText{
font-weight: bold;
}
body {
background-color: #f8f8f8;
color: #1b1a1a;
}
.panel-body {
padding-bottom: 50px;
}
.container input[type=text],
.container input[type=email],
.container select {
font-weight: 300;
font-family: 'Roboto', sans-serif;
width: 100%;
padding: 11px;
color: #8c8c8c;
background: #f9f9f9;
border: 1px solid #ebe7e7;
border-radius: 3px;
margin: 6px 0 6px 0;
font-size: 16px;
min-height: 42px !important;
font-weight: 400;
}
.container input[placeholder],
.container select[placeholder] {
color: #444444;
}
div.row {
padding-top: 8px;
}
header {
margin: 0px !important
}
@media screen and (min-width: 700px) {
header {
margin: 20px 0 75px;
float: left;
}
.panel-body {
padding-left: 150px;
padding-right: 150px;
}
}
h2 {
font-weight: 300;
font-size: 30px;
color: #2e2b2b;
line-height: 1;
}
h3 {
font-weight: 900;
margin-top: 10px;
font-size: 15px;
}
h3 .help {
font-style: italic;
font-weight: normal;
color: #888888;
}
header h3 {
text-transform: uppercase;
}
header h3 span {
display: inline-block;
margin-left: 8px;
}
header h3 em {
font-style: normal;
color: #eb8039;
}
.secure {
text-align: right;
float: right;
background: url({{ asset('/images/icon-shield.png') }}) right 22px no-repeat;
padding: 17px 55px 10px 0;
}
.secure h3 {
color: #36b855;
font-size: 30px;
margin-bottom: 8px;
margin-top: 0px;
}
.secure div {
color: #acacac;
font-size: 15px;
font-weight: 900;
text-transform: uppercase;
}
</style>
@ -42,18 +139,166 @@
{{ Former::populateField('last_name', $contact->last_name) }}
@endif
<section class="hero background hero-secure center" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1>Secure Payment</h1>
<p class="thin"><img src="{{ asset('images/icon-secure-pay.png') }}">256-BiT Encryption</p>
<!-- <img src="{{ asset('images/providers.png') }}"> -->
</div>
</div>
</section>
<div class="container">
<p>&nbsp;</p>
<section class="secure">
<div class="container">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-7">
<header>
@if ($client)
<h2>{{{ $client->getDisplayName() }}}</h2>
<h3>{{{ trans('texts.invoice') . ' ' . $invoiceNumber }}}<span>|&nbsp; {{ trans('texts.amount_due') }}: <em>{{ Utils::formatMoney($amount, $currencyId) }}</em></span></h3>
@elseif ($paymentTitle)
<h2>{{ $paymentTitle }}<br/><small>{{ $paymentSubtitle }}</small></h2>
@endif
</header>
</div>
<div class="col-md-5">
@if (Request::secure() || Utils::isNinjaDev())
<div class="secure">
<h3>{{ trans('texts.secure_payment') }}</h3>
<div>{{ trans('texts.256_encryption') }}</div>
</div>
@endif
</div>
</div>
<p>&nbsp;<br/>&nbsp;</p>
<div>
<h3>{{ trans('texts.contact_information') }}</h3>
<div class="row">
<div class="col-md-6">
{{ Former::text('first_name')->placeholder(trans('texts.first_name'))->raw() }}
</div>
<div class="col-md-6">
{{ Former::text('last_name')->placeholder(trans('texts.last_name'))->raw() }}
</div>
</div>
@if (isset($paymentTitle))
<div class="row">
<div class="col-md-12">
{{ Former::text('email')->placeholder(trans('texts.email'))->raw() }}
</div>
</div>
@endif
<p>&nbsp;<br/>&nbsp;</p>
<h3>{{ trans('texts.billing_address') }} &nbsp;<span class="help">{{ trans('texts.payment_footer1') }}</span></h3>
<div class="row">
<div class="col-md-12">
{{ Former::text('address1')->placeholder(trans('texts.address1'))->raw() }}
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ Former::text('address2')->placeholder(trans('texts.address2'))->raw() }}
</div>
<div class="col-md-6">
{{ Former::text('city')->placeholder(trans('texts.city'))->raw() }}
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ Former::text('state')->placeholder(trans('texts.state'))->raw() }}
</div>
<div class="col-md-6">
{{ Former::text('postal_code')->placeholder(trans('texts.postal_code'))->raw() }}
</div>
</div>
<p>&nbsp;<br/>&nbsp;</p>
<h3>{{ trans('texts.billing_method') }}</h3>
<div class="row">
<div class="col-md-9">
{{ Former::text('card_number')->placeholder(trans('texts.card_number'))->raw() }}
</div>
<div class="col-md-3">
{{ Former::text('cvv')->placeholder(trans('texts.cvv'))->raw() }}
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ Former::select('expiration_month')->placeholder(trans('texts.expiration_month'))
->addOption('01 - January', '1')
->addOption('02 - February', '2')
->addOption('03 - March', '3')
->addOption('04 - April', '4')
->addOption('05 - May', '5')
->addOption('06 - June', '6')
->addOption('07 - July', '7')
->addOption('08 - August', '8')
->addOption('09 - September', '9')
->addOption('10 - October', '10')
->addOption('11 - November', '11')
->addOption('12 - December', '12')->raw();
}}
</div>
<div class="col-md-6">
{{ Former::select('expiration_year')->placeholder(trans('texts.expiration_year'))
->addOption('2015', '2015')
->addOption('2016', '2016')
->addOption('2017', '2017')
->addOption('2018', '2018')
->addOption('2019', '2019')
->addOption('2020', '2020')
->addOption('2021', '2021')
->addOption('2022', '2022')
->addOption('2023', '2023')
->addOption('2024', '2024')
->addOption('2025', '2025')->raw();
}}
</div>
</div>
<div class="row" style="padding-top:18px">
<div class="col-md-5">
@if ($client && $account->showTokenCheckbox())
<input id="token_billing" type="checkbox" name="token_billing" {{ $account->selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top">
<label for="token_billing" class="checkbox" style="display: inline;">{{ trans('texts.token_billing') }}</label>
<span class="help-block" style="font-size:15px">{{ trans('texts.token_billing_secure', ['stripe_link' => link_to('https://stripe.com/', 'Stripe.com', ['target' => '_blank'])]) }}</span>
@endif
</div>
<div class="col-md-7">
@if (isset($acceptedCreditCardTypes))
<div class="pull-right">
@foreach ($acceptedCreditCardTypes as $card)
<img src="{{ $card['source'] }}" alt="{{ $card['alt'] }}" style="width: 70px; display: inline; margin-right: 6px;"/>
@endforeach
</div>
@endif
</div>
</div>
<p>&nbsp;<br/>&nbsp;</p>
<div class="row">
<div class="col-md-4 col-md-offset-4">
{{ Button::block_success_submit_lg(strtoupper(trans('texts.pay_now') . ' - ' . Utils::formatMoney($amount, $currencyId) )) }}
</div>
</div>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
<!--
@if (isset($paymentTitle))
<h2>{{ $paymentTitle }}<br/>
@if (isset($paymentSubtitle))
@ -61,176 +306,18 @@
@endif
</h2>&nbsp;<p/>
@endif
<div id="secure-form" class="row">
<div class="col-md-7 info">
<div class="col-md-12 alignCenterText" >
{{ otrans('texts.payment_title') }}
</div>
<div class="row">
@if (isset($paymentTitle))
<div class="form-group col-md-4">
{{ Former::text('first_name') }}
</div>
<div class="form-group col-md-4">
{{ Former::text('last_name') }}
</div>
<div class="form-group col-md-4">
{{ Former::text('email') }}
</div>
@else
<div class="form-group col-md-6">
{{ Former::text('first_name') }}
</div>
<div class="form-group col-md-6">
{{ Former::text('last_name') }}
</div>
@endif
</div>
<div class="row">
<div class="form-group col-md-8">
{{ Former::text('address1')->label('Street') }}
</div>
<div class="form-group col-md-4">
{{ Former::text('address2')->label('Apt/Suite') }}
</div>
</div>
<div class="row">
<div class="form-group col-md-4">
{{ Former::text('city') }}
</div>
<div class="form-group col-md-4">
{{ Former::text('state')->label('State/Province') }}
</div>
<div class="form-group col-md-4">
{{ Former::text('postal_code') }}
</div>
</div>
<div class="row">
<h5 class="col-md-12 boldText" >
{{ otrans('texts.payment_footer1') }}
</h5>
<h5 class="col-md-12 boldText">
{{ otrans('texts.payment_footer2') }}
</h5>
</div>
</div>
@if(strtolower($gateway->name) == 'beanstream')
<div class="row">
<div class="form-group col-md-4">
{{ Former::text('phone') }}
</div>
<div class="form-group col-md-4">
{{ Former::text('email') }}
</div>
<div class="form-group col-md-4">
{{ Former::select('country')->addOption('','')->label('Country')
->fromQuery($countries, 'name', 'iso_3166_2') }}
</div>
</div>
@endif
<div class="col-md-5">
<div class="col-md-12 alignCenterText" >
&nbsp;<!--{{ trans('texts.balance_due') . ' ' . Utils::formatMoney($amount, $currencyId) }}-->
</div>
<div class="col-md-12">
<div class="card">
<div class="row">
<div class="form-group col-md-12">
{{ Former::text('card_number') }}
<!-- <span class="glyphicon glyphicon-lock"></span> -->
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
{{ Former::select('expiration_month')->addOption('','')
->addOption('01 - January', '1')
->addOption('02 - February', '2')
->addOption('03 - March', '3')
->addOption('04 - April', '4')
->addOption('05 - May', '5')
->addOption('06 - June', '6')
->addOption('07 - July', '7')
->addOption('08 - August', '8')
->addOption('09 - September', '9')
->addOption('10 - October', '10')
->addOption('11 - November', '11')
->addOption('12 - December', '12')
}}
</div>
<div class="form-group col-md-6">
{{ Former::select('expiration_year')->addOption('','')
->addOption('2014', '2014')
->addOption('2015', '2015')
->addOption('2016', '2016')
->addOption('2017', '2017')
->addOption('2018', '2018')
->addOption('2019', '2019')
->addOption('2020', '2020')
}}
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
{{ Former::text('cvv') }}
</div>
<div>
<h5 class="boldText" style="margin-top: 8%;margin-left: 5%;">{{ otrans('texts.payment_cvv') }}</h5>
</div>
<div class="col-md-6">
<!-- <p><span class="glyphicon glyphicon-credit-card" style="margin-right: 10px;"></span><a href="#">Where Do I find CVV?</a></p> -->
</div>
</div>
@if(isset($acceptedCreditCardTypes))
<div class="row">
<div class="form-group col-md-12">
@foreach ($acceptedCreditCardTypes as $card)
<img src="{{ $card['source'] }}" alt="{{ $card['alt'] }}" style="width: 70px; display: inline; margin-right: 6px;"/>
@endforeach
</div>
</div>
@endif
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
{{ Button::block_primary_submit_lg(strtoupper(trans('texts.pay_now')) . ' - ' . Utils::formatMoney($amount, $currencyId) ) }}
</div>
</div>
<div class="row">
<div class="col-md-12">
<p>&nbsp;</p>
<a href="https://www.invoiceninja.com/terms" target="_blank">Click here</a> to view our terms of service.
</div>
</div>
</div>
</div>
</section>
-->
{{ Former::close() }}
<script type="text/javascript">
$(function() {
$('select').change(function() {
$(this).css({color:'#444444'});
});
});
</script>
@stop

View File

@ -1,90 +0,0 @@
@extends('public.header')
@section('content')
<section class="hero background hero-about" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1><img src="{{ asset('images/icon-about.png') }}">{{ trans('public.about.header') }}</h1>
</div>
</div>
</section>
<section class="about">
<div class="container">
<div class="row">
<div class="col-md-5 valign">
<div class="headline">
<h2>{{ trans('public.about.what_is') }}</h2>
</div>
<p class="first">{{ trans('public.description') }}</p>
</div>
<div class="col-md-7">
<img src="{{ asset('images/devices3.png') }}">
</div>
</div>
</div>
</section>
<section class="team center">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>{{ trans('public.about.team_ninja') }}</h2>
<p>{{ trans('public.about.team_ninja_text') }}</p>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="img-team">
<img src="images/shalomstark.jpg" alt="Shalom Stark">
</div>
<h2>Shalom Stark</h2>
<p>{{ trans('public.about.co_founder') }}, {{ trans('public.about.ceo') }}</p>
<p class="social blue"><a href="https://twitter.com/shalomstark" target="_blank"><img src="images/twitter.svg" alt="Twitter"></a>
<a href="http://shalomisraeltours.com/" target="_blank"><img src="images/website.svg" alt="Website"></a>
</p>
<p>{{ trans('public.about.shalom_bio') }}</p>
</div>
<div class="col-md-3">
<div class="img-team">
<img src="images/hillelcoren.jpg" alt="Hillel Coren">
</div>
<h2>Hillel Coren</h2>
<p>{{ trans('public.about.co_founder') }}, {{ trans('public.about.cto') }}</p>
<p class="social green"><a href="https://twitter.com/hillelcoren" target="_blank"><img src="images/twitter.svg" alt="Twitter"></a>
<a href="http://www.linkedin.com/profile/view?id=105143214" target="_blank"><img src="images/linkedin.svg" alt=""></a>
<a href="http://hillelcoren.com/" target="_blank"><img src="images/website.svg" alt="Website"></a>
</p>
<p>{{ trans('public.about.hillel_bio') }}</p>
</div>
<div class="col-md-3">
<div class="img-team">
<img src="images/razikantorp.jpg" alt="Razi Kantorp">
</div>
<h2>Razi Kantorp-Weglin</h2>
<p>{{ trans('public.about.designer') }}</p>
<p class="social red"><a href="https://twitter.com/kantorpweglin" target="_blank"><img src="images/twitter.svg" alt="Twitter"></a>
<a href="https://www.linkedin.com/pub/razi-kantorp/35/368/973" target="_blank"><img src="images/linkedin.svg" alt=""></a>
<a href="http://instagram.com/kantorpweglin" target="_blank"><img src="images/instagram.svg" alt="Twitter"></a>
<a href="http://kantorp-wegl.in/" target="_blank"><img src="images/website.svg" alt="Website"></a>
</p>
<p>{{ trans('public.about.razi_bio') }}</p>
</div>
<div class="col-md-3">
<div class="img-team">
<img src="images/benjacobson.jpg" alt="Ben Jacobsen">
</div>
<h2>Ben Jacobson</h2>
<p>{{ trans('public.about.marketing') }}</p>
<p class="social yellow"><a href="https://twitter.com/osbennn" target="_blank"><img src="images/twitter.svg" alt="Twitter"></a>
<a href="http://www.linkedin.com/in/osbennn" target="_blank"><img src="images/linkedin.svg" alt=""></a>
<a href="http://about.me/osbennn" target="_blank"><img src="images/me.svg" alt="Me"></a>
<a href="http://actionpackedmedia.com/" target="_blank"><img src="images/website.svg" alt="Website"></a>
</p>
<p>{{ trans('public.about.ben_bio') }}</p>
</div>
</div>
</div>
</section>
@stop

View File

@ -1,320 +0,0 @@
@extends('public.header')
@section('content')
<section class="hero background hero-plans" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1><img src="{{ asset('images/icon-plans.png') }}">{{ trans('public.compare.header') }}</h1>
</div>
</div>
</section>
<section class="plans center">
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{{ trans('public.compare.free_plan_comparison') }}</h2>
<table class="table compare-table compare-table-free">
<thead>
<tr>
<th>{{ trans('public.compare.app') }}</th>
<th>{{ trans('public.compare.cost') }}</th>
<th>{{ trans('public.compare.clients') }}</th>
<th>{{ trans('public.compare.invoices') }}</th>
<th>{{ trans('public.compare.payment_gateways') }}</th>
<th>{{ trans('public.compare.custom_logo') }}</th>
<th>{{ trans('public.compare.multiple_templates') }}</th>
<th>{{ trans('public.compare.recurring_payments') }}</th>
<th>{{ trans('public.compare.open_source') }}</th>
</tr>
</thead>
<tbody>
<tr class="active">
<td><b><a href="https://www.invoiceninja.com" target="_blank">Invoice Ninja</a></b></td>
<td>{{ trans('public.compare.free') }}</td>
<td>500</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>23</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
</tr>
<tr>
<td><a href="http://www.freshbooks.com" target="_blank" rel="nofollow">FreshBooks</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>3</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>13</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.waveapps.com" target="_blank" rel="nofollow">Wave</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>1</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.nutcache.com" target="_blank" rel="nofollow">NutCache</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>3</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://curdbee.com/" target="_blank" rel="nofollow">CurdBee</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>3</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="https://www.zoho.com/invoice/" target="_blank" rel="nofollow">Zoho Invoice</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>5</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>6</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.roninapp.com/" target="_blank" rel="nofollow">Ronin</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>2</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>1</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://invoiceable.co/" target="_blank" rel="nofollow">Invoiceable</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>1</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.getharvest.com/" target="_blank" rel="nofollow">Harvest</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>4</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>4</td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://invoiceocean.com/" target="_blank" rel="nofollow">InvoiceOcean</a></td>
<td>{{ trans('public.compare.free') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>3</td>
<td>1</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2>{{ trans('public.compare.paid_plan_comparison') }}</h2>
<table class="table compare-table compare-table-paid">
<thead>
<tr>
<th>{{ trans('public.compare.app') }}</th>
<th>{{ trans('public.compare.cost') }}</th>
<th>{{ trans('public.compare.clients') }}</th>
<th>{{ trans('public.compare.invoices') }}</th>
<th>{{ trans('public.compare.payment_gateways') }}</th>
<th>{{ trans('public.compare.custom_logo') }}</th>
<th>{{ trans('public.compare.multiple_templates') }}</th>
<th>{{ trans('public.compare.recurring_payments') }}</th>
<th>{{ trans('public.compare.open_source') }}</th>
</tr>
</thead>
<tbody>
<tr class="active">
<td><b><a href="https://www.invoiceninja.com" target="_blank">Invoice Ninja</a></b></td>
<td>$50 {{ trans('public.compare.per_year') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>23</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
</tr>
<tr>
<td><a href="http://www.freeagent.com" target="_blank" rel="nofollow">FreeAgent</a></td>
<td>$20 {{ trans('public.compare.per_month') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>3</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="https://www.xero.com/" target="_blank" rel="nofollow">Xero</a></td>
<td>$20 {{ trans('public.compare.per_month') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>5</td>
<td>8</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.invoice2go.com" target="_blank" rel="nofollow">Invoice2go</a></td>
<td>$49 {{ trans('public.compare.per_year') }}</td>
<td>50</td>
<td>100</td>
<td>1</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://invoicemachine.com/" target="_blank" rel="nofollow">Invoice Machine</a></td>
<td>$12 {{ trans('public.compare.per_month') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>30</td>
<td>2</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.freshbooks.com" target="_blank" rel="nofollow">FreshBooks</a></td>
<td>$20 {{ trans('public.compare.per_month') }}</td>
<td>25</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>13</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://curdbee.com" target="_blank" rel="nofollow">CurdBee</a></td>
<td>$50 {{ trans('public.compare.per_year') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>3</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="https://www.zoho.com/invoice/" target="_blank" rel="nofollow">Zoho Invoice</a></td>
<td>$15 {{ trans('public.compare.per_month') }}</td>
<td>500</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>6</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.roninapp.com/" target="_blank" rel="nofollow">Ronin</a></td>
<td>$29 {{ trans('public.compare.per_month') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>2</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.getharvest.com/" target="_blank" rel="nofollow">Harvest</a></td>
<td>$12 {{ trans('public.compare.per_month') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>4</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://invoiceocean.com/" target="_blank" rel="nofollow">Invoice Ocean</a></td>
<td>$9 {{ trans('public.compare.per_month') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>1</td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
<tr>
<td><a href="http://www.apptivo.com/" target="_blank" rel="nofollow">Apptivo</a></td>
<td>$10 {{ trans('public.compare.per_month') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>{{ trans('public.compare.unlimited') }}</td>
<td>3</td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
<td><span class="glyphicon glyphicon-ok"/></td>
<td><span class="glyphicon glyphicon-remove"/></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<section class="upper-footer white-bg">
<div class="container">
<div class="row">
<div class="col-md-3 center-block">
<a href="#">
<div class="cta">
<h2 onclick="return getStarted()">{{ trans('public.invoice_now') }} <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
@stop

View File

@ -1,124 +0,0 @@
@extends('public.header')
@section('content')
<script>
$(function() {
$("#feedbackSubmit").click(function() {
//clear any errors
contactForm.clearErrors();
//do a little client-side validation -- check that each field has a value and e-mail field is in proper format
var hasErrors = false;
$('.feedbackForm input,textarea').each(function() {
if (!$(this).val()) {
hasErrors = true;
contactForm.addError($(this));
}
});
var $email = $('#email');
if (!contactForm.isValidEmail($email.val())) {
hasErrors = true;
contactForm.addError($email);
}
//if there are any errors return without sending e-mail
if (hasErrors) {
return false;
}
});
});
//namespace as not to pollute global namespace
var contactForm = {
isValidEmail: function (email) {
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
return regex.test(email);
},
clearErrors: function () {
$('#emailAlert').remove();
$('.feedbackForm .help-block').hide();
$('.feedbackForm .form-group').removeClass('has-error');
},
addError: function ($input) {
$input.siblings('.help-block').show();
$input.parent('.form-group').addClass('has-error');
},
addAjaxMessage: function(msg, isError) {
$("#feedbackSubmit").after('<div id="emailAlert" class="alert alert-' + (isError ? 'danger' : 'success') + '" style="margin-top: 5px;">' + $('<div/>').text(msg).html() + '</div>');
}
};
</script>
<section class="hero background hero-contact center" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1><img src="{{ asset('images/icon-contact.png') }}">Contact <span class="thin">us</span></h1>
</div>
</div>
</section>
<section class="about contact">
<div class="container">
<div id="contact_form" class="row">
<div class="row">
<div class="col-md-7">
<div class="headline">
<h2>{{ trans('public.contact.header') }}</h2>
<p>{{ trans('public.contact.sub_header') }}</p>
</div>
{{ Form::open(['url' => 'contact_submit', 'class' => 'feedbackForm']) }}
<div class="form-group">
<input type="text" class="form-control" id="name" name="name" placeholder="{{ trans('public.contact.name') }}">
<span class="help-block" style="display: none;">{{ trans('public.contact.name_help') }}</span>
</div>
<div class="form-group">
<input type="email" class="form-control" id="email" name="email" placeholder="{{ trans('public.contact.email') }}">
<span class="help-block" style="display: none;">{{ trans('public.contact.email_help') }}</span>
</div>
<div class="form-group">
<textarea rows="10" cols="100" class="form-control" id="message" name="message" placeholder="{{ trans('public.contact.message') }}"></textarea>
<span class="help-block" style="display: none;">{{ trans('public.contact.message_help') }}</span>
</div>
<div class="row">
<div class="col-md-5">
<button type="submit" id="feedbackSubmit" class="btn btn-primary btn-lg">{{ trans('public.contact.send_message') }} <span class="glyphicon glyphicon-send"></span></button>
</div>
</div>
{{ Form::close() }}
</div>
<div class="col-md-4 col-md-offset-1 address">
<h2>{{ trans('public.contact.other_ways') }}</h2>
<p><span class="glyphicon glyphicon-send"></span><a href="mailto:contact@invoiceninja.com">contact@invoiceninja.com</a></p>
<p><span class="glyphicon glyphicon-comment"></span><a href="http://www.invoiceninja.org" target="_blank">Google Group</a></p>
<p><span class="socicon">Q</span><a href="https://github.com/hillelcoren/invoice-ninja" target="_blank">GitHub Project</a></div></p>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="upper-footer white-bg">
<div class="container">
<div class="row">
<div class="col-md-3 center-block">
<a href="#">
<div class="cta">
<h2 onclick="return getStarted()">{{ trans('public.invoice_now') }} <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
@stop

View File

@ -1,149 +0,0 @@
@extends('public.header')
@section('content')
<section class="hero background hero-faq" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1>{{ trans('public.faq.header') }}</h1>
</div>
</div>
</section>
<section class="faq">
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question1') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer1') }}</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question2') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer2') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question3') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer3') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question4') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer4') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question5') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer5') }}</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question6') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer6') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question7') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer7') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question8') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer8') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question9') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer9') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question10') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer10') }}
</p>
</div>
</div>
@if (Utils::getDemoAccountId())
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question11') }}</a>
<div class="content">
<p>{{ trans('public.faq.answer11') }}
</p>
</div>
</div>
@endif
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question12') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer12') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question13') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer13') }}
</p>
</div>
</div>
<div class="question">
<a class="expander" href="#">{{ trans('public.faq.question14') }}
</a>
<div class="content">
<p>{{ trans('public.faq.answer14') }}
</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="contact-box">
<div class="row">
<div class="col-md-4">
<img src="{{ asset('images/icon-faq.png') }}">
<h2>{{ trans('public.faq.miss_something') }}</h2>
</div>
<div class="col-md-8 valign">
<p>{{ trans('public.faq.miss_something_text') }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
@stop

View File

@ -1,119 +0,0 @@
@extends('public.header')
@section('content')
<section class="hero background hero-features center" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1><img src="{{ asset('images/icon-features.png') }}">{{ trans('public.features.header') }}</h1>
</div>
</div>
</section>
<section class="features features1">
<div class="container">
<div class="row">
<div class="col-md-5 valign">
<div class="headline">
<div class="icon open"><span class="img-wrap"><img src="{{ asset('images/icon-opensource.png') }}"></span></div><h2>{{ trans('public.features.open_source') }}</h2>
</div>
<p class="first">{{ trans('public.features.open_source_text1') }}</p>
<p>{{ trans('public.features.open_source_text2') }}</p>
</div>
<div class="col-md-7">
<img src="{{ asset('images/features1.jpg') }}">
</div>
</div>
</div>
</section>
<section class="blue features">
<div class="container">
<div class="row">
<div class="col-md-7">
<img src="{{ asset('images/devices-2.png') }}">
</div>
<div class="col-md-5 valign">
<div class="headline">
<div class="icon free"><span class="img-wrap"><img src="{{ asset('images/icon-free2.png') }}"></span></div><h2>{{ trans('public.features.free_forever') }}</h2>
</div>
<p class="first">{{ trans('public.features.free_forever_text1') }}</p>
<p>{{ trans('public.features.free_forever_text2') }}</p>
</div>
</div>
</div>
</section>
<section class="features features3">
<div class="container">
<div class="row">
<div class="col-md-5">
<div class="headline">
<div class="icon secure"><span class="img-wrap"><img src="{{ asset('images/icon-secure.png') }}"></span></div><h2>{{ trans('public.features.secure') }}</h2>
</div>
<p class="first">{{ trans('public.features.secure_text1') }}</p>
<p>{{ trans('public.features.secure_text2') }}</p>
</div>
<div class="col-md-7 valign">
<img src="{{ asset('images/laptopwicon.jpg') }}">
</div>
</div>
</div>
</section>
<section class="features features4">
<div class="container">
<div class="row">
<div class="col-md-7">
<img src="{{ asset('images/features4.jpg') }}">
</div>
<div class="col-md-5 valign">
<div class="headline">
<div class="icon pdf"><span class="img-wrap"><img src="{{ asset('images/icon-pdf.png') }}"></span></div><h2>{{ trans('public.features.live_pdf') }}</h2>
</div>
<p class="first">{{ trans('public.features.live_pdf_text1') }}</p>
<p>{{ trans('public.features.live_pdf_text2') }}</p>
<p><i>{{ trans('public.features.live_pdf_text3') }}</i></p>
</div>
</div>
</div>
</section>
<section class="features features5">
<div class="container">
<div class="row">
<div class="col-md-5 valign">
<div class="headline">
<div class="icon pay"><span class="img-wrap"><img src="{{ asset('images/icon-payment.png') }}"></span></div><h2>{{ trans('public.features.online_payments') }}</h2>
</div>
<p class="first">{{ trans('public.features.online_payments_text1') }}</p>
<p>{{ trans('public.features.online_payments_text2') }}</p>
</div>
<div class="col-md-7">
<img src="{{ asset('images/features5.jpg') }}">
</div>
</div>
</div>
</section>
<section class="upper-footer features center">
<div class="container">
<div class="row">
<div class="col-md-4">
<h2 class="thin">{{ trans('public.features.footer') }}</h2>
</div>
<div class="col-md-4">
<a href="#">
<div class="cta">
<h2 onclick="return getStarted()">{{ trans('public.features.footer_action') }}</h2>
</div>
</a>
</div>
</div>
</div>
</section>
@stop

View File

@ -1,36 +1,107 @@
@extends('master')
@section('head')
@section('head')
<link href="{{ asset('built.public.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
<script src="{{ asset('js/built.public.js') }}?no_cache={{ NINJA_VERSION }}" type="text/javascript"></script>
<style type="text/css">
<style>
.hero {
background-image: url({{ asset('/images/hero-bg-1.jpg') }});
}
.hero-about {
background-image: url({{ asset('/images/hero-bg-3.jpg') }});
}
.hero-plans {
background-image: url({{ asset('/images/hero-bg-plans.jpg') }});
}
.hero-contact {
background-image: url({{ asset('/images/hero-bg-contact.jpg') }});
}
.hero-features {
background-image: url({{ asset('/images/hero-bg-3.jpg') }});
}
.hero-secure {
background-image: url({{ asset('/images/hero-bg-secure-pay.jpg') }});
}
.hero-faq {
background-image: url({{ asset('/images/hero-bg-faq.jpg') }});
}
.hero-testi {
background-image: url({{ asset('/images/hero-bg-testi.jpg') }});
}
body {
font-family: 'Roboto', sans-serif;
font-size: 14px;
}
@media screen and (min-width: 700px) {
.navbar-header {
padding-top: 16px;
padding-bottom: 16px;
}
.navbar li a {
padding: 31px 20px 31px 20px;
}
}
#footer {
text-align: center
}
#footer .top {
background: #2e2b2b;
font-size: 12px;
font-weight: 900;
text-transform: uppercase;
padding: 40px 0 27px;
}
#footer .top li {
display: inline-block;
margin: 0 30px 10px;
}
#footer .top a {
color: #fff;
text-decoration: none;
}
#footer .bottom {
border-top: 1px solid #5f5d5d;
background: #211f1f;
font-size: 11px;
font-weight: 400;
color: #636262;
padding: 28px 0;
}
#footer .menu-item-31 a:before {
content: '';
display: inline-block;
width: 9px;
height: 15px;
background: url({{ asset('images/social/facebook.svg') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
#footer .menu-item-32 a:before {
content: '';
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/twitter.svg') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
#footer .menu-item-33 a:before {
content: '';
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/github.png') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
/* Hide bootstrap sort header icons */
table.table thead .sorting:after { content: '' !important }
table.table thead .sorting_asc:after { content: '' !important }
table.table thead .sorting_desc:after { content: '' !important}
table.table thead .sorting_asc_disabled:after { content: '' !important }
table.table thead .sorting_desc_disabled:after { content: '' !important }
@media screen and (min-width: 700px) {
#footer .top {
padding: 27px 0;
}
#footer .bottom {
padding: 25px 0;
}
}
</style>
@ -55,465 +126,154 @@
{{ Form::close() }}
<script>
if (isStorageSupported()) {
$('[name="guest_key"]').val(localStorage.getItem('guest_key'));
}
@if (isset($invoiceNow) && $invoiceNow)
getStarted();
@endif
function isStorageSupported() {
if ('localStorage' in window && window['localStorage'] !== null) {
var storage = window.localStorage;
} else {
return false;
if (isStorageSupported()) {
$('[name="guest_key"]').val(localStorage.getItem('guest_key'));
}
var testKey = 'test';
try {
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
@if (isset($invoiceNow) && $invoiceNow)
getStarted();
@endif
function isStorageSupported() {
if ('localStorage' in window && window['localStorage'] !== null) {
var storage = window.localStorage;
} else {
return false;
}
var testKey = 'test';
try {
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
}
function getStarted() {
$('#startForm').submit();
return false;
}
}
</script>
@if ((!isset($hideHeader) || !$hideHeader) && (!isset($showClientHeader) || !$showClientHeader))
<div class="navbar-top navbar hide-phone" style="margin-bottom:0px">
<div class="container">
<div class="navbar-inner">
<ul>
<li>{{ link_to('https://www.invoiceninja.com/about', trans('public.link_about_us') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/contact', trans('public.link_contact_us') ) }}</li>
<li>{{ link_to('http://blog.invoiceninja.com', trans('public.link_blog') ) }}&nbsp;&nbsp;</li>
<li><a href="https://www.facebook.com/invoiceninja" target="_blank"><span class="socicon">b</span></a></li>
<li><a href="https://twitter.com/invoiceninja" target="_blank"><span class="socicon">a</span></a></li>
<li><a href="https://www.linkedin.com/company/invoice-ninja" target="_blank"><span class="socicon">j</span></a></li>
<li><a href="https://plus.google.com/104031016152831072143" target="_blank"><span class="socicon">c</span></a></li>
<li><a href="https://github.com/hillelcoren/invoice-ninja" target="_blank"><span class="socicon">Q</span></a></li>
<li><a href="https://www.pinterest.com/invoiceninja" target="_blank"><span class="socicon">d</span></a></li>
<li><a href="http://blog.invoiceninja.com/feed/rss2" target="_blank"><span class="socicon">,</span></a></li>
</ul>
</div>
</div>
</div>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="navbar-header">
@if (!isset($hideLogo) || !$hideLogo)
{{-- Per our license, please do not remove or modify this link. --}}
<a class="navbar-brand" href="https://www.invoiceninja.com/"><img src="{{ asset('images/invoiceninja-logo.png') }}"></a>
@endif
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">{{ trans('public.toggle_navigation') }}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<nav class="navbar navbar-top navbar-inverse">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@if (!isset($hideLogo) || !$hideLogo)
{{-- Per our license, please do not remove or modify this link. --}}
<a class="navbar-brand" href="https://www.invoiceninja.com/"><img src="{{ asset('images/invoiceninja-logo.png') }}"></a>
@endif
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="hide-desktop">{{ link_to('http://blog.invoiceninja.com', trans('public.link_blog') ) }}</li>
<li class="hide-desktop">{{ link_to('https://www.invoiceninja.com/about', trans('public.link_about_us') ) }}</li>
<li class="hide-desktop">{{ link_to('https://www.invoiceninja.com/contact', trans('public.link_contact_us') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/features', trans('public.link_features') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/plans', trans('public.link_plans') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/compare-online-invoicing', trans('public.link_compare') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/testimonials', trans('public.link_testimonials') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/faq', trans('public.link_faq') ) }}</li>
<li><span class="glyphicon glyphicon-user"></span>
{{ link_to('login', trans(Auth::check() ? 'public.my_account' : 'public.login' )) }}</li>
</ul>
<div id="navbar" class="collapse navbar-collapse">
@if (!isset($hideHeader) || !$hideHeader)
<ul class="nav navbar-nav navbar-right">
<li {{ Request::is('*client/quotes') ? 'class="active"' : '' }}>
{{ link_to('/client/quotes', trans('texts.quotes') ) }}
</li>
<li {{ Request::is('*client/invoices') ? 'class="active"' : '' }}>
{{ link_to('/client/invoices', trans('texts.invoices') ) }}
</li>
<li {{ Request::is('*client/payments') ? 'class="active"' : '' }}>
{{ link_to('/client/payments', trans('texts.payments') ) }}
</li>
</ul>
@endif
</div><!--/.nav-collapse -->
</div>
</div>
</div>
@else
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="navbar-header">
{{-- Per our license, please do not remove or modify this link. --}}
@if (!isset($hideLogo) || !$hideLogo)
<a class="navbar-brand" href="https://www.invoiceninja.com/"><img src="{{ asset('images/invoiceninja-logo.png') }}"></a>
</nav>
<div class="container">
@if (Session::has('warning'))
<div class="alert alert-warning">{{ Session::get('warning') }}</div>
@endif
@if (Session::has('message'))
<div class="alert alert-info">{{ Session::get('message') }}</div>
@endif
@if (Session::has('error'))
<div class="alert alert-danger">{{ Session::get('error') }}</div>
@endif
</div>
@if (isset($showClientHeader) && $showClientHeader)
<ul class="nav navbar-nav">
<span/>
<li>{{ link_to('/client/quotes', trans('texts.quotes') ) }}</li>
<li>{{ link_to('/client/invoices', trans('texts.invoices') ) }}</li>
<li>{{ link_to('/client/payments', trans('texts.payments') ) }}</li>
<span/>
</ul>
@endif
</div>
</div>
@endif
<div style="background-color:#211f1f; width:100%">
<div class="container">
@if (Session::has('warning'))
<div class="alert alert-warning">{{ Session::get('warning') }}</div>
@endif
@yield('content')
@if (Session::has('message'))
<div class="alert alert-info">{{ Session::get('message') }}</div>
@endif
@if (Session::has('error'))
<div class="alert alert-danger">{{ Session::get('error') }}</div>
@endif
</div>
</div>
@yield('content')
<footer id="footer" role="contentinfo">
<div class="top">
<div class="wrap">
@if (!isset($hideLogo) || !$hideLogo)
<div id="footer-menu" class="menu-wrap">
<ul id="menu-footer-menu" class="menu">
<li id="menu-item-30" class="menu-item-30">
{{ link_to(NINJA_WEB_URL . '/contact', trans('texts.support')) }}
</li>
<li id="menu-item-29" class="menu-item-29">
{{ link_to('/terms', trans('texts.terms')) }}
</li>
<li id="menu-item-31" class="menu-item-31">
{{ link_to('#', 'Facebook', ['target' => '_blank', 'onclick' => 'openUrl("https://www.facebook.com/invoiceninja", "/footer/social/facebook")']) }}
</li>
<li id="menu-item-32" class="menu-item-32">
{{ link_to('#', 'Twitter', ['target' => '_blank', 'onclick' => 'openUrl("https://twitter.com/invoiceninja", "/footer/social/twitter")']) }}
</li>
<li id="menu-item-33" class="menu-item-33">
{{ link_to('#', 'GitHub', ['target' => '_blank', 'onclick' => 'openUrl("https://github.com/hillelcoren/invoice-ninja", "/footer/social/github")']) }}
</li>
</ul>
</div>
@endif
</div><!-- .wrap -->
</div><!-- .top -->
<div class="bottom">
<div class="wrap">
<div class="copy">Copyright &copy;2015 InvoiceNinja. All rights reserved.</div>
</div><!-- .wrap -->
</div><!-- .bottom -->
</footer><!-- #footer -->
<footer class="footer" style="min-height:400px">
<!--<div class="fb-follow" data-href="https://www.facebook.com/invoiceninja" data-colorscheme="light" data-layout="button" data-show-faces="false"></div>-->
@if ((!isset($hideHeader) || !$hideHeader) && (!isset($showClientHeader) || !$showClientHeader))
<div class="container">
<div class="row">
<div class="col-md-4">
<!--<div class="fb-follow" data-href="https://www.facebook.com/invoiceninja" data-colorscheme="light" data-layout="button" data-show-faces="false"></div>-->
<!--<a href="https://twitter.com/invoiceninja" class="twitter-follow-button" data-show-count="false" data-size="large">Follow @invoiceninja</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>-->
<!--<div class="fb-like" data-href="https://www.invoiceninja.com" data-layout="button" data-action="like" data-show-faces="false" data-share="false"></div> -->
<!--
<div class="fb-share-button" data-href="https://www.invoiceninja.com/" data-type="button"></div>
&nbsp;
<!--<a href="https://twitter.com/invoiceninja" class="twitter-follow-button" data-show-count="false" data-size="large">Follow @invoiceninja</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>-->
<!--<div class="fb-like" data-href="https://www.invoiceninja.com" data-layout="button" data-action="like" data-show-faces="false" data-share="false"></div> -->
<!--
<div class="fb-share-button" data-href="https://www.invoiceninja.com/" data-type="button"></div>
&nbsp;
<a href="https://twitter.com/share" class="twitter-share-button" data-url="https://www.invoiceninja.com/" data-via="invoiceninja" data-related="hillelcoren" data-count="none" data-text="Free online invoicing">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
&nbsp;
<div class="g-plusone" data-size="medium" data-width="300" data-href="https://www.invoiceninja.com/" data-annotation="none" data-count="false" data-recommendations="false"></div>
<a href="https://twitter.com/share" class="twitter-share-button" data-url="https://www.invoiceninja.com/" data-via="invoiceninja" data-related="hillelcoren" data-count="none" data-text="Free online invoicing">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
&nbsp;
<div class="g-plusone" data-size="medium" data-width="300" data-href="https://www.invoiceninja.com/" data-annotation="none" data-count="false" data-recommendations="false"></div>
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/platform.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
&nbsp;
-->
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/platform.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
&nbsp;
-->
<!--
<script src="//platform.linkedin.com/in.js" type="text/javascript">
lang: en_US
</script>
<script type="IN/Share" data-url="https://www.invoiceninja.com/"></script>
-->
<!--
<script src="//platform.linkedin.com/in.js" type="text/javascript">
lang: en_US
</script>
<script type="IN/Share" data-url="https://www.invoiceninja.com/"></script>
-->
<!--<iframe src="http://ghbtns.com/github-btn.html?user=hillelcoren&repo=invoice-ninja&type=watch" allowtransparency="true" frameborder="0" scrolling="0" width="62" height="20"></iframe>-->
<a href="{{ NINJA_WEB_URL }}"><img src="{{ asset('images/footer-logo.png') }}"></a>
<hr>
<ul class="navbar-vertical">
<li>{{ link_to('https://www.invoiceninja.com/features', trans('public.link_features') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/plans', trans('public.link_plans') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/compare-online-invoicing', trans('public.link_compare') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/testimonials', trans('public.link_testimonials') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/faq', trans('public.link_faq') ) }}</li>
<li>{{ link_to('http://blog.invoiceninja.com', trans('public.link_blog') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/about', trans('public.link_about_us') ) }}</li>
<li>{{ link_to('https://www.invoiceninja.com/contact', trans('public.link_contact_us') ) }}</li>
</ul>
</div>
<div class="col-md-4">
<h3><span class="glyphicon glyphicon-share-alt"></span>{{ trans('public.connect_with_us') }}</h3>
<div class="social">
<div class="row1">
<a href="https://www.facebook.com/invoiceninja" target="_blank"><img src="{{ asset('images/hex-facebook.png') }}"></a>
<a href="https://twitter.com/invoiceninja" target="_blank"><img src="{{ asset('images/hex-twitter.png') }}"></a>
<a href="https://www.linkedin.com/company/invoice-ninja" target="_blank"><img src="{{ asset('images/hex-linkedin.png') }}"></a>
<a href="https://plus.google.com/104031016152831072143" target="_blank"><img src="{{ asset('images/hex-gplus.png') }}"></a>
</div>
<div class="row2">
<a href="https://github.com/hillelcoren/invoice-ninja" target="_blank"><img src="{{ asset('images/hex-github.png') }}"></a>
<a href="https://www.pinterest.com/invoiceninja" target="_blank"><img src="{{ asset('images/hex-pinterest.png') }}"></a>
<a href="http://feeds.feedburner.com/invoiceninja" target="_blank"><img src="{{ asset('images/hex-rss.png') }}"></a>
</div>
</div>
<!--<h3><span class="glyphicon glyphicon-envelope"></span>Join Our Free Newsletter</h3>-->
<div class="form-group">
<!--
<input type="email" class="form-control" id="email" name="email" placeholder="Email Address">
{{ Button::submit('')->append_with_icon('chevron-right') }}
<span class="help-block" style="display: none;">Please enter a valid e-mail address.</span>
-->
<!--
<form accept-charset="UTF-8" action="https://madmimi.com/signups/subscribe/107799" id="mad_mimi_signup_form" method="post" target="_blank">
<div style="margin:0;padding:0;display:inline">
<input name="utf8" type="hidden" value=""/>
<input name="authenticity_token" type="hidden" value="jLAaWeg+Ew4gkbOUnMq+lv/Lt76bPCI/IGAWVitkFJ0="/>
</div>
<div class="mimi_field required">
<input id="signup_email" placeholder="Email" class="form-control" name="signup[email]" type="text" data-required-field="This field is required" placeholder="you@example.com"/>
<br/>
</div>
<div>
<input id="signup_first_name" placeholder="First Name" class="form-control" name="signup[first_name]" type="text" data-required-field="This field is required"/>
<br/>
</div>
<div>
<input id="signup_last_name" placeholder="Last Name" class="form-control" name="signup[last_name]" type="text" data-required-field="This field is required"/>
<br/>
</div>
<div>
<input type="submit" class="btn-lg btn-block submit" value="Subscribe" id="webform_submit_button" data-default-text="Subscribe" data-submitting-text="Sending…" data-invalid-text="↑ You forgot some required fields" data-choose-list="↑ Choose a list">
</input>
</div>
</form>
-->
<!--<iframe src="http://ghbtns.com/github-btn.html?user=hillelcoren&repo=invoice-ninja&type=watch" allowtransparency="true" frameborder="0" scrolling="0" width="62" height="20"></iframe>-->
<script type="text/javascript">
/*
(function() {
var form = document.getElementById('mad_mimi_signup_form'),
submit = document.getElementById('webform_submit_button'),
validEmail = /.+@.+\..+/,
isValid;
form.onsubmit = function(event) {
validate();
if(!isValid) {
revalidateOnChange();
return false;
}
};
function validate() {
isValid = true;
emailValidation();
fieldAndListValidation();
updateFormAfterValidation();
}
function emailValidation() {
var email = document.getElementById('signup_email');
if(!validEmail.test(email.value)) {
textFieldError(email);
isValid = false;
} else {
removeTextFieldError(email);
}
}
function fieldAndListValidation() {
var fields = form.querySelectorAll('.mimi_field.required');
for(var i = 0; i < fields.length; ++i) {
var field = fields[i],
type = fieldType(field);
if(type == 'checkboxes' || type == 'radio_buttons') {
checkboxAndRadioValidation(field);
} else {
textAndDropdownValidation(field, type);
}
}
}
function fieldType(field) {
var type = field.querySelectorAll('.field_type');
if(type.length > 0) {
return type[0].getAttribute('data-field-type');
} else if(field.className.indexOf('checkgroup') >= 0) {
return 'checkboxes';
} else {
return 'text_field';
}
}
function checkboxAndRadioValidation(field) {
var inputs = field.getElementsByTagName('input'),
selected = false;
for(var i = 0; i < inputs.length; ++i) {
var input = inputs[i];
if((input.type == 'checkbox' || input.type == 'radio') && input.checked) selected = true;
}
if(selected) {
field.className = field.className.replace(/ invalid/g, '');
} else {
if(field.className.indexOf('invalid') == -1) field.className += ' invalid';
isValid = false;
}
}
function textAndDropdownValidation(field, type) {
var inputs = field.getElementsByTagName('input');
for(var i = 0; i < inputs.length; ++i) {
var input = inputs[i];
if(input.name.indexOf('signup') >= 0) {
if(type == 'text_field') {
textValidation(input);
} else {
dropdownValidation(field, input);
}
}
}
htmlEmbedDropdownValidation(field);
}
function textValidation(input) {
if(input.id == 'signup_email') return;
var val = input.value;
if(val == '') {
textFieldError(input);
isValid = false;
return;
} else {
removeTextFieldError(input)
}
}
function dropdownValidation(field, input) {
var val = input.value;
if(val == '') {
if(field.className.indexOf('invalid') == -1) field.className += ' invalid';
onSelectCallback(input);
isValid = false;
return;
} else {
field.className = field.className.replace(/ invalid/g, '');
}
}
function htmlEmbedDropdownValidation(field) {
var dropdowns = field.querySelectorAll('.mimi_html_dropdown');
for(var i = 0; i < dropdowns.length; ++i) {
var dropdown = dropdowns[i],
val = dropdown.value;
if(val == '') {
if(field.className.indexOf('invalid') == -1) field.className += ' invalid';
isValid = false;
dropdown.onchange = validate;
return;
} else {
field.className = field.className.replace(/ invalid/g, '');
}
}
}
function textFieldError(input) {
input.className = 'required invalid';
input.placeholder = input.getAttribute('data-required-field');
}
function removeTextFieldError(input) {
input.className = 'required';
input.placeholder = '';
}
function onSelectCallback(input) {
if(typeof Widget != 'undefined' && Widget.BasicDropdown != undefined) {
var dropdownEl = input.parentNode,
instances = Widget.BasicDropdown.instances;
for(var i = 0; i < instances.length; ++i) {
var instance = instances[i];
if(instance.wrapperEl == dropdownEl) {
instance.onSelect = validate;
}
}
}
}
function updateFormAfterValidation() {
form.className = isValid ? '' : 'mimi_invalid';
submit.value = submitButtonText();
submit.disabled = !isValid;
submit.className = isValid ? 'submit' : 'disabled';
}
function submitButtonText() {
var invalidFields = document.querySelectorAll('.invalid'),
text;
if(isValid || invalidFields == undefined) {
text = submit.getAttribute('data-default-text');
} else {
if(invalidFields.length > 1 || invalidFields[0].className.indexOf('checkgroup') == -1) {
text = submit.getAttribute('data-invalid-text');
} else {
text = submit.getAttribute('data-choose-list');
}
}
return text;
}
function revalidateOnChange() {
var fields = form.querySelectorAll(".mimi_field.required");
for(var i = 0; i < fields.length; ++i) {
var inputs = fields[i].getElementsByTagName('input');
for(var j = 0; j < inputs.length; ++j) {
inputs[j].onchange = validate;
}
}
}
})();
*/
</script>
</div>
</form>
</div>
<div class="col-md-4">
@if (Request::secure())
<h3><img src="{{ asset('images/icon-secure-footer.png') }}" style="margin-right: 8px; margin-top: -5px;"></span>{{ trans('public.safe_and_secure') }}</h3>
<img src="{{ asset('images/ssl-footer.png') }}">
<hr>
@else
<h3>&nbsp;</h3>
@endif
<a href="http://opensource.org/" target="_blank"><img src="{{ asset('images/opensource-footer.png') }}"></a>
</div>
<!--
<ul class="navbar-list">
<li><a href="#">For developers</a></li>
<li><a href="#">Jobs</a></li>
<li><a href="#">Terms &amp; Conditions</a></li>
<li><a href="#">Our Blog</a></li>
</ul>
-->
</div>
</div>
</div>
</div>
@endif
</footer>
<script type="text/javascript">
jQuery(document).ready(function($) {
$('.valign').vAlign();
});
</script>
<!--
<script type="text/javascript">
jQuery(document).ready(function($) {
$('.expander').simpleexpand();
});
</script>
-->
<!--
All images in the site need to have retina versions otherwise the log fills up with requests for missing files
<script src="{{ asset('/js/retina-1.1.0.min.js') }}" type="text/javascript"></script>
-->
@stop
@stop

View File

@ -2,32 +2,100 @@
@section('content')
<section class="hero background hero-secure center" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1>License Key</h1>
<!--<p class="thin"><img src="{{ asset('images/icon-secure-pay.png') }}">256-BiT Encryption</p>-->
<!-- <img src="{{ asset('images/providers.png') }}"> -->
</div>
</div>
</section>
<style type="text/css">
body {
background-color: #f8f8f8;
color: #1b1a1a;
}
.panel-body {
padding-bottom: 100px;
}
@media screen and (min-width: 700px) {
header {
margin: 20px 0 75px;
float: left;
}
.panel-body {
padding-left: 150px;
padding-right: 150px;
}
}
header {
margin: 0px !important
}
h2 {
font-weight: 300;
font-size: 30px;
color: #2e2b2b;
line-height: 1;
}
h3 {
font-weight: 900;
margin-top: 10px;
font-size: 15px;
}
h3 .help {
font-style: italic;
font-weight: normal;
color: #888888;
}
header h3 {
text-transform: uppercase;
}
header h3 span {
display: inline-block;
margin-left: 8px;
}
header h3 em {
font-style: normal;
color: #eb8039;
}
</style>
<div class="container">
<p>&nbsp;</p>
<section class="faq">
<div class="container">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-7">
<header>
<h2>License Key<br/><small>{{ $message }}</small></h2>
</header>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<div class="row">
<div class="col-md-12">
<h3 style="text-align:center">{{ $message }}</h3>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2 style="text-align:center">{{ $license }}</h2>
<h2 style="text-align:center">{{ $license }}</h2>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<p>&nbsp;</p>
<div style="height:300px"></div>
</div>
@stop

View File

@ -1,46 +0,0 @@
@extends('public.header')
@section('content')
<section class="hero background hero-plans" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1><img src="{{ asset('images/icon-plans.png') }}">{{ trans('public.plans.header') }}</h1>
</div>
</div>
</section>
<section class="plans center">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>{{ trans('public.plans.go_pro') }}</h2>
<p>{{ trans('public.plans.go_pro_text') }}</p>
</div>
</div>
</div>
<div class="container">
@include('plans')
</div>
</div>
</section>
<section class="upper-footer white-bg">
<div class="container">
<div class="row">
<div class="col-md-3 center-block">
<a href="#">
<div class="cta">
<h2 onclick="return getStarted()">{{ trans('public.invoice_now') }} <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
@stop

View File

@ -1,239 +0,0 @@
@extends('public.header')
@section('content')
<!--<link href="http://localhost/invoice-ninja/public/css/customCss.css?no_cache=1.4.0" rel="stylesheet" type="text/css"/> -->
<link href="{{ asset('css/customCss.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
<section class="hero background hero1 center" data-speed="2" data-type="background">
<div class="caption-side"></div>
<div class="container">
<div class="row" style="margin:0;">
<div class="caption-wrap">
<div class="caption">
<h1>{{ trans('public.home.header') }}</h1>
<p>{{ trans('public.home.sub_header') }}</p>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-3 center-block">
<a href="#">
<div class="cta">
<h2 id="startButton" onclick="return getStarted()">{{ trans('public.invoice_now') }} <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
<section class="features-splash">
<div class="container">
<div class="row">
<div class="col-md-3 one">
<div class="box">
<div class="icon free"><span class="img-wrap"><img src="{{ asset('images/icon-free.png') }}"></span></div>
<h2>{{ trans('public.home.free_always') }}</h2>
<p>{{ trans('public.home.free_always_text') }}</p>
</div>
</div>
<div class="col-md-3 two">
<div class="box">
<div class="icon open"><span class="img-wrap"><img src="{{ asset('images/icon-opensource.png') }}"></span></div>
<h2>{{ trans('public.home.open_source') }}</h2>
<p>{{ trans('public.home.open_source_text') }}</p>
</div>
</div>
<div class="col-md-3 three">
<div class="box">
<div class="icon pdf"><span class="img-wrap"><img src="{{ asset('images/icon-pdf.png') }}"></span></div>
<h2>{{ trans('public.home.live_pdf') }}</h2>
<p>{{ trans('public.home.live_pdf_text') }}</p>
</div>
</div>
<div class="col-md-3 four">
<div class="box">
<div class="icon pay"><span class="img-wrap"><img src="{{ asset('images/icon-payment.png') }}"></span></div>
<h2>{{ trans('public.home.online_payments') }}</h2>
<p>{{ trans('public.home.online_payments_text') }}</p>
<p></p>
</div>
</div>
</div>
</div>
</section>
<section class="features-splash customContainer">
<div class="container">
<div class="row col-md-12">
<div class="col-md-5 col-md-offset-4 customFontHead">
<div class="col-md-12">
<span class="glyphicon glyphicon-ok"></span>
</div>
<div class="col-md-12 customTextBorder">
Invoice Ninja Does Everything You'd Expect Your Online Invoicing App to Do
</div>
</div>
</div>
<div class="row">
<div class="col-md-2 customMenuOne">
<div class="customMenuDiv">
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/InvoiceClientsViaEmail.png') }}"></span>
<span class="customSubMenu shiftLeft"> Invoice clients via email </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/InTuitiveEditingInterface.png') }}"></span>
<span class="customSubMenu shiftLeft"> Intuitive editing interface</span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/PrintablePDFInvoices.png') }}"></span>
<span class="customSubMenu shiftLeft"> Printable .pdf invoices</span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/MultipleTaxSettings.png') }}"></span>
<span class="customSubMenu shiftLeft"> Multiple tax settings</span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/ImportExportRecords.png') }}"></span>
<span class="customSubMenu shiftLeft"> Import/export records </span>
</div>
</div>
<div class="col-md-2 customMenuOne">
<div class="customMenuDiv">
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/AcceptPaymentsOnline.png') }}"></span>
<span class="customSubMenu shiftLeft"> Accept payments online</span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/BestInClassSecurity.png') }}"></span>
<span class="customSubMenu shiftLeft"> Best-in-class security </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/AdjustablePaymentTerms.png') }}"></span>
<span class="customSubMenu shiftLeft"> Adjustable payment terms </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/MultipleCurrencySupport.png') }}"></span>
<span class="customSubMenu shiftLeft"> Multiple currency support </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/RecurringInvoiceProfiles.png') }}"></span>
<span class="customSubMenu shiftLeft"> Recurring invoice profiles </span>
</div>
</div>
<div class="col-md-2 customMenuOne">
<div class="customMenuDiv">
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/FreeInvoicing.png') }}"></span>
<span class="customSubMenu shiftLeft"> Free invoicing platform </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/EstimatesProForma.png') }}"></span>
<span class="customSubMenu shiftLeft"> Estimates & pro-forma </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/PaymentTracking.png') }}"></span>
<span class="customSubMenu shiftLeft"> Payment tracking </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/MultilingualSupport.png') }}"></span>
<span class="customSubMenu shiftLeft"> Multilingual support </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/SelfHostedAvailable.png') }}"></span>
<span class="customSubMenu shiftLeft"> Self-hosted available </span>
</div>
</div>
<div class="col-md-2 customMenuOne">
<div class="customMenuDiv">
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/LivePDFCreation.png') }}"></span>
<span class="customSubMenu shiftLeft"> Live .pdf creation </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/CloneInvoices.png') }}"></span>
<span class="customSubMenu shiftLeft"> Clone invoices </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/CloudBasedApp.png') }}"></span>
<span class="customSubMenu shiftLeft"> Cloud-based app </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/OpenSource.png') }}"></span>
<span class="customSubMenu shiftLeft"> Open source </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/BeautifulTemplates.png') }}"></span>
<span class="customSubMenu shiftLeft"> Beautiful templates </span>
</div>
</div>
<div class="col-md-4 customMenuOne">
<div class="customMenuDiv">
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/CustomizeInvoices.png') }}"></span>
<span class="customSubMenu shiftLeft"> Cutomize invoices with your company logo </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/PersonalizeInvoiceColor.png') }}"></span>
<span class="customSubMenu shiftLeft"> Personalize invoice color schemes </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/InvoiceClientsViaEmail.png') }}"></span>
<span class="customSubMenu shiftLeft"> Alerts when invoices are viewed or paid </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/IntegrateWithPaymentGateways.png') }}"></span>
<span class="customSubMenu shiftLeft"> Integrate with top payment gateways </span>
</div>
<div class="customMenuDiv" >
<span class="img-wrap shiftLeft" ><img src="{{ asset('images/ManualAutomaticInvoiceNumbers.png') }}"></span>
<span class="customSubMenu shiftLeft"> Manual or automatic invoice numbers </span>
</div>
</div>
</div>
</div>
</section>
<section class="blue">
<div class="container">
<div class="row">
<div class="col-md-5">
<h1>{{ trans('public.home.footer') }}</h1>
<div class="row">
<div class="col-md-7">
<a href="#">
<div class="cta">
<h2 onclick="return getStarted()">{{ trans('public.invoice_now') }} <span>+</span></h2>
</div>
</a>
</div>
</div>
<p>{{ trans('public.no_signup_needed') }}</p>
</div>
<div class="col-md-7">
<img src="{{ asset('images/devices.png') }}">
</div>
</div>
</div>
</section>
@stop

View File

@ -2,11 +2,34 @@
@section('content')
<section class="hero3" data-speed="2" data-type="background">
<style type="text/css">
body {
background-color: #f8f8f8;
color: #1b1a1a;
}
.panel-body {
padding-bottom: 100px;
}
</style>
<div class="container">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-body">
<section data-type="background">
<div class="container">
<div class="caption">
<h1>Terms of Service & Conditions of Use
</h1>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h3>Terms of Service & Conditions of Use</h3>
</div>
</div>
</div>
</section>
@ -28,7 +51,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Definitions</h2>
<h3>Definitions</h3>
<p>Invoiceninja.com users who access invoiceninja.com services are defined as “User Accounts”. User Account clients who use invoiceninja.com services to view and/or pay invoices are defined as “Clients. The wording “data” and “content” are used interchangeably. </p></div>
</div>
</div>
@ -38,7 +61,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Responsibility</h2>
<h3>Responsibility</h3>
<p>User Accounts must ensure the confidentiality of usernames and passwords used to access their account. User Accounts are responsible for all activity occurring under their account including all laws relating to data, privacy, personal information, international copyright and trademark laws.</p>
</div>
</div>
@ -49,7 +72,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Data Ownership</h2>
<h3>Data Ownership</h3>
<p>The User Accounts owns all data generated in their invoiceninja.com account. Invoiceninja.com will not modify or distribute User Account data. Invoiceninja.com will store and access data solely for the purpose of providing services to User Accounts.</p>
<p>User Accounts are responsible for their data. Invoiceninja.com has no responsibility or liability for User Account data or Client experience. User Accounts are responsible for any loss or damage a User Account may cause to their Clients or other people. Although we have no obligation to do so, if deemed legally necessary invoiceninja.com has absolute discretion to remove or edit User Account data without notice or penalty.</p>
<p>Invoiceninja.com does not own User Account data, but we do reserve the right to use User Account data as necessary to operate invoiceninja.com and improve User Account services.</p>
@ -63,7 +86,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Copyright & Trademarks</h2>
<h3>Copyright & Trademarks</h3>
<p>User Accounts are responsible that company logos, graphics, and content uploaded to invoiceninja.com do not infringe on international copyright & trademark law.</p>
</div>
</div>
@ -75,7 +98,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Open Source License</h2>
<h3>Open Source License</h3>
<p>Invoiceninja.com is an open source application and invoiceninja.com source code is governed by international attribution assurances licensing: <a href="https://github.com/hillelcoren/invoice-ninja/blob/master/LICENSE" target="_blank">https://github.com/hillelcoren/invoice-ninja/blob/master/LICENSE</a>.</p>
</div>
</div>
@ -87,7 +110,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>User Account Limited License </h2>
<h3>User Account Limited License </h3>
<p>Invoiceninja.com grants User Accounts & Clients a limited license to access the invoiceninja.com services such as User Account creation and all invoiceninja.om services, and Client services such as viewing invoices, downloading invoices, and printing invoices. This limited license may be revoked if deemed legally necessary without notice or penalty.</p>
</div>
</div>
@ -99,7 +122,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Use of Emailing Services</h2>
<h3>Use of Emailing Services</h3>
<p>Keep it legal and clean. Any User Account emailing invoices data, hyper-links, or other material that is unlawful, libelous, defamatory, pornographic, harassing, invasive, fraudulent or otherwise objectionable will be deactivated.</p>
<p>Content that would constitute criminal offence or create legal liability, violate copyright, trademark, or intellectual property will be deleted or provided to legal authorities.</p>
</div>
@ -112,7 +135,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Security</h2>
<h3>Security</h3>
<p>Invoiceninja.com does not store or obtain credit card or sensitive financial data in any form. Responsibility for Third-Party Material User Account may utilize hyper-linking to third-party web sites. Invoiceninja.com takes no responsibility for third party content.</p>
</div>
</div>
@ -124,7 +147,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Limited Liability</h2>
<h3>Limited Liability</h3>
<p>User Accounts and Clients agree to indemnify, defend, and hold invoiceninja.com, its directors or employees harmless against any and all liability and cost as a result of using invoiceninja.com. User Accounts and Clients shall not assert any claim or allegation of any nature whatsoever against invoiceninja.com, its directors or employees. </p>
<p>Invoiceninja.com shall not be liable for damages of any kind, including but not limited to loss of site use, loss of profits or loss of data, tort or otherwise, arising out of or in any way connected with the use of or inability to use invoiceninja.com.</p>
<p>You shall defend, indemnify and hold harmless invoiceninja.com from any loss, damages, liabilities, expenses, claims and proceedings arising out of your use of invoiceninja.com.</p>
@ -137,7 +160,7 @@
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Availability</h2>
<h3>Availability</h3>
<p>Invoiceninja.com uses third party hosting that strives to ensure maximum uptime. However, invoiceninja.com cannot guarantee uninterrupted access invoiceninja.com. Invoiceninja.com reserves the right to interrupt access to invoiceninja.com for the sake of forming maintenance, updates, and security requirements.</p>
</div>
</div>
@ -147,4 +170,8 @@
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
</div>
</div>
@stop

View File

@ -1,45 +0,0 @@
@extends('public.header')
@section('content')
<section class="hero background hero-testi" data-speed="2" data-type="background">
<div class="container">
<div class="row">
<h1><img src="{{ asset('images/icon-testi.png') }}">{{ trans('public.testimonials.testimonials') }}</h1>
</div>
</div>
</section>
<section class="testi">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 center">
<p class="first">{{ trans('public.testimonials.header') }}</p>
</div>
</div>
</div>
</section>
<section class="testi blue">
<div class="container center">
<div class="row">
<div class="col-md-12">
<blockquote class="twitter-tweet" data-conversation="none" lang="en-gb"><p><a href="https://twitter.com/invoiceninja">@invoiceninja</a> it&#39;s easy to start, I just took a few minutes and I was able to create a really professional invoice! Great user experience!!</p>&mdash; David Regis (@davRegis) <a href="https://twitter.com/davRegis/statuses/444261426577031169">March 13, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" lang="en-gb"><p><a href="https://twitter.com/search?q=%23invoiceNinja&amp;src=hash">#invoiceNinja</a> awesome web app fro creating invoices on the go. <a href="https://t.co/tW20gUbK7x">https://t.co/tW20gUbK7x</a></p>&mdash; Marwan Kathayer (@marwan876) <a href="https://twitter.com/marwan876/statuses/446637280133124097">March 20, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" lang="en-gb"><p>Quite nice free &amp; opensource tool for freelance/small business folk who need to do invoicing : <a href="https://t.co/3eGzB4waR2">https://t.co/3eGzB4waR2</a></p>&mdash; Billy Bofh (@billybofh) <a href="https://twitter.com/billybofh/statuses/446646803463938048">March 20, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" lang="en-gb"><p><a href="https://twitter.com/search?q=%23FREE&amp;src=hash">#FREE</a> is good, yo. Need a slick way to invoice your clients? <a href="https://t.co/NdvqcVa2GA">https://t.co/NdvqcVa2GA</a></p>&mdash; Oroku_Saki 忍 (@Oroku_Saki) <a href="https://twitter.com/Oroku_Saki/statuses/447020836231213056">March 21, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" lang="en-gb"><p><a href="https://twitter.com/DannPetty">@DannPetty</a> invoice ninja. It&#39;s pretty cool.</p>&mdash; Brennan Gleason (@B_Dot_Gleason) <a href="https://twitter.com/B_Dot_Gleason/statuses/461695299585449985">May 1, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" lang="en-gb"><p><a href="https://twitter.com/hillelcoren">@hillelcoren</a> :hey saw <a href="https://twitter.com/search?q=%23invoiceninja&amp;src=hash">#invoiceninja</a> !What a classy app ! Good UI and specially I loved that live PDF editing + open source ! hatsoff !</p>&mdash; Pratik Shah (@pratik07shah) <a href="https://twitter.com/pratik07shah/statuses/444020090338422784">March 13, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" lang="en-gb"><p>Amazed at feedback and implementations of criticisms by <a href="https://twitter.com/invoiceninja">@invoiceninja</a>!</p>&mdash; Grant Hodgeon (@photogrant) <a href="https://twitter.com/photogrant/statuses/473269558434746369">June 2, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>
</div>
</div>
</section>
@stop

View File

@ -11,8 +11,7 @@
table.dataTable { border-radius: 3px; border-collapse: collapse;
/*border-spacing: 0;*/}
table.dataTable thead > tr > th, table.invoice-table thead > tr > th {
background-color: #0b4d78 !important;
/*background-color: #e37329 !important;*/
background-color: {{ $color }} !important;
color:#fff;
}
th:first-child {
@ -68,8 +67,8 @@
max-width: 250px;
}
.pagination>.active>a, .pagination>.active>span, .pagination>.active>a:hover, .pagination>.active>span:hover, .pagination>.active>a:focus, .pagination>.active>span:focus {
background-color: #0b4d78;
border-color: #0b4d78;
background-color: {{ $color }};
border-color: {{ $color }};
}
.pagination>li:first-child>a, .pagination>li:first-child>span {
border-bottom-left-radius: 3px;
@ -81,7 +80,7 @@
</style>
<div class="container" style="min-height:600px;">
<div class="container" id="main-container">
<p>&nbsp;</p>
@ -101,33 +100,11 @@
</div>
<script type="text/javascript">
$(function() {
$('#main-container').height($(window).height() - ($('.navbar').height() + $('footer').height() + 20));
});
</script>
@stop
@section('onReady')
var tableFilter = '';
var searchTimeout = false;
var oTable0 = $('#DataTables_Table_0').dataTable();
function filterTable(val) {
if (val == tableFilter) {
return;
}
tableFilter = val;
oTable0.fnFilter(val);
@if (isset($secEntityType))
oTable1.fnFilter(val);
@endif
}
$('#tableFilter').on('keyup', function(){
if (searchTimeout) {
window.clearTimeout(searchTimeout);
}
searchTimeout = setTimeout(function() {
filterTable($('#tableFilter').val());
}, 1000);
})
@stop

View File

@ -73,6 +73,7 @@
<p class="link">
{{ link_to('forgot_password', 'Forgot your password?') }}
</p>
<!-- if there are login errors, show them here -->
@if ( Session::get('error') )
@ -86,6 +87,27 @@
{{ Former::close() }}
@if (!Utils::isNinja())
<p/>
<center>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=635126583203143";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<div class="fb-follow" data-href="https://www.facebook.com/invoiceninja" data-colorscheme="light" data-layout="button" data-show-faces="false"></div>&nbsp;&nbsp;
<a href="https://twitter.com/invoiceninja" class="twitter-follow-button" data-show-count="false" data-related="hillelcoren" data-size="medium">Follow @invoiceninja</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
<iframe src="https://ghbtns.com/github-btn.html?user=hillelcoren&repo=invoice-ninja&type=star&count=false" frameborder="0" scrolling="0" width="50px" height="20px"></iframe>
</center>
@endif
</div>
@stop

View File

@ -2892,4 +2892,19 @@ table.table thead .sorting_asc_disabled:after { content: '' !important }
table.table thead .sorting_desc_disabled:after { content: '' !important }
/* Prevent modal from shifting page a bit - https://github.com/twbs/bootstrap/issues/9886 */
body.modal-open { overflow:inherit; padding-right:inherit !important; }
body.modal-open { overflow:inherit; padding-right:inherit !important; }
/* bootstrap 3.2.0 fix */
/* https://github.com/twbs/bootstrap/issues/13984 */
.radio input[type="radio"],
.checkbox input[type="checkbox"] {
margin-left: 0;
margin-right: 5px;
height: inherit;
width: inherit;
float: left;
display: inline-block;
position: relative;
margin-top: 3px;
}

View File

@ -31603,6 +31603,16 @@ function GetPdf(invoice, javascript){
eval(javascript);
// add footer
if (invoice.invoice_footer) {
doc.setFontType('normal');
doc.setFontSize('8');
SetPdfColor('Black',doc);
var top = doc.internal.pageSize.height - layout.marginLeft;
var numLines = invoice.invoice_footer.split("\n").length - 1;
doc.text(layout.marginLeft, top - (numLines * 8), invoice.invoice_footer);
}
return doc;
}
@ -31991,6 +32001,13 @@ if (window.ko) {
if (value) $(element).datepicker('update', value);
}
};
ko.bindingHandlers.placeholder = {
init: function (element, valueAccessor, allBindingsAccessor) {
var underlyingObservable = valueAccessor();
ko.applyBindingsToNode(element, { attr: { placeholder: underlyingObservable } } );
}
};
}
function wordWrapText(value, width)
@ -32561,11 +32578,13 @@ function displayInvoiceItems(doc, invoice, layout) {
if (newTop > 770) {
line = 0;
tableTop = layout.accountTop + layout.tablePadding;
y = tableTop;
y = tableTop + (2 * layout.tablePadding);
top = y - layout.tablePadding;
newTop = top + (numLines * layout.tableRowHeight);
doc.addPage();
console.log('== ADD PAGE ==');
}
console.log('Y: %s', y);
var left = layout.marginLeft - layout.tablePadding;
var width = layout.marginRight + layout.tablePadding;
@ -32731,6 +32750,7 @@ function displayInvoiceItems(doc, invoice, layout) {
}
*/
SetPdfColor('Black', doc);
doc.setFontType('normal');
@ -33013,18 +33033,13 @@ function setDocHexDraw(doc, hex) {
return doc.setDrawColor(r, g, b);
}
function openUrl(url, track) {
trackUrl(track ? track : url);
window.open(url, '_blank');
}
function toggleDatePicker(field) {
$('#'+field).datepicker('show');
}
function roundToTwo(num, toString) {
var val = +(Math.round(num + "e+2") + "e-2");
return toString ? val.toFixed(2) : val;
return toString ? val.toFixed(2) : (val || 0);
}
function truncate(str, length) {

File diff suppressed because one or more lines are too long

View File

@ -784,4 +784,19 @@ table.table thead .sorting_asc_disabled:after { content: '' !important }
table.table thead .sorting_desc_disabled:after { content: '' !important }
/* Prevent modal from shifting page a bit - https://github.com/twbs/bootstrap/issues/9886 */
body.modal-open { overflow:inherit; padding-right:inherit !important; }
body.modal-open { overflow:inherit; padding-right:inherit !important; }
/* bootstrap 3.2.0 fix */
/* https://github.com/twbs/bootstrap/issues/13984 */
.radio input[type="radio"],
.checkbox input[type="checkbox"] {
margin-left: 0;
margin-right: 5px;
height: inherit;
width: inherit;
float: left;
display: inline-block;
position: relative;
margin-top: 3px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="9px" height="15px">
<path fill="rgb(255,255,255)" fill-rule="evenodd" d="M 5.79 14.99C 5.79 14.99 3.07 14.99 3.07 14.99 2.88 14.99 2.72 14.84 2.72 14.65 2.72 14.65 2.72 7.49 2.72 7.49 2.72 7.49 0.35 7.49 0.35 7.49 0.15 7.49-0 7.34-0 7.15-0 7.15-0 5.16-0 5.16-0 4.96 0.15 4.81 0.35 4.81 0.35 4.81 2.52 4.81 2.52 4.81 2.52 3.33 2.74 2.7 2.75 2.67 3.39 0.13 5.77 0.01 6.24 0.01 6.32 0.01 6.37 0.01 6.37 0.01 6.37 0.01 8.65 0.01 8.65 0.01 8.85 0.01 9 0.16 9 0.35 9 0.35 9 2.43 9 2.43 9 2.62 8.85 2.78 8.65 2.78 8.65 2.78 7.52 2.78 7.52 2.78 6.5 2.78 6.14 3.38 6.14 3.95 6.14 3.95 6.14 4.81 6.14 4.81 6.14 4.81 8.52 4.81 8.52 4.81 8.71 4.81 8.86 4.96 8.86 5.16 8.86 5.16 8.86 7.15 8.86 7.15 8.86 7.35 8.71 7.5 8.52 7.5 8.52 7.5 6.13 7.5 6.13 7.5 6.13 7.5 6.13 14.65 6.13 14.65 6.13 14.84 5.98 14.99 5.79 14.99ZM 3.42 14.3C 3.42 14.3 5.44 14.3 5.44 14.3 5.44 14.3 5.44 7.15 5.44 7.15 5.44 6.96 5.59 6.81 5.79 6.81 5.79 6.81 8.17 6.81 8.17 6.81 8.17 6.81 8.17 5.5 8.17 5.5 8.17 5.5 5.79 5.5 5.79 5.5 5.6 5.5 5.44 5.35 5.44 5.16 5.44 5.16 5.44 3.95 5.44 3.95 5.44 3.02 6.08 2.08 7.52 2.08 7.52 2.08 8.3 2.08 8.3 2.08 8.3 2.08 8.3 0.7 8.3 0.7 8.3 0.7 6.34 0.7 6.34 0.7 6.31 0.7 6.28 0.7 6.24 0.7 5.7 0.7 3.93 0.86 3.41 2.87 3.4 2.9 3.19 3.57 3.22 5.15 3.22 5.24 3.18 5.33 3.12 5.4 3.05 5.47 2.96 5.5 2.87 5.5 2.87 5.5 0.7 5.5 0.7 5.5 0.7 5.5 0.7 6.8 0.7 6.8 0.7 6.8 3.07 6.8 3.07 6.8 3.26 6.8 3.42 6.96 3.42 7.15 3.42 7.15 3.42 14.3 3.42 14.3Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="19px" height="16px">
<path fill="rgb(255,255,255)" fill-rule="evenodd" d="M 6.14 15.5C 4.5 15.5 2.13 15.19 0.17 13.72 0.01 13.6-0.04 13.39 0.04 13.21 0.11 13.03 0.31 12.92 0.5 12.96 0.51 12.96 0.94 13.03 1.56 13.03 2.54 13.03 3.83 12.86 4.87 12.13 3.96 11.88 2.67 11.22 2.12 9.45 2.07 9.3 2.11 9.14 2.21 9.03 2.25 8.98 2.31 8.94 2.37 8.92 1.57 8.32 0.79 7.3 0.82 5.58 0.82 5.42 0.91 5.28 1.06 5.2 1.18 5.14 1.33 5.15 1.45 5.21 0.8 4.13 0.41 2.58 1.36 0.95 1.43 0.83 1.56 0.75 1.7 0.74 1.84 0.73 1.97 0.79 2.06 0.89 2.09 0.93 5.14 4.46 8.9 4.56 8.88 4.39 8.87 4.23 8.87 4.07 8.87 1.82 10.7-0 12.95-0 13.99-0 14.97 0.38 15.73 1.08 16.19 1.07 16.99 0.94 17.78 0.37 17.91 0.28 18.08 0.27 18.22 0.33 18.36 0.4 18.45 0.54 18.46 0.69 18.47 0.81 18.46 1.29 17.88 2.02 18.06 1.96 18.21 1.89 18.31 1.82 18.46 1.7 18.66 1.7 18.81 1.81 18.97 1.91 19.03 2.1 18.98 2.28 18.95 2.42 18.69 2.98 17.11 4.43 17.13 5.8 16.81 13.34 8.38 15.31 8.34 15.31 7.44 15.5 6.14 15.5ZM 2.13 13.87C 3.54 14.49 5.02 14.64 6.14 14.64 7.34 14.64 8.19 14.47 8.2 14.47 16.72 12.48 16.26 4.36 16.25 4.27 16.24 4.14 16.29 4.01 16.39 3.93 16.78 3.57 17.08 3.28 17.32 3.04 16.94 3.11 16.63 3.13 16.56 3.14 16.38 3.15 16.2 3.04 16.13 2.86 16.06 2.69 16.12 2.49 16.27 2.37 16.57 2.13 16.81 1.92 16.99 1.72 16.42 1.91 15.93 1.94 15.61 1.94 15.61 1.94 15.55 1.94 15.55 1.94 15.43 1.94 15.33 1.9 15.25 1.82 14.64 1.2 13.82 0.86 12.95 0.86 11.17 0.86 9.73 2.3 9.73 4.07 9.73 4.34 9.76 4.61 9.83 4.87 9.86 4.99 9.84 5.13 9.76 5.23 9.68 5.33 9.57 5.4 9.44 5.41 5.88 5.61 2.93 3.02 1.85 1.92 1.04 4.03 2.9 5.65 2.99 5.72 3.12 5.83 3.17 6.01 3.12 6.17 3.08 6.33 2.94 6.45 2.77 6.47 2.41 6.53 2.04 6.45 1.73 6.33 2.08 8.38 4.04 8.83 4.13 8.85 4.31 8.89 4.45 9.04 4.47 9.22 4.49 9.41 4.39 9.58 4.22 9.66 3.95 9.78 3.6 9.84 3.2 9.82 4.05 11.4 5.75 11.42 5.83 11.42 6 11.42 6.15 11.52 6.22 11.67 6.29 11.83 6.26 12.01 6.15 12.14 5.01 13.39 3.42 13.79 2.13 13.87Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because one or more lines are too long

View File

@ -80,6 +80,16 @@ function GetPdf(invoice, javascript){
eval(javascript);
// add footer
if (invoice.invoice_footer) {
doc.setFontType('normal');
doc.setFontSize('8');
SetPdfColor('Black',doc);
var top = doc.internal.pageSize.height - layout.marginLeft;
var numLines = invoice.invoice_footer.split("\n").length - 1;
doc.text(layout.marginLeft, top - (numLines * 8), invoice.invoice_footer);
}
return doc;
}
@ -468,6 +478,13 @@ if (window.ko) {
if (value) $(element).datepicker('update', value);
}
};
ko.bindingHandlers.placeholder = {
init: function (element, valueAccessor, allBindingsAccessor) {
var underlyingObservable = valueAccessor();
ko.applyBindingsToNode(element, { attr: { placeholder: underlyingObservable } } );
}
};
}
function wordWrapText(value, width)
@ -1038,7 +1055,7 @@ function displayInvoiceItems(doc, invoice, layout) {
if (newTop > 770) {
line = 0;
tableTop = layout.accountTop + layout.tablePadding;
y = tableTop;
y = tableTop + (2 * layout.tablePadding);
top = y - layout.tablePadding;
newTop = top + (numLines * layout.tableRowHeight);
doc.addPage();
@ -1208,6 +1225,7 @@ function displayInvoiceItems(doc, invoice, layout) {
}
*/
SetPdfColor('Black', doc);
doc.setFontType('normal');
@ -1490,18 +1508,13 @@ function setDocHexDraw(doc, hex) {
return doc.setDrawColor(r, g, b);
}
function openUrl(url, track) {
trackUrl(track ? track : url);
window.open(url, '_blank');
}
function toggleDatePicker(field) {
$('#'+field).datepicker('show');
}
function roundToTwo(num, toString) {
var val = +(Math.round(num + "e+2") + "e-2");
return toString ? val.toFixed(2) : val;
return toString ? val.toFixed(2) : (val || 0);
}
function truncate(str, length) {