mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-09 20:52:56 +01:00
Merge branch 'master' into Dev_PHP-Payments
Merged changes from master.
This commit is contained in:
commit
3ca03eb2b4
@ -6,9 +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 discussion of the code please use the [Google Group](https://groups.google.com/d/forum/invoiceninja).
|
||||
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)
|
||||
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/)
|
||||
|
||||
|
@ -532,7 +532,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'));
|
||||
|
@ -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); })
|
||||
|
@ -17,11 +17,11 @@ class UserEventHandler
|
||||
|
||||
public function onLogin()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$account->last_login = Carbon::now()->toDateTimeString();
|
||||
$account->save();
|
||||
$account = Auth::user()->account;
|
||||
$account->last_login = Carbon::now()->toDateTimeString();
|
||||
$account->save();
|
||||
|
||||
Event::fire('user.refresh');
|
||||
Event::fire('user.refresh');
|
||||
}
|
||||
|
||||
public function onRefresh()
|
||||
|
@ -36,7 +36,7 @@ return array(
|
||||
'po_number' => 'PO Number',
|
||||
'po_number_short' => 'PO #',
|
||||
'frequency_id' => 'How often',
|
||||
'dicount' => 'Discount',
|
||||
'discount' => 'Discount',
|
||||
'taxes' => 'Taxes',
|
||||
'tax' => 'Tax',
|
||||
'item' => 'Item',
|
||||
|
@ -3,52 +3,52 @@
|
||||
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',
|
||||
'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' => 'Additional Info',
|
||||
'payment_terms' => 'Payment Terms',
|
||||
'currency_id' => 'Currency',
|
||||
'size_id' => 'Size',
|
||||
'industry_id' => 'Industry',
|
||||
'private_notes' => 'Private Notes',
|
||||
'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' => 'Invoice',
|
||||
'invoice' => 'Facture',
|
||||
'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',
|
||||
'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' => 'Tax',
|
||||
'item' => 'Item',
|
||||
'tax' => 'Taxe',
|
||||
'item' => 'Ligne', //I'm not sure, I need the context : screenshot ?
|
||||
'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',
|
||||
'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',
|
||||
|
||||
);
|
||||
);
|
||||
|
@ -25,4 +25,29 @@ return array(
|
||||
'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',
|
||||
|
||||
);
|
||||
|
@ -37,11 +37,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)
|
||||
|
@ -166,7 +166,7 @@ class Account extends Eloquent
|
||||
Session::put(SESSION_DATE_FORMAT, $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT);
|
||||
Session::put(SESSION_DATE_PICKER_FORMAT, $this->date_format ? $this->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT);
|
||||
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);
|
||||
Session::put(SESSION_CURRENCY, $this->currency_id ? $this->currency_id : DEFAULT_CURRENCY);
|
||||
}
|
||||
|
||||
public function getInvoiceLabels()
|
||||
@ -178,7 +178,7 @@ class Account extends Eloquent
|
||||
'due_date',
|
||||
'invoice_number',
|
||||
'po_number',
|
||||
'dicount',
|
||||
'discount',
|
||||
'taxes',
|
||||
'tax',
|
||||
'item',
|
||||
|
@ -50,13 +50,13 @@ class InvoiceRepository
|
||||
{
|
||||
$query = \DB::table('invoices')
|
||||
->join('clients', 'clients.id', '=','invoices.client_id')
|
||||
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('invoices.account_id', '=', $accountId)
|
||||
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('invoices.account_id', '=', $accountId)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', true)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email');
|
||||
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email');
|
||||
|
||||
if ($clientPublicId)
|
||||
{
|
||||
|
@ -456,7 +456,7 @@
|
||||
if (isStorageSupported()) {
|
||||
@if (Auth::check() && !Auth::user()->registered)
|
||||
localStorage.setItem('guest_key', '{{ Auth::user()->password }}');
|
||||
@endif
|
||||
@endif
|
||||
}
|
||||
|
||||
@if (!Auth::check() || !Auth::user()->registered)
|
||||
|
@ -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>
|
||||
<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;
|
||||
|
@ -74,7 +74,7 @@
|
||||
<div class="col-md-3 center-block">
|
||||
<a href="#">
|
||||
<div class="cta">
|
||||
<h2 onclick="getStarted()">Invoice Now <span>+</span></h2>
|
||||
<h2 onclick="return getStarted()">Invoice Now <span>+</span></h2>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -53,18 +53,6 @@ var contactForm = {
|
||||
}
|
||||
};
|
||||
|
||||
function isStorageSupported() {
|
||||
try {
|
||||
return 'localStorage' in window && window['localStorage'] !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getStarted() {
|
||||
$('#startForm').submit();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<section class="hero4" data-speed="2" data-type="background">
|
||||
@ -139,7 +127,7 @@ function getStarted() {
|
||||
<div class="col-md-3 center-block">
|
||||
<a href="#">
|
||||
<div class="cta">
|
||||
<h2 onclick="getStarted()">Invoice Now <span>+</span></h2>
|
||||
<h2 onclick="return getStarted()">Invoice Now <span>+</span></h2>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -27,22 +27,31 @@
|
||||
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$(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) {
|
||||
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>
|
||||
@ -93,6 +102,13 @@
|
||||
</script>
|
||||
|
||||
|
||||
<!--
|
||||
<script src="//platform.linkedin.com/in.js" type="text/javascript">
|
||||
lang: en_US
|
||||
</script>
|
||||
<script type="IN/Share" data-url="https://www.invoiceninja.com/"></script>
|
||||
-->
|
||||
|
||||
<!--<iframe src="http://ghbtns.com/github-btn.html?user=hillelcoren&repo=invoice-ninja&type=watch" allowtransparency="true" frameborder="0" scrolling="0" width="62" height="20"></iframe>-->
|
||||
|
||||
<p> </p>
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div class="col-md-3 center-block">
|
||||
<a href="#">
|
||||
<div class="cta">
|
||||
<h2 id="startButton" onclick="getStarted()">Invoice Now <span>+</span></h2>
|
||||
<h2 id="startButton" onclick="return getStarted()">Invoice Now <span>+</span></h2>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@ -40,7 +40,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-3 one">
|
||||
<div class="box">
|
||||
<div class="icon"><img src="images/icon-free.png"></div>
|
||||
<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>
|
||||
@ -48,8 +48,7 @@
|
||||
|
||||
<div class="col-md-3 two">
|
||||
<div class="box">
|
||||
<div class="icon"><img src=
|
||||
"images/icon-opensource.png"></div>
|
||||
<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>
|
||||
@ -57,7 +56,7 @@
|
||||
|
||||
<div class="col-md-3 three">
|
||||
<div class="box">
|
||||
<div class="icon"><img src="images/icon-pdf.png"></div>
|
||||
<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>
|
||||
@ -65,8 +64,7 @@
|
||||
|
||||
<div class="col-md-3 four">
|
||||
<div class="box">
|
||||
<div class="icon"><img src=
|
||||
"images/icon-payment.png"></div>
|
||||
<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 we’ll lend a hand (we’re pretty friendly).</p>
|
||||
</div>
|
||||
@ -102,7 +100,7 @@
|
||||
<div class="col-md-3 center-block">
|
||||
<a href="#">
|
||||
<div class="cta">
|
||||
<h2 onclick="getStarted()">Invoice Now <span>+</span></h2>
|
||||
<h2 onclick="return getStarted()">Invoice Now <span>+</span></h2>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
BIN
public/apple-touch-icon.png
Executable file
BIN
public/apple-touch-icon.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
public/images/social.jpg
Normal file
BIN
public/images/social.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -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 = {}
|
||||
}
|
||||
|
@ -793,8 +793,6 @@ function GetReportTemplate1(doc, invoice, layout, checkMath)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function GetReportTemplate2(doc, invoice, layout, checkMath)
|
||||
{
|
||||
var GlobalY=0;//Y position of line at current page
|
||||
@ -1357,9 +1355,10 @@ 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');
|
||||
|
||||
|
||||
if (invoice.invoice_design_id == 1 && i > 0 && origY === layout.accountTop) {
|
||||
SetPdfColor('GrayText',doc);
|
||||
}
|
||||
@ -1400,8 +1399,8 @@ function displayGrid(doc, invoice, data, x, y, layout, hasheader, rightAlignX, r
|
||||
*/
|
||||
|
||||
doc.setFontType('normal');
|
||||
|
||||
key = invoiceLabels[key];
|
||||
|
||||
if (rightAlignTitleX) {
|
||||
marginLeft = rightAlignTitleX - (doc.getStringUnitWidth(key) * doc.internal.getFontSize());
|
||||
} else {
|
||||
|
485
public/js/stacktrace.js
Normal file
485
public/js/stacktrace.js
Normal 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;
|
||||
}));
|
Loading…
Reference in New Issue
Block a user