Merge remote-tracking branch 'upstream/master'

Conflicts:
	app/views/clients/edit.blade.php
This commit is contained in:
Razi KAntorp 2014-03-28 13:53:19 +01:00
commit 7acb1a92c1
79 changed files with 3075 additions and 6356 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@
/bootstrap/compiled.php
/bootstrap/environment.php
/vendor
/.env.development.php
/composer.phar
/composer.lock
/.DS_Store

View File

@ -6,7 +6,9 @@
Most online invoicing sites are expensive. They shouldn't be. The aim of this project is to provide a free, open-source alternative. Additionally, the hope is the codebase will serve as a sample site for Laravel as well as other JavaScript technologies.
For updates follow [@invoiceninja](https://twitter.com/invoiceninja) and [@hillelcoren](https://twitter.com/hillelcoren)
The high level instructions for setting up the site are below but there's also a [setup guide](http://hillelcoren.com/invoice-ninja/laravel-ubuntu-virtualbox/). For discussion of the code please use the [Google Group](https://groups.google.com/d/forum/invoiceninja).
For updates follow [@invoiceninja](https://twitter.com/invoiceninja) or join the [Facebook Group](https://www.facebook.com/invoiceninja).
Site design by [kantorp-wegl.in](http://kantorp-wegl.in/)
@ -66,3 +68,4 @@ Configure config/database.php and then initialize the database
* [nnnick/Chart.js](https://github.com/nnnick/Chart.js) - Simple HTML5 Charts using the <canvas> tag
* [josscrowcroft/accounting.js](https://github.com/josscrowcroft/accounting.js) - A lightweight JavaScript library for number, money and currency formatting
* [jashkenas/underscore](https://github.com/jashkenas/underscore) - JavaScript's utility _ belt
* [caouecs/Laravel4-long](https://github.com/caouecs/Laravel4-lang) - List of languages for Laravel4

View File

@ -121,8 +121,7 @@ return array(
'Barryvdh\Debugbar\ServiceProvider',
'Chumper\Datatable\DatatableServiceProvider',
'Intervention\Image\ImageServiceProvider',
'Webpatser\Countries\CountriesServiceProvider',
'Rocketeer\RocketeerServiceProvider',
'Webpatser\Countries\CountriesServiceProvider'
),
/*

View File

@ -48,7 +48,7 @@
////////////////////////////////////////////////////////////////////
// Where Former should look for translations
'translate_from' => 'validation.attributes',
'translate_from' => 'texts',
// An array of attributes to automatically translate
'translatable' => array(

View File

@ -87,6 +87,7 @@ class AccountController extends \BaseController {
'dateFormats' => DateFormat::remember(DEFAULT_QUERY_CACHE)->get(),
'datetimeFormats' => DatetimeFormat::remember(DEFAULT_QUERY_CACHE)->get(),
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
'languages' => Language::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
];
return View::make('accounts.details', $data);
@ -442,7 +443,7 @@ class AccountController extends \BaseController {
foreach ($fields as $field => $details)
{
if (!in_array($field, ['testMode', 'developerMode', 'headerImageUrl', 'solutionType', 'landingPage']))
if (!in_array($field, ['testMode', 'developerMode', 'headerImageUrl', 'solutionType', 'landingPage', 'brandName']))
{
$rules[$gateway->id.'_'.$field] = 'required';
}
@ -460,7 +461,7 @@ class AccountController extends \BaseController {
else
{
$account = Account::findOrFail(Auth::user()->account_id);
$account->account_gateways()->forceDelete();
$account->account_gateways()->delete();
if ($gatewayId)
{
@ -499,7 +500,7 @@ class AccountController extends \BaseController {
}
else
{
$account = Account::findOrFail(Auth::user()->account_id);
$account = Auth::user()->account;
$account->name = trim(Input::get('name'));
$account->work_email = trim(Input::get('work_email'));
$account->work_phone = trim(Input::get('work_phone'));
@ -514,7 +515,8 @@ class AccountController extends \BaseController {
$account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null;
$account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null;
$account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null;
$account->currency_id = Input::get('currency_id') ? Input::get('currency_id') : 1;
$account->currency_id = Input::get('currency_id') ? Input::get('currency_id') : 1; // US Dollar
$account->language_id = Input::get('language_id') ? Input::get('language_id') : 1; // English
$account->save();
$user = Auth::user();
@ -530,7 +532,7 @@ class AccountController extends \BaseController {
{
$path = Input::file('logo')->getRealPath();
File::delete('logo/' . $account->account_key . '.jpg');
Image::make($path)->resize(120, 80, true, false)->save('logo/' . $account->account_key . '.jpg');
Image::make($path)->resize(null, 120, true, false)->save('logo/' . $account->account_key . '.jpg');
}
Event::fire('user.refresh');
@ -540,6 +542,14 @@ class AccountController extends \BaseController {
}
}
public function removeLogo() {
File::delete('logo/' . Auth::user()->account->account_key . '.jpg');
Session::flash('message', 'Successfully removed logo');
return Redirect::to('company/details');
}
public function checkEmail()
{
$email = User::withTrashed()->where('email', '=', Input::get('email'))->where('id', '<>', Auth::user()->id)->first();

View File

@ -23,7 +23,7 @@ class ClientController extends \BaseController {
return View::make('list', array(
'entityType'=>ENTITY_CLIENT,
'title' => '- Clients',
'columns'=>['checkbox', 'Client', 'Contact', 'Email', 'Date Created', 'Last Login', 'Balance', 'Action']
'columns'=>Utils::trans(['checkbox', 'client', 'contact', 'email', 'date_created', 'last_login', 'balance', 'action'])
));
}
@ -43,17 +43,17 @@ class ClientController extends \BaseController {
{
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
Select <span class="caret"></span>
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('clients/'.$model->public_id.'/edit') . '">Edit Client</a></li>
<li><a href="' . URL::to('clients/'.$model->public_id.'/edit') . '">'.trans('texts.edit_client').'</a></li>
<li class="divider"></li>
<li><a href="' . URL::to('invoices/create/'.$model->public_id) . '">New Invoice</a></li>
<li><a href="' . URL::to('payments/create/'.$model->public_id) . '">New Payment</a></li>
<li><a href="' . URL::to('credits/create/'.$model->public_id) . '">New Credit</a></li>
<li><a href="' . URL::to('invoices/create/'.$model->public_id) . '">'.trans('texts.new_invoice').'</a></li>
<li><a href="' . URL::to('payments/create/'.$model->public_id) . '">'.trans('texts.new_payment').'</a></li>
<li><a href="' . URL::to('credits/create/'.$model->public_id) . '">'.trans('texts.new_credit').'</a></li>
<li class="divider"></li>
<li><a href="javascript:archiveEntity(' . $model->public_id. ')">Archive Client</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id. ')">Delete Client</a></li>
<li><a href="javascript:archiveEntity(' . $model->public_id. ')">'.trans('texts.archive_client').'</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id. ')">'.trans('texts.delete_client').'</a></li>
</ul>
</div>';
})

View File

@ -23,7 +23,7 @@ class CreditController extends \BaseController {
return View::make('list', array(
'entityType'=>ENTITY_CREDIT,
'title' => '- Credits',
'columns'=>['checkbox', 'Client', 'Credit Amount', 'Credit Balance', 'Credit Date', 'Private Notes', 'Action']
'columns'=>Utils::trans(['checkbox', 'client', 'credit_amount', 'credit_balance', 'credit_date', 'private_notes', 'action'])
));
}
@ -47,11 +47,11 @@ class CreditController extends \BaseController {
{
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
Select <span class="caret"></span>
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="javascript:archiveEntity(' . $model->public_id. ')">Archive Credit</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id. ')">Delete Credit</a></li>
<li><a href="javascript:archiveEntity(' . $model->public_id. ')">'.trans('texts.archive_credit').'</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id. ')">'.trans('texts.delete_credit').'</a></li>
</ul>
</div>';
})

View File

@ -43,11 +43,11 @@ class DashboardController extends \BaseController {
->orderBy('due_date', 'asc')->take(6)->get();
$data = [
'totalIncome' => Utils::formatMoney($totalIncome->value, Session::get(SESSION_CURRENCY)),
'billedClients' => $metrics->billed_clients,
'invoicesSent' => $metrics->invoices_sent,
'activeClients' => $metrics->active_clients,
'invoiceAvg' => Utils::formatMoney($metrics->invoice_avg, Session::get(SESSION_CURRENCY)),
'totalIncome' => Utils::formatMoney($totalIncome ? $totalIncome->value : 0, Session::get(SESSION_CURRENCY)),
'billedClients' => $metrics ? $metrics->billed_clients : 0,
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
'activeClients' => $metrics ? $metrics->active_clients : 0,
'invoiceAvg' => Utils::formatMoney(($metrics ? $metrics->invoice_avg : 0), Session::get(SESSION_CURRENCY)),
'activities' => $activities,
'pastDue' => $pastDue,
'upcoming' => $upcoming

View File

@ -16,22 +16,22 @@ class HomeController extends BaseController {
public function showWelcome()
{
return View::make('splash');
return View::make('public.splash');
}
public function showAboutUs()
{
return View::make('about_us');
return View::make('public.about_us');
}
public function showContactUs()
{
return View::make('contact_us');
return View::make('public.contact_us');
}
public function showTerms()
{
return View::make('terms');
return View::make('public.terms');
}
public function doContactUs()
@ -46,7 +46,7 @@ class HomeController extends BaseController {
'text' => $message
];
$this->mailer->sendTo('contact@invoiceninja.com', 'contact@invoiceninja.com', 'Invoice Ninja Feedback', 'contact', $data);
$this->mailer->sendTo(CONTACT_EMAIL, CONTACT_EMAIL, CONTACT_NAME, 'Invoice Ninja Feedback', 'contact', $data);
Session::flash('message', 'Successfully sent message');
return Redirect::to('/contact');

View File

@ -27,13 +27,13 @@ class InvoiceController extends \BaseController {
$data = [
'title' => '- Invoices',
'entityType'=>ENTITY_INVOICE,
'columns'=>['checkbox', 'Invoice Number', 'Client', 'Invoice Date', 'Invoice Total', 'Balance Due', 'Due Date', 'Status', 'Action']
'columns'=>Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action'])
];
if (Invoice::scope()->where('is_recurring', '=', true)->count() > 0)
{
$data['secEntityType'] = ENTITY_RECURRING_INVOICE;
$data['secColumns'] = ['checkbox', 'Frequency', 'Client', 'Start Date', 'End Date', 'Invoice Total', 'Action'];
$data['secColumns'] = Utils::trans(['checkbox', 'frequency', 'client', 'start_date', 'end_date', 'invoice_total', 'action']);
}
return View::make('list', $data);
@ -63,14 +63,14 @@ class InvoiceController extends \BaseController {
{
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
Select <span class="caret"></span>
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('invoices/'.$model->public_id.'/edit') . '">Edit Invoice</a></li>
<li><a href="' . URL::to('payments/create/' . $model->client_public_id . '/' . $model->public_id ) . '">Enter Payment</a></li>
<li><a href="' . URL::to('invoices/'.$model->public_id.'/edit') . '">'.trans('texts.edit_invoice').'</a></li>
<li><a href="' . URL::to('payments/create/' . $model->client_public_id . '/' . $model->public_id ) . '">'.trans('texts.enter_payment').'</a></li>
<li class="divider"></li>
<li><a href="javascript:archiveEntity(' . $model->public_id . ')">Archive Invoice</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id . ')">Delete Invoice</a></li>
<li><a href="javascript:archiveEntity(' . $model->public_id . ')">'.trans('texts.archive_invoice').'</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id . ')">'.trans('texts.delete_invoice').'</a></li>
</ul>
</div>';
})
@ -89,7 +89,7 @@ class InvoiceController extends \BaseController {
$table->addColumn('frequency', function($model) { return link_to('invoices/' . $model->public_id, $model->frequency); });
if (!$clientPublicId) {
$table->addColumn('client', function($model) { return link_to('clients/' . $model->client_public_id, Utils::getClientDisplayName($model)); });
$table->addColumn('client_name', function($model) { return link_to('clients/' . $model->client_public_id, Utils::getClientDisplayName($model)); });
}
return $table->addColumn('start_date', function($model) { return Utils::fromSqlDate($model->start_date); })
@ -99,13 +99,13 @@ class InvoiceController extends \BaseController {
{
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
Select <span class="caret"></span>
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="' . URL::to('invoices/'.$model->public_id.'/edit') . '">Edit Invoice</a></li>
<li><a href="' . URL::to('invoices/'.$model->public_id.'/edit') . '">'.trans('texts.edit_invoice').'</a></li>
<li class="divider"></li>
<li><a href="javascript:archiveEntity(' . $model->public_id . ')">Archive Invoice</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id . ')">Delete Invoice</a></li>
<li><a href="javascript:archiveEntity(' . $model->public_id . ')">'.trans('texts.archive_invoice').'</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id . ')">'.trans('texts.delete_invoice').'</a></li>
</ul>
</div>';
})
@ -143,7 +143,8 @@ class InvoiceController extends \BaseController {
$data = array(
'showBreadcrumbs' => false,
'invoice' => $invoice->hidePrivateFields(),
'invitation' => $invitation
'invitation' => $invitation,
'invoiceLabels' => $client->account->getInvoiceLabels(),
);
return View::make('invoices.view', $data);
@ -206,6 +207,12 @@ class InvoiceController extends \BaseController {
public static function getViewModel()
{
// Temporary fix to let users know to re-upload their logos for higher res
if (Auth::user()->account->getLogoHeight() == 80)
{
Session::flash('warning', "We've increased the logo resolution in the PDF. Please re-upload your logo to take advantage of it.");
}
return [
'account' => Auth::user()->account,
'products' => Product::scope()->orderBy('id')->get(array('product_key','notes','cost','qty')),
@ -217,6 +224,7 @@ class InvoiceController extends \BaseController {
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'frequencies' => array(
1 => 'Weekly',
2 => 'Two weeks',
@ -329,9 +337,17 @@ class InvoiceController extends \BaseController {
else
{
Session::flash('message', 'Successfully saved invoice'.$message);
if (Auth::user()->registered)
{
Session::flash('error', 'Please confirm your email address');
}
else
{
Session::flash('error', 'Please sign up to email an invoice');
}
}
}
else
{
Session::flash('message', 'Successfully saved invoice'.$message);

View File

@ -18,7 +18,7 @@ class PaymentController extends \BaseController
return View::make('list', array(
'entityType'=>ENTITY_PAYMENT,
'title' => '- Payments',
'columns'=>['checkbox', 'Invoice', 'Client', 'Transaction Reference', 'Method', 'Payment Amount', 'Payment Date', 'Action']
'columns'=>Utils::trans(['checkbox', 'invoice', 'client', 'transaction_reference', 'method', 'payment_amount', 'payment_date', 'action'])
));
}
@ -46,11 +46,11 @@ class PaymentController extends \BaseController
{
return '<div class="btn-group tr-action" style="visibility:hidden;">
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
Select <span class="caret"></span>
'.trans('texts.select').' <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="javascript:archiveEntity(' . $model->public_id. ')">Archive Payment</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id. ')">Delete Payment</a></li>
<li><a href="javascript:archiveEntity(' . $model->public_id. ')">'.trans('texts.archive_payment').'</a></li>
<li><a href="javascript:deleteEntity(' . $model->public_id. ')">'.trans('texts.delete_payment').'</a></li>
</ul>
</div>';
})
@ -116,11 +116,20 @@ class PaymentController extends \BaseController
$gateway->$function($val);
}
/*
if (!Utils::isProd())
{
$gateway->setTestMode(true);
}
*/
return $gateway;
}
private function getPaymentDetails($invoice, $input = null)
{
$key = $invoice->invoice_number . '_details';
if ($input)
{
$data = [
@ -142,11 +151,15 @@ class PaymentController extends \BaseController
'shippingPostcode' => $input['postal_code'],
];
Session::put($invoice->invoice_number . '_details', $data);
Session::put($key, $data);
}
else if (Session::get($key))
{
$data = Session::get($key);
}
else
{
$data = Session::get($invoice->invoice_number . '_details');
$data = [];
}
$card = new CreditCard($data);
@ -162,6 +175,22 @@ class PaymentController extends \BaseController
public function show_payment($invitationKey)
{
// For PayPal Express we redirect straight to their site
$invitation = Invitation::with('invoice.client.account')->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);
}
}
$invitation = Invitation::with('contact', 'invoice.client')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
@ -177,7 +206,7 @@ class PaymentController extends \BaseController
return View::make('payments.payment', $data);
}
public function do_payment($invitationKey)
public function do_payment($invitationKey, $onSite = true)
{
$rules = array(
'first_name' => 'required',
@ -192,6 +221,8 @@ class PaymentController extends \BaseController
'postal_code' => 'required',
);
if ($onSite)
{
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails())
@ -199,13 +230,15 @@ class PaymentController extends \BaseController
return Redirect::to('payment/' . $invitationKey)
->withErrors($validator);
}
else
{
}
$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];
$gateway = self::createGateway($accountGateway);
if ($onSite)
{
$client = $invoice->client;
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
@ -213,6 +246,7 @@ class PaymentController extends \BaseController
$client->state = trim(Input::get('state'));
$client->postal_code = trim(Input::get('postal_code'));
$client->save();
}
try
{
@ -259,14 +293,13 @@ class PaymentController extends \BaseController
->withInput();
}
}
}
private function createPayment($invitation, $ref, $payerId = null)
{
$invoice = $invitation->invoice;
$accountGateway = $invoice->client->account->account_gateways[0];
$payment = Payment::createNew();
$payment = Payment::createNew($invitation);
$payment->invitation_id = $invitation->id;
$payment->account_gateway_id = $accountGateway->id;
$payment->invoice_id = $invoice->id;

View File

@ -20,6 +20,17 @@ class UserController extends BaseController {
return Redirect::to(Input::get('path'));
}
public function forcePDFJS()
{
$user = Auth::user();
$user->force_pdfjs = true;
$user->save();
Session::flash('message', 'Successfully updated PDF settings');
return Redirect::to('/invoices/create');
}
/**
* Displays the form for account creation
*
@ -110,18 +121,18 @@ class UserController extends BaseController {
// with the second parameter as true.
// logAttempt will check if the 'email' perhaps is the username.
// Get the value from the config file instead of changing the controller
if ( Confide::logAttempt( $input, false ) )
if ( Input::get( 'login_email' ) && Confide::logAttempt( $input, false ) )
{
Event::fire('user.login');
// Redirect the user to the URL they were trying to access before
// caught by the authentication filter IE Redirect::guest('user/login').
// Otherwise fallback to '/'
// Fix pull #145
return Redirect::intended('/clients'); // change it to '/admin', '/dashboard' or something
return Redirect::intended('/dashboard'); // change it to '/admin', '/dashboard' or something
}
else
{
$user = new User;
//$user = new User;
// Check if there was too many login attempts
if( Confide::isThrottled( $input ) )
@ -248,12 +259,15 @@ class UserController extends BaseController {
*
*/
public function logout()
{
if (Auth::check())
{
if (!Auth::user()->registered)
{
$account = Auth::user()->account;
$account->forceDelete();
}
}
Confide::logout();

View File

@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddLanguageSupport extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('languages', function($table)
{
$table->increments('id');
$table->string('name');
$table->string('locale');
});
DB::table('languages')->insert(['name' => 'English', 'locale' => 'en']);
DB::table('languages')->insert(['name' => 'Italian', 'locale' => 'it']);
DB::table('languages')->insert(['name' => 'German', 'locale' => 'de']);
DB::table('languages')->insert(['name' => 'French', 'locale' => 'fr']);
DB::table('languages')->insert(['name' => 'Brazilian Portuguese', 'locale' => 'pt_BR']);
Schema::table('accounts', function($table)
{
$table->unsignedInteger('language_id')->default(1);
});
DB::table('accounts')->update(['language_id' => 1]);
Schema::table('accounts', function($table)
{
$table->foreign('language_id')->references('id')->on('languages');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function($table)
{
$table->dropForeign('accounts_language_id_foreign');
$table->dropColumn('language_id');
});
Schema::drop('languages');
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class EnableForcingJspdf extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function($table)
{
$table->boolean('force_pdfjs')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function($table)
{
$table->dropColumn('force_pdfjs');
});
}
}

View File

@ -5,21 +5,7 @@ class UserTableSeeder extends Seeder
public function run()
{
//DB::table('users')->delete();
/*
$account = Account::create(array(
'name' => 'Acme Inc',
));
$user = User::create(array(
'account_id' => $account->id,
'first_name' => 'Hillel',
'last_name' => 'Coren',
'email' => 'hillelcoren@gmail.com',
'password' => Hash::make('1234'),
));
*/
}
}

View File

@ -20,6 +20,11 @@ App::before(function($request)
return Redirect::secure(Request::getRequestUri());
}
}
if (Auth::check())
{
App::setLocale(Auth::user()->getLocale());
}
});

54
app/lang/de/fields.php Normal file
View File

@ -0,0 +1,54 @@
<?php
return array(
// client
'organization' => 'Organisation',
'name' => 'Name',
'website' => 'Webseite',
'work_phone' => 'Telefon',
'address' => 'Adresse',
'address1' => 'Straße',
'address2' => 'Adresszusatz',
'city' => 'Stadt',
'state' => 'Bundesland',
'postal_code' => 'Postleitzahl',
'country_id' => 'Land',
'contacts' => 'Kontakte',
'first_name' => 'Vorname',
'last_name' => 'Nachname',
'phone' => 'Telefon',
'email' => 'Email',
'additional_info' => 'Zusätzliche Info',
'payment_terms' => 'Zahlungsbedingungen',
'currency_id' => 'Währung',
'size_id' => 'Größe',
'industry_id' => 'Kategorie',
'private_notes' => 'Notizen',
// invoice
'invoice' => 'Rechnung',
'client' => 'Kunde',
'invoice_date' => 'Rechnungsdatum',
'due_date' => 'Fällig am',
'invoice_number' => 'Rechungsnummer',
'invoice_number_short' => 'Rechnung #',
'po_number' => 'Bestell Nummer',
'po_number_short' => 'BN #',
'frequency_id' => 'Wie oft',
'dicount' => 'Rabatt',
'taxes' => 'Steuern',
'tax' => 'Steuer',
'item' => 'Artikel',
'description' => 'Beschreibung',
'unit_cost' => 'Kosten pro Einheit',
'quantity' => 'Menge',
'line_total' => 'Summe',
'subtotal' => 'Zwischensumme',
'paid_to_date' => 'Zahlungsdatum',
'balance_due' => 'Rechnungsbetrag',
'invoice_design_id' => 'Design',
'terms' => 'Bedingungen',
'your_invoice' => 'Ihre Rechnung',
);

View File

@ -0,0 +1,20 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; zurück',
'next' => 'weiter &raquo;',
);

24
app/lang/de/reminders.php Normal file
View File

@ -0,0 +1,24 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Password Reminder Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
"password" => "Passwörter müssen 6 Zeichen lang sein und korrekt bestätigt werden.",
"user" => "Wir konnten leider keinen Nutzer mit dieser E-Mail Adresse finden.",
"token" => "Der Passwort-Wiederherstellungs-Schlüssel ist ungültig.",
"sent" => "Passworterinnerung wurde gesendet!",
);

54
app/lang/de/texts.php Normal file
View File

@ -0,0 +1,54 @@
<?php
return array(
// client
'organization' => 'Organisation',
'name' => 'Name',
'website' => 'Webseite',
'work_phone' => 'Telefon',
'address' => 'Adresse',
'address1' => 'Straße',
'address2' => 'Adresszusatz',
'city' => 'Stadt',
'state' => 'Bundesland',
'postal_code' => 'Postleitzahl',
'country_id' => 'Land',
'contacts' => 'Kontakte',
'first_name' => 'Vorname',
'last_name' => 'Nachname',
'phone' => 'Telefon',
'email' => 'Email',
'additional_info' => 'Zusätzliche Info',
'payment_terms' => 'Payment Terms',
'currency_id' => 'Währung',
'size_id' => 'Größe',
'industry_id' => 'Kategorie',
'private_notes' => 'Notizen',
// invoice
'invoice' => 'Rechnung',
'client' => 'Kunde',
'invoice_date' => 'Rechnungsdatum',
'due_date' => 'Fällig am',
'invoice_number' => 'Rechungsnummer',
'invoice_number_short' => 'Rechnung #',
'po_number' => 'Bestell Nummer',
'po_number_short' => 'BN #',
'frequency_id' => 'Wie oft',
'dicount' => 'Rabatt',
'taxes' => 'Steuern',
'tax' => 'Steuer',
'item' => 'Artikel',
'description' => 'Beschreibung',
'unit_cost' => 'Kosten pro Einheit',
'quantity' => 'Menge',
'line_total' => 'Summe',
'subtotal' => 'Zwischensumme',
'paid_to_date' => 'Zahlungsdatum',
'balance_due' => 'Rechnungsbetrag',
'invoice_design_id' => 'Design',
'terms' => 'Bedingungen',
'your_invoice' => 'Ihre Rechnung',
);

104
app/lang/de/validation.php Normal file
View File

@ -0,0 +1,104 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| such as the size rules. Feel free to tweak each of these messages.
|
*/
"accepted" => ":attribute muss akzeptiert werden.",
"active_url" => ":attribute ist keine gültige Internet-Adresse.",
"after" => ":attribute muss ein Datum nach dem :date sein.",
"alpha" => ":attribute darf nur aus Buchstaben bestehen.",
"alpha_dash" => ":attribute darf nur aus Buchstaben, Zahlen, Binde- und Unterstrichen bestehen. Umlaute (ä, ö, ü) und Eszett (ß) sind nicht erlaubt.",
"alpha_num" => ":attribute darf nur aus Buchstaben und Zahlen bestehen.",
"array" => ":attribute muss ein Array sein.",
"before" => ":attribute muss ein Datum vor dem :date sein.",
"between" => array(
"numeric" => ":attribute muss zwischen :min & :max liegen.",
"file" => ":attribute muss zwischen :min & :max Kilobytes groß sein.",
"string" => ":attribute muss zwischen :min & :max Zeichen lang sein.",
"array" => ":attribute muss zwischen :min & :max Elemente haben."
),
"confirmed" => ":attribute stimmt nicht mit der Bestätigung überein.",
"date" => ":attribute muss ein gültiges Datum sein.",
"date_format" => ":attribute entspricht nicht dem gültigen Format für :format.",
"different" => ":attribute und :other müssen sich unterscheiden.",
"digits" => ":attribute muss :digits Stellen haben.",
"digits_between" => ":attribute muss zwischen :min und :max Stellen haben.",
"email" => ":attribute Format ist ungültig.",
"exists" => "Der gewählte Wert für :attribute ist ungültig.",
"image" => ":attribute muss ein Bild sein.",
"in" => "Der gewählte Wert für :attribute ist ungültig.",
"integer" => ":attribute muss eine ganze Zahl sein.",
"ip" => ":attribute muss eine gültige IP-Adresse sein.",
"max" => array(
"numeric" => ":attribute darf maximal :max sein.",
"file" => ":attribute darf maximal :max Kilobytes groß sein.",
"string" => ":attribute darf maximal :max Zeichen haben.",
"array" => ":attribute darf nicht mehr als :max Elemente haben."
),
"mimes" => ":attribute muss den Dateityp :values haben.",
"min" => array(
"numeric" => ":attribute muss mindestens :min sein.",
"file" => ":attribute muss mindestens :min Kilobytes groß sein.",
"string" => ":attribute muss mindestens :min Zeichen lang sein.",
"array" => ":attribute muss mindestens :min Elemente haben."
),
"not_in" => "Der gewählte Wert für :attribute ist ungültig.",
"numeric" => ":attribute muss eine Zahl sein.",
"regex" => ":attribute Format ist ungültig.",
"required" => ":attribute muss ausgefüllt sein.",
"required_if" => ":attribute muss ausgefüllt sein wenn :other :value ist.",
"required_with" => ":attribute muss angegeben werden wenn :values ausgefüllt wurde.",
"required_with_all" => "The :attribute field is required when :values is present.",
"required_without" => ":attribute muss angegeben werden wenn :values nicht ausgefüllt wurde.",
"required_without_all" => ":attribute muss angegeben werden wenn keines der Felder :values ausgefüllt wurde.",
"same" => ":attribute und :other müssen übereinstimmen.",
"size" => array(
"numeric" => ":attribute muss gleich :size sein.",
"file" => ":attribute muss :size Kilobyte groß sein.",
"string" => ":attribute muss :size Zeichen lang sein.",
"array" => ":attribute muss genau :size Elemente haben."
),
"unique" => ":attribute ist schon vergeben.",
"url" => "Das Format von :attribute ist ungültig.",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => array(
'attribute-name' => array(
'rule-name' => 'custom-message',
),
),
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => array(),
);

154
app/lang/en/texts.php Normal file
View File

@ -0,0 +1,154 @@
<?php
return array(
// client
'organization' => 'Organization',
'name' => 'Name',
'website' => 'Website',
'work_phone' => 'Phone',
'address' => 'Address',
'address1' => 'Street',
'address2' => 'Apt/Suite',
'city' => 'City',
'state' => 'State/Province',
'postal_code' => 'Postal Code',
'country_id' => 'Country',
'contacts' => 'Contacts',
'first_name' => 'First Name',
'last_name' => 'Last Name',
'phone' => 'Phone',
'email' => 'Email',
'additional_info' => 'Additional Info',
'payment_terms' => 'Payment Terms',
'currency_id' => 'Currency',
'size_id' => 'Size',
'industry_id' => 'Industry',
'private_notes' => 'Private Notes',
// invoice
'invoice' => 'Invoice',
'client' => 'Client',
'invoice_date' => 'Invoice Date',
'due_date' => 'Due Date',
'invoice_number' => 'Invoice Number',
'invoice_number_short' => 'Invoice #',
'po_number' => 'PO Number',
'po_number_short' => 'PO #',
'frequency_id' => 'How often',
'discount' => 'Discount',
'taxes' => 'Taxes',
'tax' => 'Tax',
'item' => 'Item',
'description' => 'Description',
'unit_cost' => 'Unit Cost',
'quantity' => 'Quantity',
'line_total' => 'Line Total',
'subtotal' => 'Subtotal',
'paid_to_date' => 'Paid to Date',
'balance_due' => 'Balance Due',
'invoice_design_id' => 'Design',
'terms' => 'Terms',
'your_invoice' => 'Your Invoice',
'remove_contact' => 'Remove contact',
'add_contact' => 'Add contact',
'create_new_client' => 'Create new client',
'edit_client_details' => 'Edit client details',
'enable' => 'Enable',
'learn_more' => 'Learn more',
'manage_rates' => 'Manage rates',
'note_to_client' => 'Note to client',
'invoice_terms' => 'Invoice terms',
'save_as_default_terms' => 'Save as default terms',
'download_pdf' => 'Download PDF',
'save_invoice' => 'Save Invoice',
'clone_invoice' => 'Clone Invoice',
'archive_invoice' => 'Archive Invoice',
'delete_invoice' => 'Delete Invoice',
'email_invoice' => 'Email Invoice',
'enter_payment' => 'Enter Payment',
'tax_rates' => 'Tax Rates',
'rate' => 'Rate',
'settings' => 'Settings',
'enable_invoice_tax' => 'Enable specifying an <b>invoice tax</b>',
'enable_line_item_tax' => 'Enable specifying <b>line item taxes</b>',
// navigation
'dashboard' => 'Dashboard',
'clients' => 'Clients',
'invoices' => 'Invoices',
'payments' => 'Payments',
'credits' => 'Credits',
'history' => 'History',
'search' => 'Search',
'sign_up' => 'Sign Up',
'guest' => 'Guest',
'company_details' => 'Company Details',
'online_payments' => 'Online Payments',
'notifications' => 'Notifications',
'import_export' => 'Import/Export',
'done' => 'Done',
'cancel' => 'Cancel',
'provide_email' => 'Please provide a valid email address',
'powered_by' => 'Powered by',
'no_items' => 'No items',
// recurring invoices
'recurring_invoices' => 'Recurring Invoices',
'recurring_help' => '<p>Automatically send clients the same invoices weekly, bi-monthly, monthly, quarterly or annually. </p>
<p>Use :MONTH, :QUARTER or :YEAR for dynamic dates. Basic math works as well, for example :MONTH-1.</p>
<p>Examples of dynamic invoice variables:</p>
<ul>
<li>"Gym membership for the month of :MONTH" => "Gym membership for the month of July"</li>
<li>":YEAR+1 yearly subscription" => "2015 Yearly Subscription"</li>
<li>"Retainer payment for :QUARTER+1" => "Retainer payment for Q2"</li>
</ul>',
// dashboard
'in_total_revenue' => 'in total revenue',
'billed_client' => 'billed client',
'billed_clients' => 'billed clients',
'active_client' => 'active client',
'active_clients' => 'active clients',
'invoices_past_due' => 'Invoices Past Due',
'upcoming_invoices' => 'Upcoming invoices',
'average_invoice' => 'Average invoice',
// list pages
'archive' => 'Archive',
'delete' => 'Delete',
'archive_client' => 'Archive client',
'delete_client' => 'Delete client',
'archive_payment' => 'Archive payment',
'delete_payment' => 'Delete payment',
'archive_credit' => 'Archive credit',
'delete_credit' => 'Delete credit',
'show_archived_deleted' => 'Show archived/deleted',
'filter' => 'Filter',
'new_client' => 'New Client',
'new_invoice' => 'New Invoice',
'new_payment' => 'New Payment',
'new_credit' => 'New Credit',
'contact' => 'Contact',
'date_created' => 'Date Created',
'last_login' => 'Last Login',
'balance' => 'Balance',
'action' => 'Action',
'status' => 'Status',
'invoice_total' => 'Invoice Total',
'frequency' => 'Frequency',
'start_date' => 'Start Date',
'end_date' => 'End Date',
'transaction_reference' => 'Transaction Reference',
'method' => 'Method',
'payment_amount' => 'Payment Amount',
'payment_date' => 'Payment Date',
'credit_amount' => 'Credit Amount',
'credit_balance' => 'Credit Balance',
'credit_date' => 'Credit Date',
'empty_table' => 'No data available in table',
'select' => 'Select',
'edit_client' => 'Edit Client',
'edit_invoice' => 'Edit Invoice',
);

52
app/lang/es/fields.php Normal file
View File

@ -0,0 +1,52 @@
<?php
return array(
// client
'organization' => 'Organización',
'name' => 'Nombre',
'website' => 'Página Web',
'work_phone' => 'Teléfono',
'address' => 'Dirección',
'address1' => 'Calle',
'address2' => 'Bloq/Pta',
'city' => 'Ciudad',
'state' => 'Región/Provincia',
'postal_code' => 'Código Postal',
'country_id' => 'País',
'contacts' => 'Contactos',
'first_name' => 'Nombre',
'last_name' => 'Apellidos',
'phone' => 'Teléfono',
'email' => 'Email',
'additional_info' => 'Información extra',
'payment_terms' => 'Términos de pago',
'currency_id' => 'Divisa',
'size_id' => 'Tamaño',
'industry_id' => 'Industria',
'private_notes' => 'Notas Privadas',
// invoice
'invoice' => 'Factura',
'client' => 'Clienta',
'invoice_date' => 'Fecha de factura',
'due_date' => 'Fecha de pago',
'invoice_number' => 'Número de Factura',
'invoice_number_short' => 'Nº de Factura',
'po_number' => 'Apartado de correos',
'po_number_short' => 'Apdo.',
'frequency_id' => 'Fracuencia',
'discount' => 'Descuento',
'taxes' => 'Impuestos',
'tax' => 'Impuesto',
'item' => 'Elemento',
'description' => 'Descripción',
'unit_cost' => 'Coste unitario',
'quantity' => 'Cantidad',
'line_total' => 'Total línea',
'subtotal' => 'Subtotal',
'paid_to_date' => 'Pagado',
'balance_due' => 'Pendiente',
'invoice_design_id' => 'Diseño',
'terms' => 'Términos',
'your_invoice' => 'Tu factura',
);

0
app/lang/es/messages.php Normal file
View File

20
app/lang/es/pagination.php Executable file
View File

@ -0,0 +1,20 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Anterior',
'next' => 'Siguiente &raquo;',
);

24
app/lang/es/reminders.php Executable file
View File

@ -0,0 +1,24 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Password Reminder Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
"password" => "Las contraseñas deben contener al menos 6 caracteres y coincidir.",
"user" => "No podemos encontrar a un usuario con ese correo electrónico.",
"token" => "Este token de recuperación de contraseña es inválido.",
"sent" => "¡Recordatorio de contraseña enviado!",
);

107
app/lang/es/validation.php Executable file
View File

@ -0,0 +1,107 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| such as the size rules. Feel free to tweak each of these messages.
|
*/
"accepted" => ":attribute debe ser aceptado.",
"active_url" => ":attribute no es una URL válida.",
"after" => ":attribute debe ser una fecha posterior a :date.",
"alpha" => ":attribute solo debe contener letras.",
"alpha_dash" => ":attribute solo debe contener letras, números y guiones.",
"alpha_num" => ":attribute solo debe contener letras y números.",
"array" => ":attribute debe ser un conjunto.",
"before" => ":attribute debe ser una fecha anterior a :date.",
"between" => array(
"numeric" => ":attribute tiene que estar entre :min - :max.",
"file" => ":attribute debe pesar entre :min - :max kilobytes.",
"string" => ":attribute tiene que tener entre :min - :max caracteres.",
"array" => ":attribute tiene que tener entre :min - :max ítems.",
),
"confirmed" => "La confirmación de :attribute no coincide.",
"date" => ":attribute no es una fecha válida.",
"date_format" => ":attribute no corresponde al formato :format.",
"different" => ":attribute y :other deben ser diferentes.",
"digits" => ":attribute debe tener :digits dígitos.",
"digits_between" => ":attribute debe tener entre :min y :max dígitos.",
"email" => ":attribute no es un correo válido",
"exists" => ":attribute es inválido.",
"image" => ":attribute debe ser una imagen.",
"in" => ":attribute es inválido.",
"integer" => ":attribute debe ser un número entero.",
"ip" => ":attribute debe ser una dirección IP válida.",
"max" => array(
"numeric" => ":attribute no debe ser mayor a :max.",
"file" => ":attribute no debe ser mayor que :max kilobytes.",
"string" => ":attribute no debe ser mayor que :max caracteres.",
"array" => ":attribute no debe tener más de :max elementos.",
),
"mimes" => ":attribute debe ser un archivo con formato: :values.",
"min" => array(
"numeric" => "El tamaño de :attribute debe ser de al menos :min.",
"file" => "El tamaño de :attribute debe ser de al menos :min kilobytes.",
"string" => ":attribute debe contener al menos :min caracteres.",
"array" => ":attribute debe tener al menos :min elementos.",
),
"not_in" => ":attribute es inválido.",
"numeric" => ":attribute debe ser numérico.",
"regex" => "El formato de :attribute es inválido.",
"required" => "El campo :attribute es obligatorio.",
"required_if" => "El campo :attribute es obligatorio cuando :other es :value.",
"required_with" => "El campo :attribute es obligatorio cuando :values está presente.",
"required_with_all" => "The :attribute field is required when :values is present.",
"required_without" => "El campo :attribute es obligatorio cuando :values no está presente.",
"required_without_all" => "The :attribute field is required when none of :values are present.",
"same" => ":attribute y :other deben coincidir.",
"size" => array(
"numeric" => "El tamaño de :attribute debe ser :size.",
"file" => "El tamaño de :attribute debe ser :size kilobytes.",
"string" => ":attribute debe contener :size caracteres.",
"array" => ":attribute debe contener :size elementos.",
),
"unique" => ":attribute ya ha sido registrado.",
"url" => "El formato :attribute es inválido.",
"positive" => ":attribute debe ser mayor que cero.",
"has_credit" => "el cliente no tiene crédito suficiente.",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => array(
'attribute-name' => array(
'rule-name' => 'custom-message',
),
),
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => array(),
);

View File

@ -0,0 +1,20 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Précédent',
'next' => 'Suivant &raquo;',
);

24
app/lang/fr/reminders.php Normal file
View File

@ -0,0 +1,24 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Password Reminder Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
"password" => "Les mots de passe doivent avoir au moins six caractères et doivent être identiques.",
"user" => "Nous ne pouvons trouver cet utilisateur avec cette adresse e-mail.",
"token" => "Ce jeton de réinitialisation du mot de passe n'est pas valide.",
"sent" => "Rappel du mot de passe envoyé !",
);

54
app/lang/fr/texts.php Normal file
View File

@ -0,0 +1,54 @@
<?php
return array(
// client
'organization' => 'Entreprise',
'name' => 'Nom',
'website' => 'Site web',
'work_phone' => 'Téléphone',
'address' => 'Adresse',
'address1' => 'Rue',
'address2' => 'Appt/Batîment',
'city' => 'Ville',
'state' => 'Région/Département',
'postal_code' => 'Code Postal',
'country_id' => 'Pays',
'contacts' => 'Informations de contact', //if you speak about contact details
'first_name' => 'Prénom',
'last_name' => 'Nom',
'phone' => 'Téléphone',
'email' => 'Email',
'additional_info' => 'Informations complémentaires',
'payment_terms' => 'Conditions de paiement',
'currency_id' => 'Devise',
'size_id' => 'Taille',
'industry_id' => 'Secteur', // literal translation : Industrie
'private_notes' => 'Note personnelle',
// invoice
'invoice' => 'Facture',
'client' => 'Client',
'invoice_date' => 'Date de la facture',
'due_date' => 'Date d\'échéance',
'invoice_number' => 'Numéro de facture',
'invoice_number_short' => 'Facture #',
'po_number' => 'Numéro du bon de commande',
'po_number_short' => 'Bon de commande #',
'frequency_id' => 'Fréquence', //litteral translation : Combien de fois
'discount' => 'Remise', //can be "rabais" or "réduction"
'taxes' => 'Taxes',
'tax' => 'Taxe',
'item' => 'Ligne', //I'm not sure, I need the context : screenshot ?
'description' => 'Description',
'unit_cost' => 'Coût à l\'unité',
'quantity' => 'Quantité',
'line_total' => 'Total',
'subtotal' => 'Total',
'paid_to_date' => 'Versé à ce jour',//this one is not very used in France
'balance_due' => 'Montant total',//can be "Montant à verser" or "Somme totale"
'invoice_design_id' => 'Design', //if you speak about invoice's design -> "Modèle"
'terms' => 'Conditions',
'your_invoice' => 'Votre Facture',
);

134
app/lang/fr/validation.php Normal file
View File

@ -0,0 +1,134 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| such as the size rules. Feel free to tweak each of these messages.
|
*/
"accepted" => "Le champ :attribute doit être accepté.",
"active_url" => "Le champ :attribute n'est pas une URL valide.",
"after" => "Le champ :attribute doit être une date postérieure au :date.",
"alpha" => "Le champ :attribute doit seulement contenir des lettres.",
"alpha_dash" => "Le champ :attribute doit seulement contenir des lettres, des chiffres et des tirets.",
"alpha_num" => "Le champ :attribute doit seulement contenir des chiffres et des lettres.",
"array" => "Le champ :attribute doit être un tableau.",
"before" => "Le champ :attribute doit être une date antérieure au :date.",
"between" => array(
"numeric" => "La valeur de :attribute doit être comprise entre :min et :max.",
"file" => "Le fichier :attribute doit avoir une taille entre :min et :max kilobytes.",
"string" => "Le texte :attribute doit avoir entre :min et :max caractères.",
"array" => "Le champ :attribute doit avoir entre :min et :max éléments."
),
"confirmed" => "Le champ de confirmation :attribute ne correspond pas.",
"date" => "Le champ :attribute n'est pas une date valide.",
"date_format" => "Le champ :attribute ne correspond pas au format :format.",
"different" => "Les champs :attribute et :other doivent être différents.",
"digits" => "Le champ :attribute doit avoir :digits chiffres.",
"digits_between" => "Le champ :attribute doit avoir entre :min and :max chiffres.",
"email" => "Le champ :attribute doit être une adresse email valide.",
"exists" => "Le champ :attribute sélectionné est invalide.",
"image" => "Le champ :attribute doit être une image.",
"in" => "Le champ :attribute est invalide.",
"integer" => "Le champ :attribute doit être un entier.",
"ip" => "Le champ :attribute doit être une adresse IP valide.",
"max" => array(
"numeric" => "La valeur de :attribute ne peut être supérieure à :max.",
"file" => "Le fichier :attribute ne peut être plus gros que :max kilobytes.",
"string" => "Le texte de :attribute ne peut contenir plus de :max caractères.",
"array" => "Le champ :attribute ne peut avoir plus de :max éléments.",
),
"mimes" => "Le champ :attribute doit être un fichier de type : :values.",
"min" => array(
"numeric" => "La valeur de :attribute doit être supérieure à :min.",
"file" => "Le fichier :attribute doit être plus que gros que :min kilobytes.",
"string" => "Le texte :attribute doit contenir au moins :min caractères.",
"array" => "Le champ :attribute doit avoir au moins :min éléments."
),
"not_in" => "Le champ :attribute sélectionné n'est pas valide.",
"numeric" => "Le champ :attribute doit contenir un nombre.",
"regex" => "Le format du champ :attribute est invalide.",
"required" => "Le champ :attribute est obligatoire.",
"required_if" => "Le champ :attribute est obligatoire quand la valeur de :other est :value.",
"required_with" => "Le champ :attribute est obligatoire quand :values est présent.",
"required_with_all" => "Le champ :attribute est obligatoire quand :values est présent.",
"required_without" => "Le champ :attribute est obligatoire quand :values n'est pas présent.",
"required_without_all" => "Le champ :attribute est requis quand aucun de :values n'est présent.",
"same" => "Les champs :attribute et :other doivent être identiques.",
"size" => array(
"numeric" => "La valeur de :attribute doit être :size.",
"file" => "La taille du fichier de :attribute doit être de :size kilobytes.",
"string" => "Le texte de :attribute doit contenir :size caractères.",
"array" => "Le champ :attribute doit contenir :size éléments."
),
"unique" => "La valeur du champ :attribute est déjà utilisée.",
"url" => "Le format de l'URL de :attribute n'est pas valide.",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => array(
'attribute-name' => array(
'rule-name' => 'custom-message',
),
),
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => array(
"name" => "Nom",
"username" => "Pseudo",
"email" => "E-mail",
"first_name" => "Prénom",
"last_name" => "Nom",
"password" => "Mot de passe",
"password_confirmation" => "Confirmation du mot de passe",
"city" => "Ville",
"country" => "Pays",
"address" => "Adresse",
"phone" => "Téléphone",
"mobile" => "Portable",
"age" => "Age",
"sex" => "Sexe",
"gender" => "Genre",
"day" => "Jour",
"month" => "Mois",
"year" => "Année",
"hour" => "Heure",
"minute" => "Minute",
"second" => "Seconde",
"title" => "Titre",
"content" => "Contenu",
"description" => "Description",
"excerpt" => "Extrait",
"date" => "Date",
"time" => "Heure",
"available" => "Disponible",
"size" => "Taille"
),
);

View File

@ -0,0 +1,20 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Precedente',
'next' => 'Successivo &raquo;',
);

24
app/lang/it/reminders.php Normal file
View File

@ -0,0 +1,24 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Password Reminder Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
"password" => "Le password devono essere di almeno 6 caratteri e devono coincidere.",
"user" => "Non esiste un utente associato a questo indirizzo e-mail.",
"token" => "Questo token per la reimpostazione della password non è valido.",
"sent" => "Promemoria della password inviato!",
);

54
app/lang/it/texts.php Normal file
View File

@ -0,0 +1,54 @@
<?php
return array(
// client
'organization' => 'Organization',
'name' => 'Name',
'website' => 'Website',
'work_phone' => 'Phone',
'address' => 'Address',
'address1' => 'Street',
'address2' => 'Apt/Suite',
'city' => 'City',
'state' => 'State/Province',
'postal_code' => 'Postal Code',
'country_id' => 'Country',
'contacts' => 'Contacts',
'first_name' => 'First Name',
'last_name' => 'Last Name',
'phone' => 'Phone',
'email' => 'Email',
'additional_info' => 'Additional Info',
'payment_terms' => 'Payment Terms',
'currency_id' => 'Currency',
'size_id' => 'Size',
'industry_id' => 'Industry',
'private_notes' => 'Private Notes',
// invoice
'invoice' => 'Invoice',
'client' => 'Client',
'invoice_date' => 'Invoice Date',
'due_date' => 'Due Date',
'invoice_number' => 'Invoice Number',
'invoice_number_short' => 'Invoice #',
'po_number' => 'PO Number',
'po_number_short' => 'PO #',
'frequency_id' => 'How often',
'dicount' => 'Discount',
'taxes' => 'Taxes',
'tax' => 'Tax',
'item' => 'Item',
'description' => 'Description',
'unit_cost' => 'Unit Cost',
'quantity' => 'Quantity',
'line_total' => 'Line Total',
'subtotal' => 'Subtotal',
'paid_to_date' => 'Paid to Date',
'balance_due' => 'Balance Due',
'invoice_design_id' => 'Design',
'terms' => 'Terms',
'your_invoice' => 'Your Invoice',
);

103
app/lang/it/validation.php Normal file
View File

@ -0,0 +1,103 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| such as the size rules. Feel free to tweak each of these messages.
|
*/
"accepted" => ":attribute deve essere accettato.",
"active_url" => ":attribute non è un URL valido.",
"after" => ":attribute deve essere una data successiva al :date.",
"alpha" => ":attribute può contenere solo lettere.",
"alpha_dash" => ":attribute può contenere solo lettere, numeri e trattini.",
"alpha_num" => ":attribute può contenere solo lettere e numeri.",
"array" => ":attribute deve essere un array.",
"before" => ":attribute deve essere una data precedente al :date.",
"between" => array(
"numeric" => ":attribute deve trovarsi tra :min - :max.",
"file" => ":attribute deve trovarsi tra :min - :max kilobytes.",
"string" => ":attribute deve trovarsi tra :min - :max caratteri.",
"array" => ":attribute deve avere tra :min - :max elementi."
),
"confirmed" => "Il campo di conferma per :attribute non coincide.",
"date" => ":attribute non è una data valida.",
"date_format" => ":attribute non coincide con il formato :format.",
"different" => ":attribute e :other devono essere differenti.",
"digits" => ":attribute deve essere di :digits cifre.",
"digits_between" => ":attribute deve essere tra :min e :max cifre.",
"email" => ":attribute non è valido.",
"exists" => ":attribute selezionato/a non è valido.",
"image" => ":attribute deve essere un'immagine.",
"in" => ":attribute selezionato non è valido.",
"integer" => ":attribute deve essere intero.",
"ip" => ":attribute deve essere un indirizzo IP valido.",
"max" => array(
"numeric" => ":attribute deve essere minore di :max.",
"file" => ":attribute non deve essere più grande di :max kilobytes.",
"string" => ":attribute non può contenere più di :max caratteri.",
"array" => ":attribute non può avere più di :max elementi."
),
"mimes" => ":attribute deve essere del tipo: :values.",
"min" => array(
"numeric" => ":attribute deve valere almeno :min.",
"file" => ":attribute deve essere più grande di :min kilobytes.",
"string" => ":attribute deve contenere almeno :min caratteri.",
"array" => ":attribute deve avere almeno :min elementi."
),
"not_in" => "Il valore selezionato per :attribute non è valido.",
"numeric" => ":attribute deve essere un numero.",
"regex" => "Il formato del campo :attribute non è valido.",
"required" => ":attribute è richiesto.",
"required_if" => "Il campo :attribute è richiesto quando :other è :value.",
"required_with" => "Il campo :attribute è richiesto quando :values è presente.",
"required_with_all" => "The :attribute field is required when :values is present.",
"required_without" => "Il campo :attribute è richiesto quando :values non è presente.",
"required_without_all" => "The :attribute field is required when none of :values are present.",
"same" => ":attribute e :other devono coincidere.",
"size" => array(
"numeric" => ":attribute deve valere :size.",
"file" => ":attribute deve essere grande :size kilobytes.",
"string" => ":attribute deve contenere :size caratteri.",
"array" => ":attribute deve contenere :size elementi."
),
"unique" => ":attribute è stato già utilizzato.",
"url" => ":attribute deve essere un URL.",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => array(
'attribute-name' => array(
'rule-name' => 'custom-message',
),
),
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => array(),
);

View File

@ -0,0 +1,20 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Anterior',
'next' => 'Próximo &raquo;',
);

View File

@ -0,0 +1,24 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Password Reminder Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
"password" => "Senhas devem possuir no mínimo seis caracteres e devem ser iguais.",
"user" => "Não achamos um usuário com o endereço de e-mail informado.",
"token" => "Este token de redefinição de senha é inválido.",
"sent" => "Lmebrete de senha enviado!",
);

53
app/lang/pt_BR/texts.php Normal file
View File

@ -0,0 +1,53 @@
<?php
return array(
'organization' => 'Organização',
'name' => 'Nome',
'website' => 'Website',
'work_phone' => 'Telefone',
'address' => 'Endereço',
'address1' => 'Rua',
'address2' => 'Bloco/apto',
'city' => 'Cidade',
'state' => 'Estado',
'postal_code' => 'CEP',
'country_id' => 'País',
'contacts' => 'Contatos',
'first_name' => 'Primeiro Nome',
'last_name' => 'Último Nome',
'phone' => 'Telefone',
'email' => 'Email',
'additional_info' => 'Informações Adicionais',
'payment_terms' => 'Termos de Pagamento',
'currency_id' => 'Moeda',
'size_id' => 'Tamanho',
'industry_id' => 'Empresa',
'private_notes' => 'Notas Privadas',
// invoice
'invoice' => 'Invoice',
'client' => 'Client',
'invoice_date' => 'Invoice Date',
'due_date' => 'Due Date',
'invoice_number' => 'Invoice Number',
'invoice_number_short' => 'Invoice #',
'po_number' => 'PO Number',
'po_number_short' => 'PO #',
'frequency_id' => 'How often',
'discount' => 'Discount',
'taxes' => 'Taxes',
'tax' => 'Tax',
'item' => 'Item',
'description' => 'Description',
'unit_cost' => 'Unit Cost',
'quantity' => 'Quantity',
'line_total' => 'Line Total',
'subtotal' => 'Subtotal',
'paid_to_date' => 'Paid to Date',
'balance_due' => 'Balance Due',
'invoice_design_id' => 'Design',
'terms' => 'Terms',
'your_invoice' => 'Your Invoice',
);

View File

@ -0,0 +1,102 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| such as the size rules. Feel free to tweak each of these messages.
|
*/
"accepted" => ":attribute deve ser aceito.",
"active_url" => ":attribute não é uma URL válida.",
"after" => ":attribute deve ser uma data maior que :date.",
"alpha" => ":attribute deve conter apenas letras.",
"alpha_dash" => ":attribute pode conter apenas letras, número e traços",
"alpha_num" => ":attribute pode conter apenas letras e números.",
"array" => ":attribute deve ser um array.",
"before" => ":attribute deve ser uma data anterior a :date.",
"between" => array(
"numeric" => ":attribute deve ser entre :min - :max.",
"file" => ":attribute deve ser entre :min - :max kilobytes.",
"string" => ":attribute deve ser entre :min - :max caracteres.",
"array" => ":attribute deve conter entre :min - :max itens.",
),
"confirmed" => ":attribute confirmação não correponde.",
"date" => ":attribute não é uma data válida.",
"date_format" => ":attribute não satisfaz o formato :format.",
"different" => ":attribute e :other devem ser diferentes.",
"digits" => ":attribute deve conter :digits dígitos.",
"digits_between" => ":attribute deve conter entre :min e :max dígitos.",
"email" => ":attribute está em um formato inválido.",
"exists" => "A opção selecionada :attribute é inválida.",
"image" => ":attribute deve ser uma imagem.",
"in" => "A opção selecionada :attribute é inválida.",
"integer" => ":attribute deve ser um número inteiro.",
"ip" => ":attribute deve ser um endereço IP válido.",
"max" => array(
"numeric" => ":attribute não pode ser maior que :max.",
"file" => ":attribute não pode ser maior que :max kilobytes.",
"string" => ":attribute não pode ser maior que :max caracteres.",
"array" => ":attribute não pode conter mais que :max itens.",
),
"mimes" => ":attribute deve ser um arquivo do tipo: :values.",
"min" => array(
"numeric" => ":attribute não deve ser menor que :min.",
"file" => ":attribute deve ter no mínimo :min kilobytes.",
"string" => ":attribute deve conter no mínimo :min caracteres.",
"array" => ":attribute deve conter ao menos :min itens.",
),
"not_in" => "A opção selecionada :attribute é inválida.",
"numeric" => ":attribute deve ser um número.",
"regex" => ":attribute está em um formato inválido.",
"required" => ":attribute é um campo obrigatório.",
"required_if" => ":attribute é necessário quando :other é :value.",
"required_with" => ":attribute é obrigatório quando :values está presente.",
"required_without" => ":attribute é obrigatório quando :values não está presente.",
"same" => ":attribute e :other devem corresponder.",
"size" => array(
"numeric" => ":attribute deve ter :size.",
"file" => ":attribute deve ter :size kilobytes.",
"string" => ":attribute deve conter :size caracteres.",
"array" => ":attribute deve conter :size itens.",
),
"unique" => ":attribute já está sendo utilizado.",
"url" => ":attribute está num formato inválido.",
"positive" => ":attribute deve ser maior que zero.",
"has_credit" => "O cliente não possui crédito suficiente.",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => array(),
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => array(),
);

View File

@ -2,6 +2,35 @@
class Utils
{
public static function isProd()
{
return App::environment() == ENV_PRODUCTION;
}
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)
@ -27,11 +56,10 @@ class Utils
'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),
'input' => Input::all()
'count' => Session::get('error_count', 0)
];
Log::error('\n'.$error, $data);
Log::error($error."\n", $data);
/*
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
@ -95,7 +123,8 @@ class Utils
public static function pluralize($string, $count)
{
$string = str_replace('?', $count, $string);
return $count == 1 ? $string : $string . 's';
$field = $count == 1 ? $string : $string . 's';
return trans("texts.$field");
}
public static function toArray($data)

View File

@ -118,13 +118,21 @@ class Account extends Eloquent
public function getLogoWidth()
{
list($width, $height) = getimagesize($this->getLogoPath());
$path = $this->getLogoPath();
if (!file_exists($path)) {
return 0;
}
list($width, $height) = getimagesize($path);
return $width;
}
public function getLogoHeight()
{
list($width, $height) = getimagesize($this->getLogoPath());
$path = $this->getLogoPath();
if (!file_exists($path)) {
return 0;
}
list($width, $height) = getimagesize($path);
return $height;
}
@ -160,4 +168,37 @@ class Account extends Eloquent
Session::put(SESSION_DATETIME_FORMAT, $this->datetime_format ? $this->datetime_format->format : DEFAULT_DATETIME_FORMAT);
Session::put(SESSION_CURRENCY, $this->currency_id ? $this->currency_id : DEFAULT_CURRENCY);
}
public function getInvoiceLabels()
{
$data = [];
$fields = [
'invoice',
'invoice_date',
'due_date',
'invoice_number',
'po_number',
'discount',
'taxes',
'tax',
'item',
'description',
'unit_cost',
'quantity',
'line_total',
'subtotal',
'paid_to_date',
'balance_due',
'terms',
'your_invoice',
];
foreach ($fields as $field)
{
$data[$field] = trans("texts.$field");
}
return $data;
}
}

View File

@ -96,13 +96,13 @@ class Activity extends Eloquent
public static function createInvoice($invoice)
{
if ($invoice->is_recurring)
if (Auth::check())
{
$message = Utils::encodeActivity(null, 'created recurring', $invoice);
$message = Utils::encodeActivity(Auth::user(), 'created', $invoice);
}
else
{
$message = Utils::encodeActivity(Auth::user(), 'created', $invoice);
$message = Utils::encodeActivity(null, 'created', $invoice);
}
$client = $invoice->client;
@ -235,9 +235,7 @@ class Activity extends Eloquent
$client->last_login = $now;
$client->save();
$activity = new Activity;
$activity->user_id = $invitation->user_id;
$activity->account_id = $invitation->user->account_id;
$activity = Activity::getBlank($invitation);
$activity->client_id = $invitation->invoice->client_id;
$activity->invitation_id = $invitation->id;
$activity->contact_id = $invitation->contact_id;
@ -259,7 +257,7 @@ class Activity extends Eloquent
if ($payment->contact_id)
{
$activity = new Activity;
$activity = Activity::getBlank($client);
$activity->contact_id = $payment->contact_id;
$activity->message = Utils::encodeActivity($payment->invitation->contact, 'entered payment');
}

7
app/models/Language.php Executable file
View File

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

View File

@ -104,11 +104,25 @@ class User extends ConfideUser implements UserInterface, RemindableInterface
}
}
public function getLocale()
{
$language = Language::remember(DEFAULT_QUERY_CACHE)->where('id', '=', $this->account->language_id)->first();
return $language->locale;
}
public function showGreyBackground()
{
return !$this->theme_id || in_array($this->theme_id, [2, 3, 5, 6, 7, 8, 10, 11, 12]);
}
public function showSignUpPopOver()
{
$count = Session::get(SESSION_COUNTER, 0);
Session::put(SESSION_COUNTER, ++$count);
return $count == 1 || $count % 7 == 0;
}
public function afterSave($success=true, $forced = false)
{
if ($this->email)

View File

@ -37,7 +37,10 @@ class ContactMailer extends Mailer {
'emailFooter' => $invoice->account->email_footer
];
$this->sendTo($invitation->contact->email, $invitation->user->email, $subject, $view, $data);
$fromEmail = $invitation->user->email;
$fromName = $invitation->user->getDisplayName();
$this->sendTo($invitation->contact->email, $fromEmail, $fromName, $subject, $view, $data);
Activity::emailInvoice($invitation);
}
@ -63,6 +66,7 @@ class ContactMailer extends Mailer {
'paymentAmount' => Utils::formatMoney($payment->amount, $payment->client->currency_id)
];
$this->sendTo($payment->contact->email, $payment->invitation->user->email, $subject, $view, $data);
$user = $payment->invitation->user;
$this->sendTo($payment->contact->email, $user->email, $user->getDisplayName(), $subject, $view, $data);
}
}

View File

@ -4,7 +4,7 @@ use Mail;
class Mailer {
public function sendTo($toEmail, $fromEmail, $subject, $view, $data = [])
public function sendTo($toEmail, $fromEmail, $fromName, $subject, $view, $data = [])
{
$views = [
'emails.'.$view.'_html',
@ -13,9 +13,10 @@ class Mailer {
//$view = 'emails.' . $view;
Mail::queue($views, $data, function($message) use ($toEmail, $fromEmail, $subject)
Mail::queue($views, $data, function($message) use ($toEmail, $fromEmail, $fromName, $subject)
{
$message->to($toEmail)->replyTo($fromEmail)->subject($subject);
$message->to($toEmail)->from($fromEmail, $fromName)->sender($fromEmail, $fromName)
->replyTo($fromEmail, $fromName)->returnPath($fromEmail)->subject($subject);
});
}
}

View File

@ -22,7 +22,7 @@ class UserMailer extends Mailer {
'user' => $user
];
$this->sendTo($user->email, CONTACT_EMAIL, $subject, $view, $data);
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
}
public function sendNotification(User $user, Invoice $invoice, $type, Payment $payment = null)
@ -63,6 +63,6 @@ class UserMailer extends Mailer {
$subject = "Invoice {$invoice->invoice_number} was $action {$invoice->client->getDisplayName()}";
$this->sendTo($user->email, CONTACT_EMAIL, $subject, $view, $data);
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
}
}

View File

@ -1,5 +1,6 @@
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
@ -22,38 +23,6 @@
//dd(gethostname());
//Log::error('test');
/*
Event::listen('illuminate.query', function($query, $bindings, $time, $name)
{
$data = compact('bindings', 'time', 'name');
// Format binding data for sql insertion
foreach ($bindings as $i => $binding)
{
if ($binding instanceof \DateTime)
{
$bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
}
else if (is_string($binding))
{
$bindings[$i] = "'$binding'";
}
}
// Insert bindings into query
$query = str_replace(array('%', '?'), array('%%', '%s'), $query);
$query = vsprintf($query, $bindings);
Log::info($query, $data);
});
*/
/*
Route::get('/send_emails', function() {
Artisan::call('ninja:send-invoices');
});
*/
Route::get('/', 'HomeController@showWelcome');
Route::get('/rocksteady', 'HomeController@showWelcome');
Route::get('/about', 'HomeController@showAboutUs');
@ -87,11 +56,13 @@ Route::group(array('before' => 'auth'), function()
{
Route::get('dashboard', 'DashboardController@index');
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
Route::get('force_inline_pdf', 'UserController@forcePDFJS');
Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData'));
Route::get('company/{section?}', 'AccountController@showSection');
Route::post('company/{section?}', 'AccountController@doSection');
Route::post('user/setTheme', 'UserController@setTheme');
Route::post('remove_logo', 'AccountController@removeLogo');
Route::resource('clients', 'ClientController');
Route::get('api/clients', array('as'=>'api.clients', 'uses'=>'ClientController@getDatatable'));
@ -127,7 +98,7 @@ Route::group(array('before' => 'auth'), function()
HTML::macro('nav_link', function($url, $text, $url2 = '', $extra = '') {
$class = ( Request::is($url) || Request::is($url.'/*') || Request::is($url2) ) ? ' class="active"' : '';
return '<li'.$class.'><a href="'.URL::to($url).'" '.$extra.'>'.$text.'</a></li>';
return '<li'.$class.'><a href="'.URL::to($url).'" '.$extra.'>'.trans("texts.$text").'</a></li>';
});
HTML::macro('tab_link', function($url, $text, $active = false) {
@ -163,7 +134,17 @@ HTML::macro('image_data', function($imagePath) {
HTML::macro('breadcrumbs', function() {
$str = '<ol class="breadcrumb">';
$crumbs = explode('/', $_SERVER['REQUEST_URI']);
// Get the breadcrumbs by exploding the current path.
$basePath = Utils::basePath();
$parts = explode('?', $_SERVER['REQUEST_URI']);
$path = $parts[0];
if ($basePath != '/')
{
$path = str_replace($basePath, '', $path);
}
$crumbs = explode('/', $path);
foreach ($crumbs as $key => $val)
{
@ -172,6 +153,7 @@ HTML::macro('breadcrumbs', function() {
unset($crumbs[$key]);
}
}
$crumbs = array_values($crumbs);
for ($i=0; $i<count($crumbs); $i++) {
$crumb = trim($crumbs[$i]);
@ -190,7 +172,10 @@ HTML::macro('breadcrumbs', function() {
return $str . '</ol>';
});
define('CONTACT_EMAIL', 'contact@invoiceninja.com');
define('CONTACT_NAME', 'Invoice Ninja');
define('ENV_DEVELOPMENT', 'local');
define('ENV_STAGING', 'staging');
@ -240,13 +225,16 @@ define('SESSION_CURRENCY', 'currency');
define('SESSION_DATE_FORMAT', 'dateFormat');
define('SESSION_DATE_PICKER_FORMAT', 'datePickerFormat');
define('SESSION_DATETIME_FORMAT', 'datetimeFormat');
define('SESSION_COUNTER', 'sessionCounter');
define('DEFAULT_TIMEZONE', 'US/Eastern');
define('DEFAULT_CURRENCY', 1); // US Dollar
define('DEFAULT_DATE_FORMAT', 'M j, Y');
define('DEFAULT_DATE_PICKER_FORMAT', 'M d, yyyy');
define('DEFAULT_DATETIME_FORMAT', 'F j, Y, g:i a');
define('DEFAULT_QUERY_CACHE', 120);
define('DEFAULT_QUERY_CACHE', 120); // minutes
define('GATEWAY_PAYPAL_EXPRESS', 17);
if (Auth::check() && !Session::has(SESSION_TIMEZONE))
@ -254,7 +242,6 @@ if (Auth::check() && !Session::has(SESSION_TIMEZONE))
Event::fire('user.refresh');
}
Validator::extend('positive', function($attribute, $value, $parameters)
{
return Utils::parseFloat($value) > 0;
@ -270,3 +257,37 @@ Validator::extend('has_credit', function($attribute, $value, $parameters)
return $credit >= $amount;
});
/*
Event::listen('illuminate.query', function($query, $bindings, $time, $name)
{
$data = compact('bindings', 'time', 'name');
// Format binding data for sql insertion
foreach ($bindings as $i => $binding)
{
if ($binding instanceof \DateTime)
{
$bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
}
else if (is_string($binding))
{
$bindings[$i] = "'$binding'";
}
}
// Insert bindings into query
$query = str_replace(array('%', '?'), array('%%', '%s'), $query);
$query = vsprintf($query, $bindings);
Log::info($query, $data);
});
*/
/*
if (Auth::check() && Auth::user()->id === 1)
{
Auth::loginUsingId(1);
}
*/

View File

@ -1,170 +0,0 @@
@extends('master')
@section('head')
<link href="{{ asset('css/bootstrap.splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('css/splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('images/apple-touch-icon-114x114-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="114x114">
<link href="{{ asset('images/apple-touch-icon-72x72-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="72x72">
<link href="{{ asset('images/apple-touch-icon-57x57-precomposed.png') }}" rel="apple-touch-icon-precomposed">
@stop
@section('body')
{{ Form::open(array('url' => 'get_started', 'id' => 'startForm')) }}
{{ Form::hidden('guest_key') }}
{{ Form::close() }}
<script>
$(document).ready(function () {
if (isStorageSupported()) {
$('[name="guest_key"]').val(localStorage.getItem('guest_key'));
}
});
function isStorageSupported() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
}
function getStarted() {
$('#startForm').submit();
}
</script>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="navbar-inner">
<a class="brand" href="/"><img src=
"images/invoiceninja-logo.png"></a>
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
</div>
</div>
</div>
</div>
<section class="hero3" data-speed="2" data-type="background">
<div class="container">
<div class="caption">
<h1>WHY INVOICE NINJA?
</h1>
</div>
</div>
</section>
<section class="about center">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Open Source Platform</h2>
<p>Free yourself from online invoicing platforms with high monthly fees and limited functionality. Being <a href="https://github.com/hillelcoren/invoice-ninja" target="_blank">open source</a> allows us fast app development, security audits by the open-course community, and we can keep it <strong>FREE!</strong></p>
</div>
</div>
</div>
</section>
<section class="about white-bg">
<div class="container">
<div class="row">
<div class="col-md-5">
<div class="screendump">
<img src="images/about1.jpg">
</div>
</div>
<div class="col-md-7">
<h2>Live PDF Creation</h2>
<p><strong>Look professional from day #1.</strong> Select one of our beautiful invoice templates to suit your company identity, switch between designs in real time to preview invoices & email them to clients with one click. The live preview PDF function was designed for an efficient and hassle-free experience, and its awesome!
</p>
</div>
</div>
</div>
</section>
<section class="about">
<div class="container">
<div class="row">
<div class="col-md-7">
<h2>Online Payments</h2>
<p><strong>Authorize.net, Beanstream, PayPal?</strong> InvoiceNinja supports the most popular online payment gateways! If you need help integrating a third party gateway we dont yet support, please contact us! Were happy to help! If you need assistance of want to learn more about online payment solutions, contact us!</p>
</div>
<div class="col-md-5">
<div class="screendump">
<img src="images/about2.jpg">
</div>
</div>
</div>
</div>
</section>
<!--
<section class="about center white-bg">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Info about the company/story</h2>
<p>Donec id elit non mi porta gravida at eget metus.
Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet
risus. Etiam porta sem malesuada magna mollis euismod.
Donec sed odio dui.</p>
</div>
</div>
</div>
</section>
-->
<section class="upper-footer">
<div class="container">
<div class="row">
<div class="col-md-3 center-block">
<a href="#">
<div class="cta">
<h2 onclick="getStarted()">Invoice Now <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
<footer>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="social">
<!--
<a href="http://twitter.com/eas_id"><span class=
"socicon">c</span></a>
-->
<a href=
"http://facebook.com/invoiceninja" target="_blank"><span class=
"socicon">b</span></a> <a href=
"http://twitter.com/invoiceninja" target="_blank"><span class=
"socicon">a</span></a>
<p>Copyright © 2014 InvoiceNinja. All rights reserved.</p>
</div>
<div class="navbar-inner">
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
<!--
<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>
</footer><script src="{{ asset('/js/retina-1.1.0.min.js') }}" type="text/javascript"></script>
@stop

View File

@ -29,11 +29,12 @@
{{ Former::text('name') }}
{{ Former::text('work_email')->label('Email') }}
{{ Former::text('work_phone')->label('Phone') }}
{{ Former::file('logo')->max(2, 'MB')->accept('image')->inlineHelp('Supported: JPEG, GIF and PNG. Recommnded size: 120px width, 80px height') }}
{{ Former::file('logo')->max(2, 'MB')->accept('image')->inlineHelp('Supported: JPEG, GIF and PNG. Recommended height: 120px') }}
@if (file_exists($account->getLogoPath()))
<center>
{{ HTML::image($account->getLogoPath(), "Logo") }}
{{ HTML::image($account->getLogoPath(), "Logo") }} &nbsp;
<a href="#" onclick="deleteLogo()">Remove logo</a>
</center><br/>
@endif
@ -61,8 +62,9 @@
{{ Former::text('email') }}
{{ Former::text('phone') }}
{{ Former::legend('Localization') }}
{{ Former::select('language_id')->addOption('','')->label('Language')
->fromQuery($languages, 'name', 'id') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')
->fromQuery($currencies, 'name', 'id') }}
{{ Former::select('timezone_id')->addOption('','')->label('Timezone')
@ -82,12 +84,22 @@
{{ Former::close() }}
{{ Form::open(['url' => 'remove_logo', 'class' => 'removeLogoForm']) }}
{{ Form::close() }}
<script type="text/javascript">
$(function() {
$('#country_id').combobox();
});
function deleteLogo() {
if (confirm('Are you sure?')) {
$('.removeLogoForm').submit();
}
}
</script>
@stop

View File

@ -3,10 +3,10 @@
@section('content')
<ul class="nav nav-tabs nav nav-justified">
{{ HTML::nav_link('company/details', 'Company Details') }}
{{ HTML::nav_link('company/payments', 'Online Payments') }}
{{ HTML::nav_link('company/notifications', 'Notifications') }}
{{ HTML::nav_link('company/import_export', 'Import/Export', 'company/import_map') }}
{{ HTML::nav_link('company/details', 'company_details') }}
{{ HTML::nav_link('company/payments', 'online_payments') }}
{{ HTML::nav_link('company/notifications', 'notifications') }}
{{ HTML::nav_link('company/import_export', 'import_export', 'company/import_map') }}
</ul>
<p>&nbsp;</p>

View File

@ -14,6 +14,28 @@
{{ Former::checkbox('notify_viewed')->label('&nbsp;')->text('Email me when an invoice is <b>viewed</b>') }}
{{ Former::checkbox('notify_paid')->label('&nbsp;')->text('Email me when an invoice is <b>paid</b>') }}
{{ Former::legend('Site Updates') }}
<div class="form-group">
<label for="invoice_terms" class="control-label col-lg-4 col-sm-4"></label>
<div class="col-lg-8 col-sm-8">
<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>
</div></div>
{{ Former::legend('Custom Messages') }}
{{ Former::textarea('invoice_terms')->label('Set default invoice terms') }}
{{ Former::textarea('email_footer')->label('Set default email signature') }}

View File

@ -14,10 +14,8 @@
@if ($accountGateway)
{{ Former::populateField('gateway_id', $accountGateway->gateway_id) }}
@foreach ($accountGateway->fields as $field => $junk)
@if ($field == 'testMode' || $field == 'developerMode')
@if ($config->$field)
{{-- Former::populateField($accountGateway->gateway_id.'_'.$field, true ) --}}
@endif
@if (in_array($field, ['solutionType', 'landingPage', 'headerImageUrl', 'brandName']))
{{-- do nothing --}}
@else
{{ Former::populateField($accountGateway->gateway_id.'_'.$field, $config->$field) }}
@endif
@ -32,7 +30,7 @@
<div id="gateway_{{ $gateway->id }}_div" style="display: none">
@foreach ($gateway->fields as $field => $details)
@if ($field == 'solutionType' || $field == 'landingPage')
@if (in_array($field, ['solutionType', 'landingPage', 'headerImageUrl', 'brandName']))
{{-- do nothing --}}
@elseif ($field == 'testMode' || $field == 'developerMode')
{{-- Former::checkbox($gateway->id.'_'.$field)->label(Utils::toSpaceCase($field))->text('Enable') --}}

View File

@ -1,216 +0,0 @@
@extends('master')
@section('head')
<link href="{{ asset('vendor/bootstrap/dist/css/bootstrap.min.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('css/bootstrap.splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('css/splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('images/apple-touch-icon-114x114-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="114x114">
<link href="{{ asset('images/apple-touch-icon-72x72-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="72x72">
<link href="{{ asset('images/apple-touch-icon-57x57-precomposed.png') }}" rel="apple-touch-icon-precomposed">
@stop
@section('body')
{{ Form::open(array('url' => 'get_started', 'id' => 'startForm')) }}
{{ Form::hidden('guest_key') }}
{{ Form::close() }}
<script>
$(document).ready(function () {
if (isStorageSupported()) {
$('[name="guest_key"]').val(localStorage.getItem('guest_key'));
}
$("#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>');
}
};
function isStorageSupported() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
}
function getStarted() {
$('#startForm').submit();
}
</script>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="navbar-inner">
<a class="brand" href="/"><img src=
"images/invoiceninja-logo.png"></a>
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
</div>
</div>
</div>
</div>
<section class="hero4" data-speed="2" data-type="background">
<div class="container">
<div class="caption">
<h1>Contact us
</h1>
</div>
</div>
</section>
<section class="about contact">
<div class="container">
<div id="contact_form" class="row">
@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 class="row">
<div class="col-md-7">
<h2>Questions, special requests, or just want to say hi?</h2>
<p>Fill in the form below and we'll get back to you as soon as possible. Hope to hear from you!</p>
{{ Former::open('contact')->addClass('feedbackForm') }}
<div class="form-group">
<input type="text" class="form-control" id="name" name="name" placeholder="Name">
<span class="help-block" style="display: none;">Please enter your name.</span>
</div>
<div class="form-group">
<input type="email" class="form-control" id="email" name="email" placeholder="Email Address">
<span class="help-block" style="display: none;">Please enter a valid e-mail address.</span>
</div>
<div class="form-group">
<textarea rows="10" cols="100" class="form-control" id="message" name="message" placeholder="Message"></textarea>
<span class="help-block" style="display: none;">Please enter a message.</span>
</div>
<div class="row">
<div class="col-md-5">
<button type="submit" id="feedbackSubmit" class="btn btn-primary btn-lg">Send Message <span class="glyphicon glyphicon-send"></span></button>
</div>
</div>
{{ Former::close() }}
</div>
<div class="col-md-4 col-md-offset-1 address">
<h2>Other ways to reach us</h2>
<p><span class="glyphicon glyphicon-send"></span><a href="mailto:contact@invoiceninja.com">contact@invoiceninja.com</a></p>
<p><span class="glyphicon glyphicon-earphone"></span>+1-800-763-1948</p>
<p><span class="github"></span><div style="padding-top:10px"> &nbsp;&nbsp;<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="getStarted()">Invoice Now <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
<footer>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="social">
<!--
<a href="http://twitter.com/eas_id"><span class=
"socicon">c</span></a>
-->
<a href=
"http://facebook.com/invoiceninja" target="_blank"><span class=
"socicon">b</span></a> <a href=
"http://twitter.com/invoiceninja" target="_blank"><span class=
"socicon">a</span></a>
<p>Copyright © 2014 InvoiceNinja. All rights reserved.</p>
</div>
<div class="navbar-inner">
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
<!--
<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>
</footer><script src="{{ asset('/js/retina-1.1.0.min.js') }}" type="text/javascript"></script>
@stop

View File

@ -11,7 +11,7 @@
{{ $totalIncome }}
</div>
<div class="in-thin">
in total revenue
{{ trans('texts.in_total_revenue') }}
</div>
</div>
</div>
@ -24,7 +24,7 @@
{{ $billedClients }}
</div>
<div class="in-thin">
{{ Utils::pluralize('billed client', $billedClients) }}
{{ Utils::pluralize('billed_client', $billedClients) }}
</div>
</div>
</div>
@ -52,7 +52,7 @@
<div class="panel panel-default dashboard" style="min-height:320px">
<div class="panel-heading" style="background-color:#0b4d78">
<h3 class="panel-title in-bold-white">
<i class="glyphicon glyphicon-exclamation-sign"></i> Notifications
<i class="glyphicon glyphicon-exclamation-sign"></i> {{ trans('texts.notifications') }}
</h3>
</div>
<ul class="panel-body list-group">
@ -69,16 +69,16 @@
<div class="panel panel-default dashboard" style="min-height:320px">
<div class="panel-heading" style="background-color:#e37329">
<h3 class="panel-title in-bold-white">
<i class="glyphicon glyphicon-time"></i> Invoices Past Due
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.invoices_past_due') }}
</h3>
</div>
<div class="panel-body">
<table class="table table-striped">
<thead>
<th>Invoice #</th>
<th>Client</th>
<th>Due date</th>
<th>Balance due</th>
<th>{{ trans('texts.invoice_number_short') }}</th>
<th>{{ trans('texts.client') }}</th>
<th>{{ trans('texts.due_date') }}</th>
<th>{{ trans('texts.balance_due') }}</th>
</thead>
<tbody>
@foreach ($pastDue as $invoice)
@ -101,16 +101,16 @@
<div class="panel panel-default dashboard" style="min-height:320px;">
<div class="panel-heading" style="margin:0;">
<h3 class="panel-title">
<i class="glyphicon glyphicon-time"></i> Upcoming invoices
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.upcoming_invoices') }}
</h3>
</div>
<div class="panel-body">
<table class="table table-striped">
<thead>
<th>Invoice #</th>
<th>Client</th>
<th>Due date</th>
<th>Balance due</th>
<th>{{ trans('texts.invoice_number_short') }}</th>
<th>{{ trans('texts.client') }}</th>
<th>{{ trans('texts.due_date') }}</th>
<th>{{ trans('texts.balance_due') }}</th>
</thead>
<tbody>
@foreach ($upcoming as $invoice)
@ -129,12 +129,12 @@
<div class="col-md-3">
<div class="active-clients">
<div class="in-bold in-white" style="font-size:42px">{{ $activeClients }}</div>
<div class="in-thin in-white">{{ Utils::pluralize('active client', $activeClients) }}</div>
<div class="in-thin in-white">{{ Utils::pluralize('active_client', $activeClients) }}</div>
</div>
</div>
<div class="col-md-3">
<div class="average-invoice">
<div><b>Average invoice</b></div>
<div><b>{{ trans('texts.average_invoice') }}</b></div>
<div class="in-bold in-white" style="font-size:42px">{{ $invoiceAvg }}</div>
</div>

View File

@ -60,6 +60,19 @@
var currency = currencyMap[currency_id];
return accounting.formatMoney(value, hide_symbol ? '' : currency.symbol, currency.precision, currency.thousand_separator, currency.decimal_separator);
}
/* Set the defaults for DataTables initialisation */
$.extend( true, $.fn.dataTable.defaults, {
"sDom": "t<'row-fluid'<'span6'i><'span6'p>>",
"sPaginationType": "bootstrap",
"bInfo": true,
"oLanguage": {
'sEmptyTable': "{{ trans('texts.empty_table') }}",
'sLengthMenu': '_MENU_',
'sSearch': ''
}
} );
</script>
@stop
@ -84,7 +97,7 @@
<div class="collapse navbar-collapse" id="navbar-collapse-1">
<ul class="nav navbar-nav" style="font-weight: bold">
{{ HTML::nav_link('dashboard', 'Dashboard') }}
{{ HTML::nav_link('dashboard', 'dashboard') }}
{{ HTML::menu_link('client') }}
{{ HTML::menu_link('invoice') }}
{{ HTML::menu_link('payment') }}
@ -94,6 +107,22 @@
<div class="navbar-form navbar-right">
@if (Auth::check() && !Auth::user()->registered)
{{ Button::sm_success_primary('Sign up', array('id' => 'signUpButton', 'data-toggle'=>'modal', 'data-target'=>'#signUpModal')) }} &nbsp;
@if (Auth::check() && Auth::user()->showSignUpPopOver())
<button id="signUpPopOver" type="button" class="btn btn-default" data-toggle="popover" data-placement="bottom" data-content="Sign up to save your work" data-html="true" style="display:none">
Sign Up
</button>
<script>
$(function() {
$('#signUpPopOver').show().popover('show').hide();
$('body').click(function() {
$('#signUpPopOver').popover('hide');
});
});
</script>
@endif
@endif
@if (Auth::check())
@ -103,7 +132,7 @@
@if (Auth::check() && Auth::user()->registered)
{{ Auth::user()->getFullName() }}
@else
My Company
Guest
@endif
</span>
<span class="caret"></span>
@ -124,16 +153,16 @@
<form class="navbar-form navbar-right" role="search">
<div class="form-group">
<input type="text" id="search" class="form-control" placeholder="Search">
<input type="text" id="search" class="form-control" placeholder="{{ trans('texts.search') }}">
</div>
</form>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">History <b class="caret"></b></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ trans('texts.history') }} <b class="caret"></b></a>
<ul class="dropdown-menu">
@if (count(Session::get(RECENTLY_VIEWED)) == 0)
<li><a href="#">No items</a></li>
<li><a href="#">{{ trans('texts.no_items') }}</a></li>
@else
@foreach (Session::get(RECENTLY_VIEWED) as $link)
<li><a href="{{ $link->url }}">{{ $link->name }}</a></li>
@ -164,6 +193,10 @@
{{ HTML::breadcrumbs() }}
@endif
@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
@ -291,7 +324,7 @@
@endif
@if ($_SERVER['SERVER_NAME'] != 'www.invoiceninja.com')
<div class="container">Powered by <a href="https://www.invoiceninja.com/" target="_blank">InvoiceNinja.com</a></div>
<div class="container">{{ trans('texts.powered_by') }} <a href="https://www.invoiceninja.com/" target="_blank">InvoiceNinja.com</a></div>
@endif
<p>&nbsp;</p>
@ -368,10 +401,10 @@
$.ajax({
type: 'POST',
url: '{{ URL::to('signup/submit') }}',
data: 'new_email=' + $('form.signUpForm #new_email').val() +
'&new_password=' + $('form.signUpForm #new_password').val() +
'&new_first_name=' + $('form.signUpForm #new_first_name').val() +
'&new_last_name=' + $('form.signUpForm #new_last_name').val(),
data: 'new_email=' + encodeURIComponent($('form.signUpForm #new_email').val()) +
'&new_password=' + encodeURIComponent($('form.signUpForm #new_password').val()) +
'&new_first_name=' + encodeURIComponent($('form.signUpForm #new_first_name').val()) +
'&new_last_name=' + encodeURIComponent($('form.signUpForm #new_last_name').val()),
success: function(result) {
if (result) {
localStorage.setItem('guest_key', '');
@ -472,4 +505,5 @@
</script>
@stop

View File

@ -20,7 +20,7 @@
'client' => 'required',
'email' => 'required',
'product_key' => 'max:20',
)); }}
)) }}
<input type="submit" style="display:none" name="submitButton" id="submitButton">
@ -70,7 +70,7 @@
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'due_date\')"></i>') }}
</div>
<div data-bind="visible: is_recurring" style="display: none">
{{ Former::select('frequency_id')->label('How often')->options($frequencies)->data_bind("value: frequency_id") }}
{{ Former::select('frequency_id')->options($frequencies)->data_bind("value: frequency_id") }}
{{ Former::text('start_date')->data_bind("datePicker: start_date, valueUpdate: 'afterkeydown'")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'start_date\')"></i>') }}
{{ Former::text('end_date')->data_bind("datePicker: end_date, valueUpdate: 'afterkeydown'")
@ -82,7 +82,7 @@
</div>
@else
<div data-bind="visible: invoice_status_id() < CONSTS.INVOICE_STATUS_SENT">
{{ Former::checkbox('recurring')->text('Enable &nbsp;&nbsp; <a href="#" onclick="showLearnMore()"><i class="glyphicon glyphicon-question-sign"></i> Learn more</a>')->data_bind("checked: is_recurring")
{{ 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")
->inlineHelp($invoice && $invoice->last_sent_date ? 'Last invoice sent ' . Utils::dateToString($invoice->last_sent_date) : '') }}
</div>
@endif
@ -90,15 +90,15 @@
</div>
<div class="col-md-4" id="col_2">
{{ Former::text('invoice_number')->label('Invoice #')->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") }}
{{ Former::text('po_number')->label('PO #')->data_bind("value: po_number, valueUpdate: 'afterkeydown'") }}
{{ Former::text('invoice_number')->label(trans('texts.invoice_number_short'))->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") }}
{{ Former::text('po_number')->label(trans('texts.po_number_short'))->data_bind("value: po_number, valueUpdate: 'afterkeydown'") }}
{{ Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'")->append('%') }}
{{-- Former::select('currency_id')->label('Currency')->addOption('', '')->fromQuery($currencies, 'name', 'id')->data_bind("value: currency_id") --}}
{{-- Former::select('currency_id')->addOption('', '')->fromQuery($currencies, 'name', 'id')->data_bind("value: currency_id") --}}
<div class="form-group" style="margin-bottom: 8px">
<label for="recurring" class="control-label col-lg-4 col-sm-4">Taxes</label>
<label for="recurring" class="control-label col-lg-4 col-sm-4">{{ trans('texts.taxes') }}</label>
<div class="col-lg-8 col-sm-8" style="padding-top: 7px">
<a href="#" data-bind="click: $root.showTaxesForm"><i class="glyphicon glyphicon-list-alt"></i> Manage rates</a>
<a href="#" data-bind="click: $root.showTaxesForm"><i class="glyphicon glyphicon-list-alt"></i> {{ trans('texts.manage_rates') }}</a>
</div>
</div>
@ -113,12 +113,12 @@
<thead>
<tr>
<th style="min-width:32px;" class="hide-border"></th>
<th style="min-width:160px">Item</th>
<th style="width:100%">Description</th>
<th style="min-width:120px">Unit Cost</th>
<th style="min-width:120px">Quantity</th>
<th style="min-width:120px;display:none;" data-bind="visible: $root.invoice_item_taxes.show">Tax</th>
<th style="min-width:120px;">Line&nbsp;Total</th>
<th style="min-width:160px">{{ trans('texts.item') }}</th>
<th style="width:100%">{{ trans('texts.description') }}</th>
<th style="min-width:120px">{{ trans('texts.unit_cost') }}</th>
<th style="min-width:120px">{{ trans('texts.quantity') }}</th>
<th style="min-width:120px;display:none;" data-bind="visible: $root.invoice_item_taxes.show">{{ trans('texts.tax') }}</th>
<th style="min-width:120px;">{{ trans('texts.line_total') }}</th>
<th style="min-width:32px;" class="hide-border"></th>
</tr>
</thead>
@ -128,7 +128,7 @@
<i style="display:none" data-bind="visible: actionsVisible() &amp;&amp; $parent.invoice_items().length > 1" class="fa fa-sort"></i>
</td>
<td>
{{ Former::text('product_key')->useDatalist(Product::getProductKeys($products), 'key')->onkeyup('onItemChange()')
{{ Former::text('product_key')->useDatalist(Product::getProductKeys($products), 'product_key')->onkeyup('onItemChange()')
->raw()->data_bind("value: product_key, valueUpdate: 'afterkeydown'")->addClass('datalist') }}
</td>
<td>
@ -157,41 +157,41 @@
<td colspan="2" rowspan="5">
<br/>
{{ Former::textarea('public_notes')->data_bind("value: wrapped_notes, valueUpdate: 'afterkeydown'")
->label(false)->placeholder('Note to client')->style('width: 520px; resize: none') }}
->label(false)->placeholder(trans('texts.note_to_client'))->style('width: 520px; resize: none') }}
{{ Former::textarea('terms')->data_bind("value: wrapped_terms, valueUpdate: 'afterkeydown'")
->label(false)->placeholder('Invoice terms')->style('width: 520px; resize: none')
->label(false)->placeholder(trans('texts.invoice_terms'))->style('width: 520px; resize: none')
->addGroupClass('less-space-bottom') }}
<label class="checkbox" style="width: 200px">
<input type="checkbox" style="width: 24px" data-bind="checked: set_default_terms"/>Save as default terms
<input type="checkbox" style="width: 24px" data-bind="checked: set_default_terms"/>{{ trans('texts.save_as_default_terms') }}
</label>
</td>
<td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="2">Subtotal</td>
<td colspan="2">{{ trans('texts.subtotal') }}</td>
<td style="text-align: right"><span data-bind="text: totals.subtotal"/></td>
</tr>
<tr style="display:none" data-bind="visible: discount() > 0">
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="2">Discount</td>
<td colspan="2">{{ trans('texts.discount') }}</td>
<td style="text-align: right"><span data-bind="text: totals.discounted"/></td>
</tr>
<tr style="display:none" data-bind="visible: $root.invoice_taxes.show">
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td>Tax</td>
<td>{{ trans('texts.tax') }}</td>
<td style="min-width:120px"><select class="form-control" style="width:100%" data-bind="value: tax, options: $root.tax_rates, optionsText: 'displayName'"></select></td>
<td style="text-align: right"><span data-bind="text: totals.taxAmount"/></td>
</tr>
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="2">Paid to Date</td>
<td colspan="2">{{ trans('texts.paid_to_date') }}</td>
<td style="text-align: right" data-bind="text: totals.paidToDate"></td>
</tr>
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
<td colspan="2"><b>Balance Due</b></td>
<td colspan="2"><b>{{ trans('texts.balance_due') }}</b></td>
<td style="text-align: right"><span data-bind="text: totals.total"/></td>
</tr>
</tfoot>
@ -210,30 +210,29 @@
{{ Former::select('invoice_design_id')->label('Design')->style('display:inline;width:120px')->raw()
{{ Former::select('invoice_design_id')->style('display:inline;width:120px')->raw()
->fromQuery($invoiceDesigns, 'name', 'id')->data_bind("value: invoice_design_id") }}
{{ Button::primary('Download PDF', array('onclick' => 'onDownloadClick()'))->append_with_icon('download-alt'); }}
{{ Button::primary(trans('texts.download_pdf'), array('onclick' => 'onDownloadClick()'))->append_with_icon('download-alt'); }}
@if (!$invoice || (!$invoice->trashed() && !$invoice->client->trashed()))
@if ($invoice)
<div id="primaryActions" style="text-align:left" class="btn-group">
<button class="btn-success btn" type="button">Save Invoice</button>
<button class="btn-success btn" type="button">{{ trans('texts.save_invoice') }}</button>
<button class="btn-success btn dropdown-toggle" type="button" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="javascript:onSaveClick()" id="saveButton">Save Invoice</a></li>
<li><a href="javascript:onCloneClick()">Clone Invoice</a></li>
<li><a href="javascript:onSaveClick()" id="saveButton">{{ trans('texts.save_invoice') }}</a></li>
<li><a href="javascript:onCloneClick()">{{ trans('texts.clone_invoice') }}</a></li>
<li class="divider"></li>
<li><a href="javascript:onArchiveClick()">Archive Invoice</a></li>
<li><a href="javascript:onDeleteClick()">Delete Invoice</a></li>
<li><a href="javascript:onArchiveClick()">{{ trans('texts.archive_invoice') }}</a></li>
<li><a href="javascript:onDeleteClick()">{{ trans('texts.delete_invoice') }}</a></li>
</ul>
</div>
{{-- DropdownButton::normal('Download PDF',
Navigation::links(
array(
@ -257,13 +256,13 @@
)
, array('id'=>'primaryActions', 'style'=>'text-align:left', 'data-bind'=>'css: $root.enable.save'))->split(); --}}
@else
{{ Button::success('Save Invoice', array('id' => 'saveButton', 'onclick' => 'onSaveClick()')) }}
{{ Button::success(trans('texts.save_invoice'), array('id' => 'saveButton', 'onclick' => 'onSaveClick()')) }}
@endif
{{ Button::normal('Email Invoice', array('id' => 'email_button', 'onclick' => 'onEmailClick()'))->append_with_icon('send'); }}
{{ Button::normal(trans('texts.email_invoice'), array('id' => 'email_button', 'onclick' => 'onEmailClick()'))->append_with_icon('send'); }}
@if ($invoice)
{{ Button::primary('Enter Payment', array('onclick' => 'onPaymentClick()'))->append_with_icon('usd'); }}
{{ Button::primary(trans('texts.enter_payment'), array('onclick' => 'onPaymentClick()'))->append_with_icon('usd'); }}
@endif
@endif
@ -281,33 +280,33 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="clientModalLabel">Client</h4>
<h4 class="modal-title" id="clientModalLabel">{{ trans('texts.client') }}</h4>
</div>
<div class="container" style="width: 100%">
<div style="background-color: #fff" class="row" data-bind="with: client" onkeypress="clientModalEnterClick(event)">
<div class="col-md-6" style="margin-left:0px;margin-right:0px" >
{{ Former::legend('Organization') }}
{{ Former::legend('organization') }}
{{ Former::text('name')->data_bind("value: name, valueUpdate: 'afterkeydown', attr { placeholder: name.placeholder }") }}
{{ Former::text('website')->data_bind("value: website, valueUpdate: 'afterkeydown'") }}
{{ Former::text('work_phone')->data_bind("value: work_phone, valueUpdate: 'afterkeydown'")->label('Phone') }}
{{ Former::text('work_phone')->data_bind("value: work_phone, valueUpdate: 'afterkeydown'") }}
{{ Former::legend('Address') }}
{{ Former::text('address1')->label('Street')->data_bind("value: address1, valueUpdate: 'afterkeydown'") }}
{{ Former::text('address2')->label('Apt/Suite')->data_bind("value: address2, valueUpdate: 'afterkeydown'") }}
{{ Former::legend('address') }}
{{ Former::text('address1')->data_bind("value: address1, valueUpdate: 'afterkeydown'") }}
{{ Former::text('address2')->data_bind("value: address2, valueUpdate: 'afterkeydown'") }}
{{ Former::text('city')->data_bind("value: city, valueUpdate: 'afterkeydown'") }}
{{ Former::text('state')->label('State/Province')->data_bind("value: state, valueUpdate: 'afterkeydown'") }}
{{ Former::text('state')->data_bind("value: state, valueUpdate: 'afterkeydown'") }}
{{ Former::text('postal_code')->data_bind("value: postal_code, valueUpdate: 'afterkeydown'") }}
{{ Former::select('country_id')->addOption('','')->label('Country')->addGroupClass('country_select')
{{ Former::select('country_id')->addOption('','')->addGroupClass('country_select')
->fromQuery($countries, 'name', 'id')->data_bind("dropdown: country_id") }}
</div>
<div class="col-md-6" style="margin-left:0px;margin-right:0px" >
{{ Former::legend('Contacts') }}
{{ Former::legend('contacts') }}
<div data-bind='template: { foreach: contacts,
beforeRemove: hideContact,
afterAdd: showContact }'>
@ -320,23 +319,23 @@
<div class="form-group">
<div class="col-lg-8 col-lg-offset-4">
<span class="redlink bold" data-bind="visible: $parent.contacts().length > 1">
{{ link_to('#', 'Remove contact -', array('data-bind'=>'click: $parent.removeContact')) }}
{{ link_to('#', trans('texts.remove_contact').' -', array('data-bind'=>'click: $parent.removeContact')) }}
</span>
<span data-bind="visible: $index() === ($parent.contacts().length - 1)" class="pull-right greenlink bold">
{{ link_to('#', 'Add contact +', array('data-bind'=>'click: $parent.addContact')) }}
{{ link_to('#', trans('texts.add_contact').' +', array('data-bind'=>'click: $parent.addContact')) }}
</span>
</div>
</div>
</div>
{{ Former::legend('Additional Info') }}
{{ Former::legend('additional_info') }}
{{ Former::select('payment_terms')->addOption('','0')->data_bind('value: payment_terms')
->fromQuery($paymentTerms, 'name', 'num_days') }}
{{ Former::select('currency_id')->addOption('','')->label('Currency')->data_bind('value: currency_id')
{{ Former::select('currency_id')->addOption('','')->data_bind('value: currency_id')
->fromQuery($currencies, 'name', 'id') }}
{{ Former::select('size_id')->addOption('','')->label('Size')->data_bind('value: size_id')
{{ Former::select('size_id')->addOption('','')->data_bind('value: size_id')
->fromQuery($sizes, 'name', 'id') }}
{{ Former::select('industry_id')->addOption('','')->label('Industry')->data_bind('value: industry_id')
{{ Former::select('industry_id')->addOption('','')->data_bind('value: industry_id')
->fromQuery($industries, 'name', 'id') }}
{{ Former::textarea('private_notes')->data_bind('value: private_notes') }}
@ -346,9 +345,9 @@
</div>
<div class="modal-footer" style="margin-top: 0px">
<span class="error-block" id="emailError" style="display:none;float:left;font-weight:bold">Please provide a valid email address.</span><span>&nbsp;</span>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button id="clientDoneButton" type="button" class="btn btn-primary" data-bind="click: $root.clientFormComplete">Done</button>
<span class="error-block" id="emailError" style="display:none;float:left;font-weight:bold">{{ trans('texts.provide_email') }}</span><span>&nbsp;</span>
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
<button id="clientDoneButton" type="button" class="btn btn-primary" data-bind="click: $root.clientFormComplete">{{ trans('texts.done') }}</button>
</div>
</div>
@ -360,7 +359,7 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="taxModalLabel">Tax Rates</h4>
<h4 class="modal-title" id="taxModalLabel">{{ trans('texts.tax_rates') }}</h4>
</div>
<div style="background-color: #fff" onkeypress="taxModalEnterClick(event)">
@ -368,8 +367,8 @@
<thead>
<tr>
<th class="hide-border"></th>
<th class="hide-border">Name</th>
<th class="hide-border">Rate</th>
<th class="hide-border">{{ trans('texts.name') }}</th>
<th class="hide-border">{{ trans('texts.rate') }}</th>
<th class="hide-border"></th>
</tr>
</thead>
@ -390,9 +389,9 @@
</table>
&nbsp;
{{ Former::checkbox('invoice_taxes')->text('Enable specifying an <b>invoice tax</b>')
->label('Settings')->data_bind('checked: $root.invoice_taxes, enable: $root.tax_rates().length > 1') }}
{{ Former::checkbox('invoice_item_taxes')->text('Enable specifying <b>line item taxes</b>')
{{ Former::checkbox('invoice_taxes')->text(trans('texts.enable_invoice_tax'))
->label(trans('texts.settings'))->data_bind('checked: $root.invoice_taxes, enable: $root.tax_rates().length > 1') }}
{{ Former::checkbox('invoice_item_taxes')->text(trans('texts.enable_line_item_tax'))
->label('&nbsp;')->data_bind('checked: $root.invoice_item_taxes, enable: $root.tax_rates().length > 1') }}
<br/>
@ -401,7 +400,7 @@
<div class="modal-footer" style="margin-top: 0px">
<!-- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> -->
<button type="button" class="btn btn-primary" data-bind="click: $root.taxFormComplete">Done</button>
<button type="button" class="btn btn-primary" data-bind="click: $root.taxFormComplete">{{ trans('texts.done') }}</button>
</div>
</div>
@ -413,22 +412,11 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="recurringModalLabel">Recurring Invoices</h4>
<h4 class="modal-title" id="recurringModalLabel">{{ trans('texts.recurring_invoices') }}</h4>
</div>
<div style="background-color: #fff; padding-left: 16px; padding-right: 16px">
&nbsp;
<p>Automatically send clients the same invoices weekly, bi-monthly, monthly, quarterly or annually. </p>
<p>Use :MONTH, :QUARTER or :YEAR for dynamic dates. Basic math works as well, for example :MONTH-1.</p>
<p>Examples of dynamic invoice variables:</p>
<ul>
<li>"Gym membership for the month of :MONTH" => "Gym membership for the month of July"</li>
<li>":YEAR+1 yearly subscription" => "2015 Yearly Subscription"</li>
<li>"Retainer payment for :QUARTER+1" => "Retainer payment for Q2"</li>
</ul>
&nbsp;
&nbsp; {{ trans('texts.recurring_help') }} &nbsp;
</div>
<div class="modal-footer" style="margin-top: 0px">
@ -616,14 +604,15 @@
var isRefreshing = false;
var needsRefresh = false;
function getPDFString() {
var invoice = createInvoiceModel();
var doc = generatePDF(invoice);
var doc = generatePDF(invoice, invoiceLabels);
if (!doc) return;
return doc.output('datauristring');
}
function refreshPDF() {
if (isFirefox || (isChrome && !isChromium)) {
if ({{ Auth::user()->force_pdfjs ? 'false' : 'true' }} && (isFirefox || (isChrome && !isChromium))) {
var string = getPDFString();
$('#theFrame').attr('src', string).show();
} else {
@ -956,7 +945,7 @@
self.clientLinkText = ko.computed(function() {
if (self.invoice().client().public_id())
{
return 'Edit client details';
return "{{ trans('texts.edit_client_details') }}";
}
else
{
@ -966,7 +955,7 @@
}
else
{
return 'Create new client';
return "{{ trans('texts.create_new_client') }}";
}
}
});
@ -980,7 +969,7 @@
self.discount = ko.observable('');
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', $account->invoice_terms) }}', 300));
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.public_notes = ko.observable('');
self.po_number = ko.observable('');
@ -1445,10 +1434,10 @@
var products = {{ $products }};
var clients = {{ $clients }};
var invoiceLabels = {{ json_encode($invoiceLabels) }};
var clientMap = {};
var $clientSelect = $('select#client');
for (var i=0; i<clients.length; i++) {
var client = clients[i];
for (var j=0; j<client.contacts.length; j++) {

View File

@ -29,6 +29,19 @@
$(function() {
window.invoice = {{ $invoice->toJson() }};
invoice.imageLogo1 = "{{ HTML::image_data('images/report_logo1.jpg') }}";
invoice.imageLogoWidth1 =120;
invoice.imageLogoHeight1 = 40
invoice.imageLogo2 = "{{ HTML::image_data('images/report_logo2.jpg') }}";
invoice.imageLogoWidth2 =325/2;
invoice.imageLogoHeight2 = 81/2;
invoice.imageLogo3 = "{{ HTML::image_data('images/report_logo3.jpg') }}";
invoice.imageLogoWidth3 =325/2;
invoice.imageLogoHeight3 = 81/2;
@if (file_exists($invoice->client->account->getLogoPath()))
invoice.image = "{{ HTML::image_data($invoice->client->account->getLogoPath()) }}";
invoice.imageWidth = {{ $invoice->client->account->getLogoWidth() }};
@ -60,6 +73,8 @@
}
});
var invoiceLabels = {{ json_encode($invoiceLabels) }};
function onDownloadClick() {
var doc = generatePDF(invoice);
doc.save('Invoice-' + invoice.invoice_number + '.pdf');

View File

@ -8,23 +8,23 @@
{{ Former::text('id') }}
</div>
{{ DropdownButton::normal('Archive',
{{ DropdownButton::normal(trans('texts.archive'),
Navigation::links(
array(
array('Archive '.ucwords($entityType), "javascript:submitForm('archive')"),
array('Delete '.ucwords($entityType), "javascript:submitForm('delete')"),
array(trans('texts.archive_'.$entityType), "javascript:submitForm('archive')"),
array(trans('texts.delete_'.$entityType), "javascript:submitForm('delete')"),
)
)
, array('id'=>'archive'))->split(); }}
&nbsp;<label for="trashed" style="font-weight:normal; margin-left: 10px;">
<input id="trashed" type="checkbox" onclick="setTrashVisible()"
{{ Session::get('show_trash') ? 'checked' : ''}}/> Show archived/deleted {{ $entityType }}s
{{ Session::get('show_trash') ? 'checked' : ''}}/> {{ trans('texts.show_archived_deleted')}} {{ strtolower(trans('texts.'.$entityType.'s')) }}
</label>
<div id="top_right_buttons" class="pull-right">
<input id="tableFilter" type="text" style="width:140px;margin-right:17px" class="form-control pull-left" placeholder="Filter"/>
{{ Button::success_link(URL::to($entityType . 's/create'), 'New ' . Utils::getEntityName($entityType), array('class' => 'pull-right'))->append_with_icon('plus-sign'); }}
<input id="tableFilter" type="text" style="width:140px;margin-right:17px" class="form-control pull-left" placeholder="{{ trans('texts.filter') }}"/>
{{ Button::success_link(URL::to($entityType . 's/create'), trans("texts.new_$entityType"), array('class' => 'pull-right'))->append_with_icon('plus-sign'); }}
</div>

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<title>Invoice Ninja {{ isset($title) ? $title : '' }}</title>
<title>Invoice Ninja {{ isset($title) ? $title : ' - Free Online Invoicing' }}</title>
<link rel="canonical" href="https://www.invoiceninja.com"></link>
<link href="{{ asset('favicon.ico') }}" rel="icon" type="image/x-icon">
@ -15,18 +15,19 @@
<meta property="og:site_name" content="Invoice Ninja"></meta>
<meta property="og:url" content="https://www.invoiceninja.com"></meta>
<meta property="og:title" content="Invoice Ninja"></meta>
<meta property="og:image" content="https://www.invoiceninja.com/images/facebook.jpg"></meta>
<meta property="og:image" content="https://www.invoiceninja.com/images/social.jpg"></meta>
<meta property="og:description" content="Simple, Intuitive Invoicing."></meta>
<meta name="keywords" content="Invoice Ninja"></meta>
<script src="{{ asset('vendor/jquery/jquery.min.js') }}" type="text/javascript"></script>
<script type="text/javascript">
window.onerror = function(e) {
try {
$.ajax({
type: 'GET',
url: '{{ URL::to('log_error') }}',
data: 'error='+e+'&url='+window.location
data: 'error='+encodeURIComponent(e)+'&url='+encodeURIComponent(window.location)
});
} catch(err) {}
return false;
@ -45,22 +46,16 @@
<body>
@if (App::environment() == ENV_PRODUCTION)
@if (App::environment() == ENV_PRODUCTION && isset($_ENV['ANALYTICS_KEY']) && $_ENV['ANALYTICS_KEY'])
<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)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-46031341-1');
ga('create', '{{ $_ENV['ANALYTICS_KEY'] }}');
ga('send', 'pageview');
</script>
@else
<style>
.navbar {
background-color: #006600 !important;
}
</style>
@endif
@yield('body')

View File

@ -0,0 +1,86 @@
@extends('public.header')
@section('content')
<section class="hero3" data-speed="2" data-type="background">
<div class="container">
<div class="caption">
<h1>WHY INVOICE NINJA?
</h1>
</div>
</div>
</section>
<section class="about center">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Open Source Platform</h2>
<p>Free yourself from online invoicing platforms with high monthly fees and limited functionality. Being <a href="https://github.com/hillelcoren/invoice-ninja" target="_blank">open source</a> allows us fast app development, security audits by the open-course community, and we can keep it <strong>FREE!</strong></p>
</div>
</div>
</div>
</section>
<section class="about white-bg">
<div class="container">
<div class="row">
<div class="col-md-5">
<div class="screendump">
<img src="images/about1.jpg">
</div>
</div>
<div class="col-md-7">
<h2>Live PDF Creation</h2>
<p><strong>Look professional from day #1.</strong> Select one of our beautiful invoice templates to suit your company identity, switch between designs in real time to preview invoices & email them to clients with one click. The live preview PDF function was designed for an efficient and hassle-free experience, and its awesome!
</p>
</div>
</div>
</div>
</section>
<section class="about">
<div class="container">
<div class="row">
<div class="col-md-7">
<h2>Online Payments</h2>
<p><strong>Authorize.net, Beanstream, PayPal?</strong> InvoiceNinja supports the most popular online payment gateways! If you need help integrating a third party gateway we dont yet support, please contact us! Were happy to help! If you need assistance of want to learn more about online payment solutions, contact us!</p>
</div>
<div class="col-md-5">
<div class="screendump">
<img src="images/about2.jpg">
</div>
</div>
</div>
</div>
</section>
<!--
<section class="about center white-bg">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>Info about the company/story</h2>
<p>Donec id elit non mi porta gravida at eget metus.
Fusce dapibus, tellus ac cursus commodo, tortor mauris
condimentum nibh, ut fermentum massa justo sit amet
risus. Etiam porta sem malesuada magna mollis euismod.
Donec sed odio dui.</p>
</div>
</div>
</div>
</section>
-->
<section class="upper-footer">
<div class="container">
<div class="row">
<div class="col-md-3 center-block">
<a href="#">
<div class="cta">
<h2 onclick="return getStarted()">Invoice Now <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
@stop

View File

@ -0,0 +1,138 @@
@extends('public.header')
@section('content')
<script>
$(document).ready(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="hero4" data-speed="2" data-type="background">
<div class="container">
<div class="caption">
<h1>Contact us
</h1>
</div>
</div>
</section>
<section class="about contact">
<div class="container">
<div id="contact_form" class="row">
@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 class="row">
<div class="col-md-7">
<h2>Questions, special requests, or just want to say hi?</h2>
<p>Fill in the form below and we'll get back to you as soon as possible. Hope to hear from you!</p>
{{ Form::open(['url' => 'contact', 'class' => 'feedbackForm']) }}
<div class="form-group">
<input type="text" class="form-control" id="name" name="name" placeholder="Name">
<span class="help-block" style="display: none;">Please enter your name.</span>
</div>
<div class="form-group">
<input type="email" class="form-control" id="email" name="email" placeholder="Email Address">
<span class="help-block" style="display: none;">Please enter a valid e-mail address.</span>
</div>
<div class="form-group">
<textarea rows="10" cols="100" class="form-control" id="message" name="message" placeholder="Message"></textarea>
<span class="help-block" style="display: none;">Please enter a message.</span>
</div>
<div class="row">
<div class="col-md-5">
<button type="submit" id="feedbackSubmit" class="btn btn-primary btn-lg">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>Other ways to reach us</h2>
<p><span class="glyphicon glyphicon-send"></span><a href="mailto:contact@invoiceninja.com">contact@invoiceninja.com</a></p>
<p><span class="glyphicon glyphicon-earphone"></span>+1-800-763-1948</p>
<p><span class="github"></span><div style="padding-top:10px"> &nbsp;&nbsp;<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()">Invoice Now <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
@stop

View File

@ -0,0 +1,141 @@
@extends('master')
@section('head')
<link href="{{ asset('vendor/bootstrap/dist/css/bootstrap.min.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('css/bootstrap.splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('css/splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('images/apple-touch-icon-114x114-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="114x114">
<link href="{{ asset('images/apple-touch-icon-72x72-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="72x72">
<link href="{{ asset('images/apple-touch-icon-57x57-precomposed.png') }}" rel="apple-touch-icon-precomposed">
@stop
@section('body')
<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>
{{ Form::open(array('url' => 'get_started', 'id' => 'startForm')) }}
{{ Form::hidden('guest_key') }}
{{ Form::close() }}
<script>
$(document).ready(function () {
if (isStorageSupported()) {
$('[name="guest_key"]').val(localStorage.getItem('guest_key'));
}
});
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>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="navbar-inner">
<a class="brand" href="/"><img src=
"images/invoiceninja-logo.png"></a>
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
</div>
</div>
</div>
@yield('content')
<footer>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="social">
<!--<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/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;
<!-- Place this tag where you want the +1 button to render. -->
<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>
<!-- Place this tag after the last +1 button tag. -->
<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>
-->
<!--<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>-->
<p>&nbsp;</p>
<p>Copyright © 2014 InvoiceNinja. All rights reserved.</p>
</div>
<div class="navbar-inner">
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
<!--
<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>
</footer><script src="{{ asset('/js/retina-1.1.0.min.js') }}" type="text/javascript"></script>
@stop

111
app/views/public/splash.blade.php Executable file
View File

@ -0,0 +1,111 @@
@extends('public.header')
@section('content')
<section class="hero background" 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>THE <span style="color:#2299c0">SIMPLE</span> &amp;
<span style="color:#edd71e">FREE</span> WAY TO INVOICE
CLIENTS</h1>
<p>It's that easy. Stop spending time on
complicated and expensive invoicing.<br>
No fuss, just get started and <span style=
"color:#2299c0">get paid.</span></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()">Invoice Now <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
<section class="features">
<div class="container">
<div class="row">
<div class="col-md-3 one">
<div class="box">
<div class="icon"><img src="{{ asset('images/icon-free.png') }}"></div>
<h2>100% FREE, ALWAYS</h2>
<p>Invoicing with no monthly fee, because you have enough bills already! Free, now and forever! Quality invoicing to build your business and get paid.</p>
</div>
</div>
<div class="col-md-3 two">
<div class="box">
<div class="icon"><img src="{{ asset('images/icon-opensource.png') }}"></div>
<h2>OPEN-SOURCE</h2>
<p>Cloud-based, super secure, and user-developed. Open source platforms are a better way to do business (and save the world). Need we say more?</p>
</div>
</div>
<div class="col-md-3 three">
<div class="box">
<div class="icon"><img src="{{ asset('images/icon-pdf.png') }}"></div>
<h2>LIVE .PDF VIEW</h2>
<p>Create beautiful email-ready .PDF invoices created instantly as you type. Our Save & send feature saves you time and impresses clients.</p>
</div>
</div>
<div class="col-md-3 four">
<div class="box">
<div class="icon"><img src="{{ asset('images/icon-payment.png') }}"></div>
<h2>ONLINE PAYMENTS</h2>
<p>PayPal? Authorize.Net? Stripe? We support many payment technologies and if you need help or advice well lend a hand (were pretty friendly).</p>
</div>
</div>
</div>
</div>
</section>
<section class="blue">
<div class="container">
<div class="row">
<div class="col-md-6">
<!--<h1>2.500 <span>sent invoices</span></h1>-->
</div>
<div class="col-md-6">
<!--<h1>$350.456 <span>billed</span></h1>-->
</div>
</div>
</div>
</section>
<section class="hero2">
<div class="container">
<div class="caption">
<h1>SIMPLE, INTUITIVE INVOICING.</h1>
</div>
</div>
</section>
<section class="upper-footer">
<div class="container">
<div class="row">
<div class="col-md-3 center-block">
<a href="#">
<div class="cta">
<h2 onclick="return getStarted()">Invoice Now <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
@stop

View File

@ -1,29 +1,6 @@
@extends('master')
@extends('public.header')
@section('head')
<link href="{{ asset('css/bootstrap.splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('css/splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('images/apple-touch-icon-114x114-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="114x114">
<link href="{{ asset('images/apple-touch-icon-72x72-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="72x72">
<link href="{{ asset('images/apple-touch-icon-57x57-precomposed.png') }}" rel="apple-touch-icon-precomposed">
@stop
@section('body')
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="navbar-inner">
<a class="brand" href="#"><img src=
"images/invoiceninja-logo.png"></a>
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
</div>
</div>
</div>
</div>
@section('content')
<section class="hero3" data-speed="2" data-type="background">
<div class="container">
@ -170,41 +147,4 @@
<p>&nbsp;</p>
<p>&nbsp;</p>
<footer>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="social">
<!--
<a href="http://twitter.com/eas_id"><span class=
"socicon">c</span></a>
-->
<a href=
"http://facebook.com/invoiceninja" target="_blank"><span class=
"socicon">b</span></a> <a href=
"http://twitter.com/invoiceninja" target="_blank"><span class=
"socicon">a</span></a>
<p>Copyright © 2014 InvoiceNinja. All rights reserved.</p>
</div>
<div class="navbar-inner">
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
<!--
<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>
</footer><script src="{{ asset('/js/retina-1.1.0.min.js') }}" type="text/javascript"></script>
@stop
@stop

View File

@ -1,196 +0,0 @@
@extends('master')
@section('head')
<link href="{{ asset('css/bootstrap.splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('css/splash.css') }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('images/apple-touch-icon-114x114-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="114x114">
<link href="{{ asset('images/apple-touch-icon-72x72-precomposed.png') }}" rel="apple-touch-icon-precomposed" sizes="72x72">
<link href="{{ asset('images/apple-touch-icon-57x57-precomposed.png') }}" rel="apple-touch-icon-precomposed">
@stop
@section('body')
{{ Form::open(array('url' => 'get_started', 'id' => 'startForm')) }}
{{ Form::hidden('guest_key') }}
{{ Form::close() }}
<script>
$(document).ready(function () {
if (isStorageSupported()) {
$('[name="guest_key"]').val(localStorage.getItem('guest_key'));
}
});
function isStorageSupported() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
}
function getStarted() {
$('#startForm').submit();
}
</script>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="navbar-inner">
<a class="brand" href="/"><img src=
"images/invoiceninja-logo.png"></a>
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
</div>
</div>
</div>
<section class="hero background" 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>THE <span style="color:#2299c0">SIMPLE</span> &amp;
<span style="color:#edd71e">FREE</span> WAY TO INVOICE
CLIENTS</h1>
<p>It's that easy. Stop spending time on
complicated and expensive invoicing.<br>
No fuss, just get started and <span style=
"color:#2299c0">get paid.</span></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="getStarted()">Invoice Now <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
<section class="features">
<div class="container">
<div class="row">
<div class="col-md-3 one">
<div class="box">
<div class="icon"><img src="images/icon-free.png"></div>
<h2>100% FREE, ALWAYS</h2>
<p>Invoicing with no monthly fee, because you have enough bills already! Free, now and forever! Quality invoicing to build your business and get paid.</p>
</div>
</div>
<div class="col-md-3 two">
<div class="box">
<div class="icon"><img src=
"images/icon-opensource.png"></div>
<h2>OPEN-SOURCE</h2>
<p>Cloud-based, super secure, and user-developed. Open source platforms are a better way to do business (and save the world). Need we say more?</p>
</div>
</div>
<div class="col-md-3 three">
<div class="box">
<div class="icon"><img src="images/icon-pdf.png"></div>
<h2>LIVE .PDF VIEW</h2>
<p>Create beautiful email-ready .PDF invoices created instantly as you type. Our Save & send feature saves you time and impresses clients.</p>
</div>
</div>
<div class="col-md-3 four">
<div class="box">
<div class="icon"><img src=
"images/icon-payment.png"></div>
<h2>ONLINE PAYMENTS</h2>
<p>PayPal? Authorize.Net? Stripe? We support many payment technologies and if you need help or advice well lend a hand (were pretty friendly).</p>
</div>
</div>
</div>
</div>
</section>
<section class="blue">
<div class="container">
<div class="row">
<div class="col-md-6">
<!--<h1>2.500 <span>sent invoices</span></h1>-->
</div>
<div class="col-md-6">
<!--<h1>$350.456 <span>billed</span></h1>-->
</div>
</div>
</div>
</section>
<section class="hero2">
<div class="container">
<div class="caption">
<h1>SIMPLE, INTUITIVE INVOICING.</h1>
</div>
</div>
</section>
<section class="upper-footer">
<div class="container">
<div class="row">
<div class="col-md-3 center-block">
<a href="#">
<div class="cta">
<h2 onclick="getStarted()">Invoice Now <span>+</span></h2>
</div>
</a>
</div>
</div>
</div>
</section>
<footer>
<div class="navbar" style="margin-bottom:0px">
<div class="container">
<div class="social">
<!--
<a href="http://twitter.com/eas_id"><span class=
"socicon">c</span></a>
-->
<a href=
"http://facebook.com/invoiceninja" target="_blank"><span class=
"socicon">b</span></a> <a href=
"http://twitter.com/invoiceninja" target="_blank"><span class=
"socicon">a</span></a>
<p>Copyright © 2014 InvoiceNinja. All rights reserved.</p>
</div>
<div class="navbar-inner">
<ul class="navbar-list">
<li>{{ link_to('about', 'About Us' ) }}</li>
<li>{{ link_to('contact', 'Contact Us' ) }}</li>
<li>{{ link_to('login', Auth::check() ? 'My Account' : 'Login' ) }}</li>
</ul>
<!--
<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>
</footer><script src="{{ asset('/js/retina-1.1.0.min.js') }}" type="text/javascript"></script>
@stop

View File

@ -13,8 +13,9 @@
"chumper/datatable": "2.x",
"omnipay/omnipay": "2.x",
"intervention/image": "dev-master",
"webpatser/laravel-countries": "dev-master",
"anahkiasen/rocketeer": "dev-develop",
"webpatser/laravel-countries": "dev-master"
},
"require-dev": {
"codeception/codeception": "dev-master"
},
"autoload": {

5171
composer.lock generated

File diff suppressed because it is too large Load Diff

BIN
public/apple-touch-icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -528,6 +528,15 @@ footer .social .socicon {
float: left;
}
div.fb_iframe_widget {
display: inline;
}
div.fb_iframe_widget > span {
vertical-align: top !important;
}
@font-face {
font-family: 'socicon';

View File

@ -616,6 +616,16 @@ color: #fff;
background-color: #08273c;
border-color: #08273c;
}
#signUpPopOver {
cursor: pointer;
}
div.fb_iframe_widget {
display: inline;
}
div.fb_iframe_widget > span {
vertical-align: top !important;
}
@media (max-width: 767px) {
.navbar-default .navbar-nav .open .dropdown-menu > li > a {
color: #ecf0f1;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/images/social.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

280
public/js/jspdf.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -39,7 +39,6 @@ cell ocupied by the width of the char in that position.
@returns {Array}
*/
var getCharWidthsArray = API.getCharWidthsArray = function(text, options){
if (!options) {
options = {}
}

View File

@ -15,7 +15,7 @@ function GetReportTemplate4(doc, invoice, layout, checkMath) {
if (invoice.image)
{
var left = layout.headerRight - invoice.imageWidth;
doc.addImage(invoice.image, 'JPEG', left, 30, invoice.imageWidth, invoice.imageHeight);
doc.addImage(invoice.image, 'JPEG', left, 30);
}
/* table header */
@ -69,6 +69,7 @@ function GetReportTemplate4(doc, invoice, layout, checkMath) {
y += displaySubtotals(doc, layout, invoice, y+20, 480) + 20;
/*
if (checkMath && NINJA.parseFloat(total).toFixed(4) != NINJA.parseFloat(invoice.amount).toFixed(4))
{
var doc = new jsPDF('p', 'pt');
@ -78,7 +79,7 @@ function GetReportTemplate4(doc, invoice, layout, checkMath) {
onerror('Failed to generate PDF ' + total + ', ' + invoice.amount );
return doc;
}
*/
doc.setDrawColor(200,200,200);
doc.setFillColor(230,230,230);
@ -92,7 +93,7 @@ function GetReportTemplate4(doc, invoice, layout, checkMath) {
doc.setFontType("bold");
doc.text(layout.footerLeft, y, 'Balance Due');
total = formatMoney(total - (invoice.amount - invoice.balance), currencyId);
total = formatMoney(invoice.balance_amount, currencyId)
var totalX = layout.headerRight - (doc.getStringUnitWidth(total) * doc.internal.getFontSize());
doc.text(totalX, y, total);
@ -182,22 +183,6 @@ function getQuarter(offset) {
return 'Q' + quarter;
}
/* Set the defaults for DataTables initialisation */
$.extend( true, $.fn.dataTable.defaults, {
"sDom": "t<'row-fluid'<'span6'i><'span6'p>>",
//"sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>",
"sPaginationType": "bootstrap",
//"bProcessing": true,
//"iDisplayLength": 50,
"bInfo": true,
"oLanguage": {
//"sLengthMenu": "_MENU_ records per page"
"sLengthMenu": "_MENU_",
"sSearch": ""
}
//"sScrollY": "500px",
} );
/* Default class modification */
$.extend( $.fn.dataTableExt.oStdClasses, {
@ -626,17 +611,17 @@ $.fn.datepicker.defaults.todayHighlight = true;
function GetPdf(invoice,checkMath,report_id){
var layout = {
accountTop: 30,
accountTop: 40,
marginLeft: 50,
marginRight: 550,
headerTop: 140,
headerTop: 150,
headerLeft: 360,
headerRight: 550,
rowHeight: 15,
tableRowHeight: 10,
footerLeft: 420,
tablePadding: 12,
tableTop: 250,
tableTop: 260,
descriptionLeft: 162,
unitCostRight: 410,
qtyRight: 480,
@ -691,7 +676,6 @@ function GetReportTemplate1(doc, invoice, layout, checkMath)
var account = invoice.account;
var currencyId = client.currency_id;
layout.headerTop = 140;
layout.headerRight = 550;
layout.rowHeight = 15;
@ -700,7 +684,7 @@ function GetReportTemplate1(doc, invoice, layout, checkMath)
if (invoice.image)
{
var left = layout.headerRight - invoice.imageWidth;
doc.addImage(invoice.image, 'JPEG', layout.marginLeft, layout.accountTop, invoice.imageWidth, invoice.imageHeight);
doc.addImage(invoice.image, 'JPEG', layout.marginLeft, 30);
}
if (invoice.imageLogo1)
@ -717,7 +701,7 @@ function GetReportTemplate1(doc, invoice, layout, checkMath)
SetPdfColor('LightBlue',doc);
doc.setFontSize('11');
doc.text(50, layout.headerTop, 'INVOICE');
doc.text(50, layout.headerTop, invoiceLabels.invoice.toUpperCase());
//doc.setDrawColor(220,220,220);
//doc.line(30, y, 560, y); // horizontal line
@ -726,8 +710,8 @@ function GetReportTemplate1(doc, invoice, layout, checkMath)
SetPdfColor('Black',doc); //set black color
doc.setFontSize(9);
displayInvoice(doc, invoice, 50, 160, layout);
displayClient(doc, invoice, 220, 160, layout);
displayInvoice(doc, invoice, 50, 170, layout);
displayClient(doc, invoice, 220, 170, layout);
doc.setLineWidth(0.3);
doc.setDrawColor(200,200,200);
@ -793,8 +777,6 @@ function GetReportTemplate1(doc, invoice, layout, checkMath)
function GetReportTemplate2(doc, invoice, layout, checkMath)
{
var GlobalY=0;//Y position of line at current page
@ -822,7 +804,7 @@ function GetReportTemplate2(doc, invoice, layout, checkMath)
if (invoice.image)
{
var left = layout.headerRight - invoice.imageWidth;
doc.addImage(invoice.image, 'JPEG', layout.marginLeft, 30, invoice.imageWidth, invoice.imageHeight);
doc.addImage(invoice.image, 'JPEG', layout.marginLeft, 30);
}
Report2AddFooter (invoice,doc);
@ -897,7 +879,7 @@ function GetReportTemplate2(doc, invoice, layout, checkMath)
SetPdfColor('SomeGreen',doc);
doc.setFontSize('14');
doc.setFontType("bold");
doc.text(50, GlobalY, 'YOUR INVOICE');
doc.text(50, GlobalY, invoiceLabels.your_invoice.toUpperCase());
var z=GlobalY;
@ -1140,7 +1122,7 @@ function GetReportTemplate3(doc, invoice, layout, checkMath)
{
y=130;
var left = layout.headerRight - invoice.imageWidth;
doc.addImage(invoice.image, 'JPEG', layout.marginLeft, y, invoice.imageWidth, invoice.imageHeight);
doc.addImage(invoice.image, 'JPEG', layout.marginLeft, y);
}
Report3AddFooter (invoice, account, doc, layout);
@ -1307,11 +1289,11 @@ function displayInvoice(doc, invoice, x, y, layout, rightAlignX) {
}
var data = [
{'Invoice Number': invoice.invoice_number},
{'PO Number': invoice.po_number},
{'Invoice Date': invoice.invoice_date},
{'Due Date': invoice.due_date},
{'Balance Due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}
{'invoice_number': invoice.invoice_number},
{'po_number': invoice.po_number},
{'invoice_date': invoice.invoice_date},
{'due_date': invoice.due_date},
{'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)}
];
displayGrid(doc, invoice, data, x, y, layout, true, rightAlignX);
@ -1325,10 +1307,10 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX)
//var taxTitle = 'Tax ' + getInvoiceTaxRate(invoice) + '%';
var data = [
{'Subtotal': formatMoney(invoice.subtotal_amount, invoice.client.currency_id)},
{'Discount': invoice.discount_amount > 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false},
{'Tax': invoice.tax_amount > 0 ? formatMoney(invoice.tax_amount, invoice.client.currency_id) : false},
{'Paid to Date': formatMoney(invoice.amount - invoice.balance, invoice.client.currency_id)}
{'subtotal': formatMoney(invoice.subtotal_amount, invoice.client.currency_id)},
{'discount': invoice.discount_amount > 0 ? formatMoney(invoice.discount_amount, invoice.client.currency_id) : false},
{'tax': invoice.tax_amount > 0 ? formatMoney(invoice.tax_amount, invoice.client.currency_id) : false},
{'paid_to_date': formatMoney(invoice.amount - invoice.balance, invoice.client.currency_id)}
];
return displayGrid(doc, invoice, data, 300, y, layout, true, 550, rightAlignTitleX) + 10;
@ -1357,6 +1339,7 @@ function concatStrings() {
function displayGrid(doc, invoice, data, x, y, layout, hasheader, rightAlignX, rightAlignTitleX) {
var numLines = 0;
var origY = y;
for (var i=0; i<data.length; i++) {
doc.setFontType('normal');
@ -1391,11 +1374,16 @@ function displayGrid(doc, invoice, data, x, y, layout, hasheader, rightAlignX, r
}
doc.text(marginLeft, y, value);
/*
if (rightAlignTitleX && i === 0) {
doc.setFontType('bold');
} else {
doc.setFontType('normal');
}
*/
doc.setFontType('normal');
key = invoiceLabels[key];
if (rightAlignTitleX) {
marginLeft = rightAlignTitleX - (doc.getStringUnitWidth(key) * doc.internal.getFontSize());
@ -1427,7 +1415,7 @@ function displayNotesAndTerms(doc, layout, invoice, y)
if (invoice.terms) {
doc.setFontType("bold");
doc.text(layout.marginLeft, y, "Terms");
doc.text(layout.marginLeft, y, invoiceLabels.terms);
y += 16;
doc.setFontType("normal");
doc.text(layout.marginLeft, y, invoice.terms);
@ -1503,26 +1491,25 @@ function getInvoiceTaxRate(invoice) {
function displayInvoiceHeader(doc, invoice, layout) {
var costX = layout.unitCostRight - (doc.getStringUnitWidth('Unit Cost') * doc.internal.getFontSize());
var qtyX = layout.qtyRight - (doc.getStringUnitWidth('Quantity') * doc.internal.getFontSize());
var taxX = layout.taxRight - (doc.getStringUnitWidth('Tax') * doc.internal.getFontSize());
var totalX = layout.lineTotalRight - (doc.getStringUnitWidth('Line Total') * doc.internal.getFontSize());
var costX = layout.unitCostRight - (doc.getStringUnitWidth(invoiceLabels.unit_cost) * doc.internal.getFontSize());
var qtyX = layout.qtyRight - (doc.getStringUnitWidth(invoiceLabels.quantity) * doc.internal.getFontSize());
var taxX = layout.taxRight - (doc.getStringUnitWidth(invoiceLabels.tax) * doc.internal.getFontSize());
var totalX = layout.lineTotalRight - (doc.getStringUnitWidth(invoiceLabels.line_total) * doc.internal.getFontSize());
doc.text(layout.marginLeft, layout.tableTop, 'Item');
doc.text(layout.descriptionLeft, layout.tableTop, 'Description');
doc.text(costX, layout.tableTop, 'Unit Cost');
doc.text(qtyX, layout.tableTop, 'Quantity');
doc.text(totalX, layout.tableTop, 'Line Total');
doc.text(layout.marginLeft, layout.tableTop, invoiceLabels.item);
doc.text(layout.descriptionLeft, layout.tableTop, invoiceLabels.description);
doc.text(costX, layout.tableTop, invoiceLabels.unit_cost);
doc.text(qtyX, layout.tableTop, invoiceLabels.quantity);
doc.text(totalX, layout.tableTop, invoiceLabels.line_total);
if (invoice.has_taxes)
{
doc.text(taxX, layout.tableTop, 'Tax');
doc.text(taxX, layout.tableTop, invoiceLabels.tax);
}
}
function displayInvoiceItems(doc, invoice, layout) {
doc.setFontType("normal");
var line = 1;

485
public/js/stacktrace.js Normal file
View File

@ -0,0 +1,485 @@
// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
// Luke Smith http://lucassmith.name/ (2008)
// Loic Dachary <loic@dachary.org> (2008)
// Johan Euphrosine <proppy@aminche.com> (2008)
// Oyvind Sean Kinsey http://kinsey.no/blog (2010)
// Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010)
/*global module, exports, define, ActiveXObject*/
(function(global, factory) {
if (typeof exports === 'object') {
// Node
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD
define(factory);
} else {
// Browser globals
global.printStackTrace = factory();
}
}(this, function() {
/**
* Main function giving a function stack trace with a forced or passed in Error
*
* @cfg {Error} e The error to create a stacktrace from (optional)
* @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
* @return {Array} of Strings with functions, lines, files, and arguments where possible
*/
function printStackTrace(options) {
options = options || {guess: true};
var ex = options.e || null, guess = !!options.guess;
var p = new printStackTrace.implementation(), result = p.run(ex);
return (guess) ? p.guessAnonymousFunctions(result) : result;
}
printStackTrace.implementation = function() {
};
printStackTrace.implementation.prototype = {
/**
* @param {Error} [ex] The error to create a stacktrace from (optional)
* @param {String} [mode] Forced mode (optional, mostly for unit tests)
*/
run: function(ex, mode) {
ex = ex || this.createException();
mode = mode || this.mode(ex);
if (mode === 'other') {
return this.other(arguments.callee);
} else {
return this[mode](ex);
}
},
createException: function() {
try {
this.undef();
} catch (e) {
return e;
}
},
/**
* Mode could differ for different exception, e.g.
* exceptions in Chrome may or may not have arguments or stack.
*
* @return {String} mode of operation for the exception
*/
mode: function(e) {
if (e['arguments'] && e.stack) {
return 'chrome';
}
if (e.stack && e.sourceURL) {
return 'safari';
}
if (e.stack && e.number) {
return 'ie';
}
if (e.stack && e.fileName) {
return 'firefox';
}
if (e.message && e['opera#sourceloc']) {
// e.message.indexOf("Backtrace:") > -1 -> opera9
// 'opera#sourceloc' in e -> opera9, opera10a
// !e.stacktrace -> opera9
if (!e.stacktrace) {
return 'opera9'; // use e.message
}
if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
// e.message may have more stack entries than e.stacktrace
return 'opera9'; // use e.message
}
return 'opera10a'; // use e.stacktrace
}
if (e.message && e.stack && e.stacktrace) {
// e.stacktrace && e.stack -> opera10b
if (e.stacktrace.indexOf("called from line") < 0) {
return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
}
// e.stacktrace && e.stack -> opera11
return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
}
if (e.stack && !e.fileName) {
// Chrome 27 does not have e.arguments as earlier versions,
// but still does not have e.fileName as Firefox
return 'chrome';
}
return 'other';
},
/**
* Given a context, function name, and callback function, overwrite it so that it calls
* printStackTrace() first with a callback and then runs the rest of the body.
*
* @param {Object} context of execution (e.g. window)
* @param {String} functionName to instrument
* @param {Function} callback function to call with a stack trace on invocation
*/
instrumentFunction: function(context, functionName, callback) {
context = context || window;
var original = context[functionName];
context[functionName] = function instrumented() {
callback.call(this, printStackTrace().slice(4));
return context[functionName]._instrumented.apply(this, arguments);
};
context[functionName]._instrumented = original;
},
/**
* Given a context and function name of a function that has been
* instrumented, revert the function to it's original (non-instrumented)
* state.
*
* @param {Object} context of execution (e.g. window)
* @param {String} functionName to de-instrument
*/
deinstrumentFunction: function(context, functionName) {
if (context[functionName].constructor === Function &&
context[functionName]._instrumented &&
context[functionName]._instrumented.constructor === Function) {
context[functionName] = context[functionName]._instrumented;
}
},
/**
* Given an Error object, return a formatted Array based on Chrome's stack string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
chrome: function(e) {
return (e.stack + '\n')
.replace(/^[\s\S]+?\s+at\s+/, ' at ') // remove message
.replace(/^\s+(at eval )?at\s+/gm, '') // remove 'at' and indentation
.replace(/^([^\(]+?)([\n$])/gm, '{anonymous}() ($1)$2')
.replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}() ($1)')
.replace(/^(.+) \((.+)\)$/gm, '$1@$2')
.split('\n')
.slice(0, -1);
},
/**
* Given an Error object, return a formatted Array based on Safari's stack string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
safari: function(e) {
return e.stack.replace(/\[native code\]\n/m, '')
.replace(/^(?=\w+Error\:).*$\n/m, '')
.replace(/^@/gm, '{anonymous}()@')
.split('\n');
},
/**
* Given an Error object, return a formatted Array based on IE's stack string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
ie: function(e) {
return e.stack
.replace(/^\s*at\s+(.*)$/gm, '$1')
.replace(/^Anonymous function\s+/gm, '{anonymous}() ')
.replace(/^(.+)\s+\((.+)\)$/gm, '$1@$2')
.split('\n')
.slice(1);
},
/**
* Given an Error object, return a formatted Array based on Firefox's stack string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
firefox: function(e) {
return e.stack.replace(/(?:\n@:0)?\s+$/m, '')
.replace(/^(?:\((\S*)\))?@/gm, '{anonymous}($1)@')
.split('\n');
},
opera11: function(e) {
var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
var lines = e.stacktrace.split('\n'), result = [];
for (var i = 0, len = lines.length; i < len; i += 2) {
var match = lineRE.exec(lines[i]);
if (match) {
var location = match[4] + ':' + match[1] + ':' + match[2];
var fnName = match[3] || "global code";
fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
}
}
return result;
},
opera10b: function(e) {
// "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
// "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
// "@file://localhost/G:/js/test/functional/testcase1.html:15"
var lineRE = /^(.*)@(.+):(\d+)$/;
var lines = e.stacktrace.split('\n'), result = [];
for (var i = 0, len = lines.length; i < len; i++) {
var match = lineRE.exec(lines[i]);
if (match) {
var fnName = match[1] ? (match[1] + '()') : "global code";
result.push(fnName + '@' + match[2] + ':' + match[3]);
}
}
return result;
},
/**
* Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
opera10a: function(e) {
// " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
// " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
var lines = e.stacktrace.split('\n'), result = [];
for (var i = 0, len = lines.length; i < len; i += 2) {
var match = lineRE.exec(lines[i]);
if (match) {
var fnName = match[3] || ANON;
result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
}
}
return result;
},
// Opera 7.x-9.2x only!
opera9: function(e) {
// " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
// " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
var lines = e.message.split('\n'), result = [];
for (var i = 2, len = lines.length; i < len; i += 2) {
var match = lineRE.exec(lines[i]);
if (match) {
result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
}
}
return result;
},
// Safari 5-, IE 9-, and others
other: function(curr) {
var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10;
var slice = Array.prototype.slice;
while (curr && curr['arguments'] && stack.length < maxStackSize) {
fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
args = slice.call(curr['arguments'] || []);
stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
try {
curr = curr.caller;
} catch (e) {
stack[stack.length] = '' + e;
break;
}
}
return stack;
},
/**
* Given arguments array as a String, substituting type names for non-string types.
*
* @param {Arguments,Array} args
* @return {String} stringified arguments
*/
stringifyArguments: function(args) {
var result = [];
var slice = Array.prototype.slice;
for (var i = 0; i < args.length; ++i) {
var arg = args[i];
if (arg === undefined) {
result[i] = 'undefined';
} else if (arg === null) {
result[i] = 'null';
} else if (arg.constructor) {
// TODO constructor comparison does not work for iframes
if (arg.constructor === Array) {
if (arg.length < 3) {
result[i] = '[' + this.stringifyArguments(arg) + ']';
} else {
result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
}
} else if (arg.constructor === Object) {
result[i] = '#object';
} else if (arg.constructor === Function) {
result[i] = '#function';
} else if (arg.constructor === String) {
result[i] = '"' + arg + '"';
} else if (arg.constructor === Number) {
result[i] = arg;
} else {
result[i] = '?';
}
}
}
return result.join(',');
},
sourceCache: {},
/**
* @return the text from a given URL
*/
ajax: function(url) {
var req = this.createXMLHTTPObject();
if (req) {
try {
req.open('GET', url, false);
//req.overrideMimeType('text/plain');
//req.overrideMimeType('text/javascript');
req.send(null);
//return req.status == 200 ? req.responseText : '';
return req.responseText;
} catch (e) {
}
}
return '';
},
/**
* Try XHR methods in order and store XHR factory.
*
* @return <Function> XHR function or equivalent
*/
createXMLHTTPObject: function() {
var xmlhttp, XMLHttpFactories = [
function() {
return new XMLHttpRequest();
}, function() {
return new ActiveXObject('Msxml2.XMLHTTP');
}, function() {
return new ActiveXObject('Msxml3.XMLHTTP');
}, function() {
return new ActiveXObject('Microsoft.XMLHTTP');
}
];
for (var i = 0; i < XMLHttpFactories.length; i++) {
try {
xmlhttp = XMLHttpFactories[i]();
// Use memoization to cache the factory
this.createXMLHTTPObject = XMLHttpFactories[i];
return xmlhttp;
} catch (e) {
}
}
},
/**
* Given a URL, check if it is in the same domain (so we can get the source
* via Ajax).
*
* @param url <String> source url
* @return <Boolean> False if we need a cross-domain request
*/
isSameDomain: function(url) {
return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs.
},
/**
* Get source code from given URL if in the same domain.
*
* @param url <String> JS source URL
* @return <Array> Array of source code lines
*/
getSource: function(url) {
// TODO reuse source from script tags?
if (!(url in this.sourceCache)) {
this.sourceCache[url] = this.ajax(url).split('\n');
}
return this.sourceCache[url];
},
guessAnonymousFunctions: function(stack) {
for (var i = 0; i < stack.length; ++i) {
var reStack = /\{anonymous\}\(.*\)@(.*)/,
reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
frame = stack[i], ref = reStack.exec(frame);
if (ref) {
var m = reRef.exec(ref[1]);
if (m) { // If falsey, we did not get any file/line information
var file = m[1], lineno = m[2], charno = m[3] || 0;
if (file && this.isSameDomain(file) && lineno) {
var functionName = this.guessAnonymousFunction(file, lineno, charno);
stack[i] = frame.replace('{anonymous}', functionName);
}
}
}
}
return stack;
},
guessAnonymousFunction: function(url, lineNo, charNo) {
var ret;
try {
ret = this.findFunctionName(this.getSource(url), lineNo);
} catch (e) {
ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
}
return ret;
},
findFunctionName: function(source, lineNo) {
// FIXME findFunctionName fails for compressed source
// (more than one function on the same line)
// function {name}({args}) m[1]=name m[2]=args
var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
// {name} = function ({args}) TODO args capture
// /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
var reFunctionExpression = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/;
// {name} = eval()
var reFunctionEvaluation = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
// Walk backwards in the source lines until we find
// the line which matches one of the patterns above
var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
for (var i = 0; i < maxLines; ++i) {
// lineNo is 1-based, source[] is 0-based
line = source[lineNo - i - 1];
commentPos = line.indexOf('//');
if (commentPos >= 0) {
line = line.substr(0, commentPos);
}
// TODO check other types of comments? Commented code may lead to false positive
if (line) {
code = line + code;
m = reFunctionExpression.exec(code);
if (m && m[1]) {
return m[1];
}
m = reFunctionDeclaration.exec(code);
if (m && m[1]) {
//return m[1] + "(" + (m[2] || "") + ")";
return m[1];
}
m = reFunctionEvaluation.exec(code);
if (m && m[1]) {
return m[1];
}
}
}
return '(?)';
}
};
return printStackTrace;
}));