diff --git a/app/Http/Controllers/AppController.php b/app/Http/Controllers/AppController.php index 964923ee615b..c261d6fbdea0 100644 --- a/app/Http/Controllers/AppController.php +++ b/app/Http/Controllers/AppController.php @@ -10,6 +10,8 @@ use Input; use Utils; use View; use Session; +use Cookie; +use Response; use App\Models\User; use App\Ninja\Mailers\Mailer; use App\Ninja\Repositories\AccountRepository; @@ -34,7 +36,15 @@ class AppController extends BaseController return Redirect::to('/'); } - return View::make('setup'); + $view = View::make('setup'); + + /* + $cookie = Cookie::forget('ninja_session', '/', 'www.ninja.dev'); + Cookie::queue($cookie); + return Response::make($view)->withCookie($cookie); + */ + + return Response::make($view); } public function doSetup() diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index dd622557b80d..1a18941bb6e9 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -451,7 +451,7 @@ class InvoiceController extends BaseController $pdfUpload = Input::get('pdfupload'); if (!empty($pdfUpload) && strpos($pdfUpload, 'data:application/pdf;base64,') === 0) { - $this->storePDF(Input::get('pdfupload'), $invoice->id); + $this->storePDF(Input::get('pdfupload'), $invoice); } if ($action == 'clone') { @@ -597,11 +597,9 @@ class InvoiceController extends BaseController return View::make('invoices.history', $data); } - private function storePDF($encodedString, $invoiceId) + private function storePDF($encodedString, $invoice) { - $uploadsDir = storage_path().'/pdfcache/'; $encodedString = str_replace('data:application/pdf;base64,', '', $encodedString); - $name = 'cache-'.$invoiceId.'.pdf'; - file_put_contents($uploadsDir.$name, base64_decode($encodedString)); + file_put_contents($invoice->getPDFPath(), base64_decode($encodedString)); } } diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index b75ad9d369f1..230f9d64997a 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -631,12 +631,7 @@ class PaymentController extends BaseController $payment->contact_id = $invitation->contact_id; $payment->transaction_reference = $ref; $payment->payment_date = date_create()->format('Y-m-d'); - - if ($invoice->partial) { - $invoice->partial = 0; - $invoice->save(); - } - + if ($payerId) { $payment->payer_id = $payerId; } diff --git a/app/Http/Middleware/DuplicateSubmissionCheck.php b/app/Http/Middleware/DuplicateSubmissionCheck.php index 782c9209dda7..2468f7ac9b77 100644 --- a/app/Http/Middleware/DuplicateSubmissionCheck.php +++ b/app/Http/Middleware/DuplicateSubmissionCheck.php @@ -17,7 +17,7 @@ class DuplicateSubmissionCheck $lastPage = session(SESSION_LAST_REQUEST_PAGE); $lastTime = session(SESSION_LAST_REQUEST_TIME); - if ($lastPage == $path && (microtime(true) - $lastTime <= 1.5)) { + if ($lastPage == $path && (microtime(true) - $lastTime <= 1)) { return redirect('/')->with('warning', trans('texts.duplicate_post')); } diff --git a/app/Http/routes.php b/app/Http/routes.php index 94f49865e6c4..127f19e4114d 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -69,7 +69,7 @@ post('/login', array('as' => 'login', 'uses' => 'Auth\AuthController@postLoginWr get('/logout', array('as' => 'logout', 'uses' => 'Auth\AuthController@getLogout')); get('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getEmail')); post('/forgot', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postEmail')); -get('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); +get('/password/reset/{token}', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@getReset')); post('/password/reset', array('as' => 'forgot', 'uses' => 'Auth\PasswordController@postReset')); get('/user/confirm/{code}', 'UserController@confirm'); @@ -546,4 +546,4 @@ if (Auth::check() && Auth::user()->id === 1) { Auth::loginUsingId(1); } -*/ +*/ \ No newline at end of file diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 13db7ae3577f..244141ea8a43 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -214,7 +214,6 @@ class Activity extends Eloquent if ($invoice->isPaid() && $invoice->balance > 0) { $invoice->invoice_status_id = INVOICE_STATUS_PARTIAL; - $invoice->save(); } } } @@ -292,7 +291,7 @@ class Activity extends Eloquent $invoice = $payment->invoice; $invoice->balance = $invoice->balance - $payment->amount; $invoice->invoice_status_id = ($invoice->balance > 0) ? INVOICE_STATUS_PARTIAL : INVOICE_STATUS_PAID; - $invoice->partial = 0; + $invoice->partial = max(0, $invoice->partial - $payment->amount); $invoice->save(); } diff --git a/app/Models/Invitation.php b/app/Models/Invitation.php index 5b38da2d0e78..a42c82eb53f3 100644 --- a/app/Models/Invitation.php +++ b/app/Models/Invitation.php @@ -33,7 +33,7 @@ class Invitation extends EntityModel $url = SITE_URL; if ($this->account->subdomain) { - $url = str_replace(['://www', '://'], "://{$this->account->subdomain}.", $url); + $url = str_replace('://www.', "://{$this->account->subdomain}.", $url); } return "{$url}/view/{$this->invitation_key}"; diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index fe1156c4e46c..890854a05b05 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -57,6 +57,11 @@ class Invoice extends EntityModel return trans("texts.$entityType") . '_' . $this->invoice_number . '.pdf'; } + public function getPDFPath() + { + return storage_path() . '/pdfcache/cache-' . $this->id . '.pdf'; + } + public function getLink() { return link_to('invoices/'.$this->public_id, $this->invoice_number); diff --git a/app/Ninja/Mailers/Mailer.php b/app/Ninja/Mailers/Mailer.php index 246bbdecef26..21f82009b749 100644 --- a/app/Ninja/Mailers/Mailer.php +++ b/app/Ninja/Mailers/Mailer.php @@ -22,11 +22,10 @@ class Mailer } if(isset($data['invoice_id'])) { - $invoice = Invoice::scope()->with("account")->where('id', '=', $data['invoice_id'])->get()->first(); - $pdfPath = storage_path().'/pdfcache/cache-'.$invoice->id.'.pdf'; - if($invoice->account->pdf_email_attachment && file_exists($pdfPath)) { + $invoice = Invoice::with('account')->where('id', '=', $data['invoice_id'])->get()->first(); + if($invoice->account->pdf_email_attachment && file_exists($invoice->getPDFPath())) { $message->attach( - $pdfPath, + $invoice->getPDFPath(), array('as' => $invoice->getFileName(), 'mime' => 'application/pdf') ); } diff --git a/app/Ninja/Repositories/AccountRepository.php b/app/Ninja/Repositories/AccountRepository.php index b18ccfd983ec..2b7f66595e42 100644 --- a/app/Ninja/Repositories/AccountRepository.php +++ b/app/Ninja/Repositories/AccountRepository.php @@ -35,7 +35,7 @@ class AccountRepository $user = new User(); if (!$firstName && !$lastName && !$email && !$password) { $user->password = str_random(RANDOM_KEY_LENGTH); - //$user->email = $user->username = str_random(RANDOM_KEY_LENGTH); + $user->username = str_random(RANDOM_KEY_LENGTH); } else { $user->first_name = $firstName; $user->last_name = $lastName; diff --git a/public/js/built.js b/public/js/built.js index ea550343f3c4..818c6ed24ac2 100644 --- a/public/js/built.js +++ b/public/js/built.js @@ -33090,6 +33090,83 @@ function roundToTwo(num, toString) { function truncate(str, length) { return (str && str.length > length) ? (str.substr(0, length-1) + '...') : str; } + +(function($) +{ + /** + * Auto-growing textareas; technique ripped from Facebook + * + * + * http://github.com/jaz303/jquery-grab-bag/tree/master/javascripts/jquery.autogrow-textarea.js + */ + $.fn.autogrow = function(options) + { + return this.filter('textarea').each(function() + { + var self = this; + var $self = $(self); + var minHeight = $self.height(); + var noFlickerPad = $self.hasClass('autogrow-short') ? 0 : parseInt($self.css('lineHeight')) || 0; + var settings = $.extend({ + preGrowCallback: null, + postGrowCallback: null + }, options ); + + var shadow = $('
').css({ + position: 'absolute', + top: -10000, + left: -10000, + width: $self.width(), + fontSize: $self.css('fontSize'), + fontFamily: $self.css('fontFamily'), + fontWeight: $self.css('fontWeight'), + lineHeight: $self.css('lineHeight'), + resize: 'none', + 'word-wrap': 'break-word' + }).appendTo(document.body); + + var update = function(event) + { + var times = function(string, number) + { + for (var i=0, r=''; i/g, '>') + .replace(/&/g, '&') + .replace(/\n$/, '
 ') + .replace(/\n/g, '
') + .replace(/ {2,}/g, function(space){ return times(' ', space.length - 1) + ' ' }); + + // Did enter get pressed? Resize in this keydown event so that the flicker doesn't occur. + if (event && event.data && event.data.event === 'keydown' && event.keyCode === 13) { + val += '
'; + } + + shadow.css('width', $self.width()); + shadow.html(val + (noFlickerPad === 0 ? '...' : '')); // Append '...' to resize pre-emptively. + + var newHeight=Math.max(shadow.height() + noFlickerPad, minHeight); + if(settings.preGrowCallback!=null){ + newHeight=settings.preGrowCallback($self,shadow,newHeight,minHeight); + } + + $self.height(newHeight); + + if(settings.postGrowCallback!=null){ + settings.postGrowCallback($self); + } + } + + $self.change(update).keyup(update).keydown({event:'keydown'},update); + $(window).resize(update); + + update(); + }); + }; +})(jQuery); function GetPdfMake(invoice, javascript, callback) { var account = invoice.account; eval(javascript); diff --git a/readme.md b/readme.md index 9c308b8ed0ea..0602323130b0 100644 --- a/readme.md +++ b/readme.md @@ -16,12 +16,14 @@ Developed by [@hillelcoren](https://twitter.com/hillelcoren) | Designed by [kant ### Features -* Core application built using Laravel 5 -* Invoice PDF generation directly in the browser -* Integrates with many payment providers +* Built using Laravel 5 +* Live PDF generation +* Integrates with 30+ payment providers * Recurring invoices * Tax rates and payment terms * Multi-user support +* Partial payments +* Custom email templates * [Zapier](https://zapier.com/) integration * [D3.js](http://d3js.org/) visualizations diff --git a/resources/lang/da/validation.php b/resources/lang/da/validation.php index acdb9c443adc..1ffb84108fe5 100644 --- a/resources/lang/da/validation.php +++ b/resources/lang/da/validation.php @@ -73,6 +73,10 @@ return array( "unique" => ":attribute er allerede taget.", "url" => ":attribute formatet er ugyldigt.", + "positive" => "The :attribute must be greater than zero.", + "has_credit" => "The client does not have enough credit.", + "notmasked" => "The values are masked", + /* |-------------------------------------------------------------------------- | Custom Validation Language Lines diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index e45efcefff5b..644ce5996115 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -605,34 +605,6 @@ return array( 'app_title' => 'Free Open-Source Online Invoicing', 'app_description' => 'Invoice Ninja is a free, open-source solution for invoicing and billing customers. With Invoice Ninja, you can easily build and send beautiful invoices from any device that has access to the web. Your clients can print your invoices, download them as pdf files, and even pay you online from within the system.', - 'plans' => [ - 'header' => 'The Plans', - 'free' => 'Free', - 'unlimited' => 'Unlimited', - 'pro_plan' => 'Pro Plan', - - 'go_pro' => 'Go Pro to Unlock Premium Invoice Ninja Features', - 'go_pro_text' => 'We believe that the free version of Invoice Ninja is a truly awesome product loaded with the key features you need to bill your clients electronically. But for those who crave still more Ninja awesomeness, we\'ve unmasked the Invoice Ninja Pro plan, which offers more versatility, power and customization options for just $50 per year.', - - 'number_clients' => 'Number of clients per account', - 'unlimited_invoices' => 'Unlimited client invoices', - 'company_logo' => 'Add your company logo', - 'live_pdf' => 'Live .PDF invoice creation', - 'four_templates' => '4 beautiful invoice templates', - 'payments' => 'Accept credit card payments', - 'additional_templates' => 'Additional invoice templates', - 'multi_user' => 'Multi-user support', - 'quotes' => 'Quotes/pro-forma invoices', - 'advanced_settings' => 'Advanced invoice settings', - 'data_vizualizations' => 'Dynamic data vizualizations', - 'email_support' => 'Priority email support', - 'remove_created_by' => 'Remove "Created by Invoice Ninja"', - 'latest_features' => 'Latest and greatest features', - 'pricing' => 'Pricing', - 'free_always' => 'Free /Always!', - 'year_price' => '$50 /Year', - ], - 'rows' => 'rows', 'www' => 'www', 'logo' => 'Logo', @@ -653,6 +625,4 @@ return array( 'recurring' => 'Recurring', 'last_invoice_sent' => 'Last invoice sent :date', - - ); diff --git a/resources/lang/es/validation.php b/resources/lang/es/validation.php index 2baee0ab1da4..cfbecbbda772 100644 --- a/resources/lang/es/validation.php +++ b/resources/lang/es/validation.php @@ -72,9 +72,6 @@ return array( "url" => "El formato :attribute es inválido.", "positive" => ":attribute debe ser mayor que cero.", "has_credit" => "el cliente no tiene crédito suficiente.", - - "positive" => "The :attribute must be greater than zero.", - "has_credit" => "The client does not have enough credit.", "notmasked" => "The values are masked", diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 2baee0ab1da4..cfbecbbda772 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -72,9 +72,6 @@ return array( "url" => "El formato :attribute es inválido.", "positive" => ":attribute debe ser mayor que cero.", "has_credit" => "el cliente no tiene crédito suficiente.", - - "positive" => "The :attribute must be greater than zero.", - "has_credit" => "The client does not have enough credit.", "notmasked" => "The values are masked", diff --git a/resources/lang/nl/validation.php b/resources/lang/nl/validation.php index 81ab739e520f..f4640669437b 100644 --- a/resources/lang/nl/validation.php +++ b/resources/lang/nl/validation.php @@ -73,9 +73,6 @@ return array( "positive" => ":attribute moet groter zijn dan nul.", "has_credit" => "De klant heeft niet voldoende krediet.", - - "positive" => "The :attribute must be greater than zero.", - "has_credit" => "The client does not have enough credit.", "notmasked" => "The values are masked", /* diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 27d5c008900c..1ebe97268459 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -71,9 +71,6 @@ return array( "positive" => ":attribute deve ser maior que zero.", "has_credit" => "O cliente não possui crédito suficiente.", - -"positive" => "The :attribute must be greater than zero.", -"has_credit" => "The client does not have enough credit.", "notmasked" => "The values are masked", diff --git a/resources/lang/sv/validation.php b/resources/lang/sv/validation.php index 93283f454cae..d65bf9664572 100644 --- a/resources/lang/sv/validation.php +++ b/resources/lang/sv/validation.php @@ -73,6 +73,10 @@ return [ "unique" => ":attribute används redan.", "url" => "Formatet :attribute är ogiltigt.", + "positive" => "The :attribute must be greater than zero.", + "has_credit" => "The client does not have enough credit.", + "notmasked" => "The values are masked", + /* |-------------------------------------------------------------------------- | Custom Validation Language Lines diff --git a/resources/views/accounts/details.blade.php b/resources/views/accounts/details.blade.php index 3529a4c5fa4b..3388f2f801f3 100644 --- a/resources/views/accounts/details.blade.php +++ b/resources/views/accounts/details.blade.php @@ -35,7 +35,7 @@ {!! Former::text('name') !!} - @if (Auth::user()->isPro()) + @if (Auth::user()->isPro() && !Utils::isNinja()) {{ Former::setOption('capitalize_translations', false) }} {!! Former::text('subdomain')->placeholder('texts.www')->onchange('onSubdomainChange()') !!} @endif @@ -266,6 +266,7 @@ '&confirm_password=' + encodeURIComponent($('form #confirm_password').val()), success: function(result) { if (result == 'success') { + NINJA.formIsChanged = false; $('#changePasswordButton').hide(); $('#successDiv').show(); $('#cancelChangePasswordButton').html('{{ trans('texts.close') }}'); @@ -289,5 +290,8 @@ +@stop +@section('onReady') + $('#name').focus(); @stop \ No newline at end of file diff --git a/resources/views/accounts/invoice_settings.blade.php b/resources/views/accounts/invoice_settings.blade.php index 097cd1e0d02f..cb95cc05d5c8 100644 --- a/resources/views/accounts/invoice_settings.blade.php +++ b/resources/views/accounts/invoice_settings.blade.php @@ -138,4 +138,8 @@ +@stop + +@section('onReady') + $('#custom_invoice_label1').focus(); @stop \ No newline at end of file diff --git a/resources/views/accounts/product.blade.php b/resources/views/accounts/product.blade.php index b31d7e543050..571beda764ab 100644 --- a/resources/views/accounts/product.blade.php +++ b/resources/views/accounts/product.blade.php @@ -58,6 +58,10 @@ window.model = new ViewModel(); ko.applyBindings(model); + $(function() { + $('#product_key').focus(); + }); + @stop \ No newline at end of file diff --git a/resources/views/accounts/token.blade.php b/resources/views/accounts/token.blade.php index b99888369394..50c17f635f05 100644 --- a/resources/views/accounts/token.blade.php +++ b/resources/views/accounts/token.blade.php @@ -30,4 +30,8 @@ {!! Former::close() !!} +@stop + +@section('onReady') + $('#name').focus(); @stop \ No newline at end of file diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 231786890837..c1bb719cba95 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -114,7 +114,6 @@ {!! Former::close() !!} - @if (!Utils::isNinja())