diff --git a/.env.example b/.env.example index 1dc6d99bd419..20c977c663e9 100644 --- a/.env.example +++ b/.env.example @@ -8,8 +8,8 @@ DB_TYPE=mysql DB_STRICT=false DB_HOST=localhost DB_DATABASE=ninja -DB_USERNAME -DB_PASSWORD +DB_USERNAME=ninja +DB_PASSWORD=ninja MAIL_DRIVER=smtp MAIL_PORT=587 @@ -23,6 +23,7 @@ MAIL_PASSWORD PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address' LOG=single REQUIRE_HTTPS=false +API_SECRET=password GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET diff --git a/.travis.yml b/.travis.yml index 5c303f42917a..c7dd08d994c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: php -sudo: false +sudo: true php: - 5.5 - - 5.6 - - 7.0 - - hhvm +# - 5.6 +# - 7.0 +# - hhvm addons: hosts: @@ -30,12 +30,22 @@ before_install: install: # install Composer dependencies - - travis_retry composer update --prefer-dist; + - rm composer.lock + # these providers require referencing git commit's which cause Travis to fail + - sed -i '/mollie/d' composer.json + - sed -i '/2checkout/d' composer.json + - travis_retry composer install --prefer-dist; before_script: + # prevent MySQL went away error + - mysql -u root -e 'SET @@GLOBAL.wait_timeout=28800;' # copy configuration files + - cp .env.example .env - cp tests/_bootstrap.php.default tests/_bootstrap.php - - cp tests/.env.circleci .env + - php artisan key:generate --no-interaction + - sed -i 's/APP_ENV=production/APP_ENV=development/g' .env + - sed -i 's/APP_DEBUG=false/APP_DEBUG=true/g' .env + - sed -i 's/REQUIRE_HTTPS=false/NINJA_DEV=true/g' .env # create the database and user - mysql -u root -e "create database IF NOT EXISTS ninja;" - mysql -u root -e "GRANT ALL PRIVILEGES ON ninja.* To 'ninja'@'localhost' IDENTIFIED BY 'ninja'; FLUSH PRIVILEGES;" @@ -53,7 +63,29 @@ before_script: - curl -L http://ninja.dev:8000/update script: - - php ./vendor/codeception/codeception/codecept run --html --debug + - php ./vendor/codeception/codeception/codecept run --debug acceptance AllPagesCept.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance APICest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance CheckBalanceCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance ClientCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance CreditCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance InvoiceDesignCest.php + #- php ./vendor/codeception/codeception/codecept run acceptance OnlinePaymentCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance PaymentCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance TaskCest.php + #- php ./vendor/codeception/codeception/codecept run --debug acceptance TaxRatesCest.php + + #- sed -i 's/NINJA_DEV=true/NINJA_PROD=true/g' .env + #- php ./vendor/codeception/codeception/codecept run acceptance GoProCest.php + +after_script: + - cat .env + - mysql -u root -e 'select * from accounts;' ninja + - mysql -u root -e 'select * from account_gateways;' ninja + - mysql -u root -e 'select * from clients;' ninja + - mysql -u root -e 'select * from invoices;' ninja + - mysql -u root -e 'select * from invoice_items;' ninja + - cat storage/logs/laravel.log notifications: email: diff --git a/app/Console/Commands/GenerateResources.php b/app/Console/Commands/GenerateResources.php new file mode 100644 index 000000000000..a1dc404f79bb --- /dev/null +++ b/app/Console/Commands/GenerateResources.php @@ -0,0 +1,68 @@ + $value) { + if (is_array($value)) { + echo $key; + } else { + echo "$key => $value\n"; + } + } + } + + protected function getArguments() + { + return array( + //array('example', InputArgument::REQUIRED, 'An example argument.'), + ); + } + + protected function getOptions() + { + return array( + //array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null), + ); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index e281afb926d4..bd2720afa96f 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -18,6 +18,7 @@ class Kernel extends ConsoleKernel 'App\Console\Commands\SendRenewalInvoices', 'App\Console\Commands\SendReminders', 'App\Console\Commands\TestOFX', + 'App\Console\Commands\GenerateResources', ]; /** diff --git a/app/Http/Controllers/AccountApiController.php b/app/Http/Controllers/AccountApiController.php index 193cf2d7c5ef..77e85243d7a9 100644 --- a/app/Http/Controllers/AccountApiController.php +++ b/app/Http/Controllers/AccountApiController.php @@ -71,6 +71,8 @@ class AccountApiController extends BaseAPIController 'invoices' => ['invoice_items', 'user', 'client', 'payments'], 'products' => [], 'tax_rates' => [], + 'expenses' => ['client', 'invoice', 'vendor'], + 'payments' => ['invoice'], ]; foreach ($map as $key => $values) { diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 56a7d1a82b43..6c0f59a3ba91 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -117,13 +117,7 @@ class AccountController extends BaseController { Session::put("show_trash:{$entityType}", $visible == 'true'); - if ($entityType == 'user') { - return Redirect::to('settings/'.ACCOUNT_USER_MANAGEMENT); - } elseif ($entityType == 'token') { - return Redirect::to('settings/'.ACCOUNT_API_TOKENS); - } else { - return Redirect::to("{$entityType}s"); - } + return RESULT_SUCCESS; } public function getSearchData() @@ -958,7 +952,13 @@ class AccountController extends BaseController 'text' => $reason, ]; - $this->userMailer->sendTo(CONTACT_EMAIL, $email, $name, 'Invoice Ninja Feedback [Canceled Account]', 'contact', $data); + $subject = 'Invoice Ninja - Canceled Account'; + + if (Auth::user()->isPaidPro()) { + $subject .= ' [PRO]'; + } + + $this->userMailer->sendTo(CONTACT_EMAIL, $email, $name, $subject, 'contact', $data); } $user = Auth::user(); diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index dfbd2b386162..58b831503b78 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -200,7 +200,7 @@ class AccountGatewayController extends BaseController if ($gatewayId == GATEWAY_DWOLLA) { $optional = array_merge($optional, ['key', 'secret']); } elseif ($gatewayId == GATEWAY_STRIPE) { - if (Utils::isNinjaDev() && Input::get('23_apiKey') == env('TEST_API_KEY')) { + if (Utils::isNinjaDev()) { // do nothing - we're unable to acceptance test with StripeJS } else { $rules['publishable_key'] = 'required'; diff --git a/app/Http/Controllers/ClientApiController.php b/app/Http/Controllers/ClientApiController.php index a3fea1435fb4..4bac33236646 100644 --- a/app/Http/Controllers/ClientApiController.php +++ b/app/Http/Controllers/ClientApiController.php @@ -136,11 +136,11 @@ class ClientApiController extends BaseAPIController { if ($request->action == ACTION_ARCHIVE) { - try { - $client = Client::scope($publicId)->withTrashed()->firstOrFail(); - } catch (ModelNotFoundException $e) { + + $client = Client::scope($publicId)->withTrashed()->first(); + + if(!$client) return $this->errorResponse(['message'=>'Record not found'], 400); - } $this->clientRepo->archive($client); @@ -154,7 +154,7 @@ class ClientApiController extends BaseAPIController $client = Client::scope($publicId)->withTrashed()->first(); if(!$client) - return $this->errorResponse(['message'=>'Client not found.']); + return $this->errorResponse(['message'=>'Client not found.'], 400); $this->clientRepo->restore($client); @@ -173,7 +173,7 @@ class ClientApiController extends BaseAPIController ->first(); if(!$client) - return $this->errorResponse(['message'=>'Client not found.']); + return $this->errorResponse(['message'=>'Client not found.'],400); $transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer')); $data = $this->createItem($client, $transformer, ENTITY_CLIENT); diff --git a/app/Http/Controllers/ExpenseApiController.php b/app/Http/Controllers/ExpenseApiController.php new file mode 100644 index 000000000000..88ff5497cefd --- /dev/null +++ b/app/Http/Controllers/ExpenseApiController.php @@ -0,0 +1,64 @@ +expenseRepo = $expenseRepo; + $this->expenseService = $expenseService; + } + + public function index() + { + + $expenses = Expense::scope() + ->withTrashed() + ->orderBy('created_at','desc'); + + $expenses = $expenses->paginate(); + + $transformer = new ExpenseTransformer(Auth::user()->account, Input::get('serializer')); + $paginator = Expense::scope()->withTrashed()->paginate(); + + $data = $this->createCollection($expenses, $transformer, ENTITY_EXPENSE, $paginator); + + return $this->response($data); + + } + + public function update() + { + //stub + + } + + public function store() + { + //stub + + } + + public function destroy() + { + //stub + + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 6f2a6e884a30..5535b863e857 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -96,7 +96,7 @@ class ExpenseController extends BaseController { $expense = Expense::scope($publicId)->firstOrFail(); $expense->expense_date = Utils::fromSqlDate($expense->expense_date); - + $actions = []; if ($expense->invoice) { $actions[] = ['url' => URL::to("invoices/{$expense->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")]; diff --git a/app/Http/Controllers/InvoiceApiController.php b/app/Http/Controllers/InvoiceApiController.php index f6a5b9b60b3d..f99e12a4d740 100644 --- a/app/Http/Controllers/InvoiceApiController.php +++ b/app/Http/Controllers/InvoiceApiController.php @@ -1,6 +1,7 @@ invoiceRepo = $invoiceRepo; $this->clientRepo = $clientRepo; $this->paymentRepo = $paymentRepo; + $this->invoiceService = $invoiceService; $this->mailer = $mailer; } @@ -84,6 +87,36 @@ class InvoiceApiController extends BaseAPIController return $this->response($data); } + /** + * @SWG\Get( + * path="/invoices/{invoice_id}", + * summary="Individual Invoice", + * tags={"invoice"}, + * @SWG\Response( + * response=200, + * description="A single invoice", + * @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Invoice")) + * ), + * @SWG\Response( + * response="default", + * description="an ""unexpected"" error" + * ) + * ) + */ + + public function show($publicId) + { + + $invoice = Invoice::scope($publicId)->withTrashed()->first(); + + if(!$invoice) + return $this->errorResponse(['message'=>'Invoice does not exist!'], 404); + + $transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($invoice, $transformer, 'invoice'); + + return $this->response($data); + } /** * @SWG\Post( @@ -156,7 +189,7 @@ class InvoiceApiController extends BaseAPIController $data = self::prepareData($data, $client); $data['client_id'] = $client->id; - $invoice = $this->invoiceRepo->save($data); + $invoice = $this->invoiceService->save($data); $payment = false; // Optionally create payment with invoice @@ -168,14 +201,6 @@ class InvoiceApiController extends BaseAPIController ]); } - if (!isset($data['id'])) { - $invitation = Invitation::createNew(); - $invitation->invoice_id = $invoice->id; - $invitation->contact_id = $client->contacts[0]->id; - $invitation->invitation_key = str_random(RANDOM_KEY_LENGTH); - $invitation->save(); - } - if (isset($data['email_invoice']) && $data['email_invoice']) { if ($payment) { $this->mailer->sendPaymentConfirmation($payment); @@ -249,7 +274,7 @@ class InvoiceApiController extends BaseAPIController private function prepareItem($item) { // if only the product key is set we'll load the cost and notes - if ($item['product_key'] && empty($item['cost']) && empty($item['notes'])) { + if (!empty($item['product_key']) && empty($item['cost']) && empty($item['notes'])) { $product = Product::findProductByKey($item['product_key']); if ($product) { if (empty($item['cost'])) { @@ -282,12 +307,17 @@ class InvoiceApiController extends BaseAPIController $data = Input::all(); $error = null; - $invoice = Invoice::scope($data['id'])->firstOrFail(); + $invoice = Invoice::scope($data['id'])->withTrashed()->first(); + + if(!$invoice) + return $this->errorResponse(['message'=>'Invoice does not exist.'], 400); + + + $this->mailer->sendInvoice($invoice, false, false); - $this->mailer->sendInvoice($invoice); if($error) { - $response['error'] = "There was an error sending the invoice"; + return $this->errorResponse(['message'=>'There was an error sending the invoice'], 400); } else { $response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT); @@ -351,7 +381,7 @@ class InvoiceApiController extends BaseAPIController $data = $request->input(); $data['public_id'] = $publicId; - $this->invoiceRepo->save($data); + $this->invoiceService->save($data); $invoice = Invoice::scope($publicId)->with('client', 'invoice_items', 'invitations')->firstOrFail(); $transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); diff --git a/app/Http/Controllers/PaymentApiController.php b/app/Http/Controllers/PaymentApiController.php index 5a0f508b98bd..7022f0c3e840 100644 --- a/app/Http/Controllers/PaymentApiController.php +++ b/app/Http/Controllers/PaymentApiController.php @@ -58,8 +58,8 @@ class PaymentApiController extends BaseAPIController $payments = $payments->orderBy('created_at', 'desc')->paginate(); $paginator = $paginator->paginate(); + $transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer')); - $data = $this->createCollection($payments, $transformer, 'payments', $paginator); return $this->response($data); @@ -98,11 +98,8 @@ class PaymentApiController extends BaseAPIController $payment = Payment::scope($publicId)->withTrashed()->firstOrFail(); $this->paymentRepo->archive($payment); - $invoice = Invoice::scope($data['invoice_id'])->with('client')->with(['payments' => function($query) { - $query->withTrashed(); - }])->first(); - $transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); - $data = $this->createItem($invoice, $transformer, 'invoice'); + $transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($payment, $transformer, 'invoice'); return $this->response($data); } @@ -113,12 +110,17 @@ class PaymentApiController extends BaseAPIController return $error; } + /* $invoice = Invoice::scope($data['invoice_id'])->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) { $query->withTrashed(); }])->withTrashed()->first(); - $transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); - $data = $this->createItem($invoice, $transformer, 'invoice'); + */ + + $transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($payment, $transformer, 'invoice'); + return $this->response($data); + } @@ -175,13 +177,17 @@ class PaymentApiController extends BaseAPIController $this->contactMailer->sendPaymentConfirmation($payment); } + /* $invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) { $query->withTrashed(); }])->first(); - $transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); - $data = $this->createItem($invoice, $transformer, 'invoice'); + */ + + $transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($payment, $transformer, 'invoice'); return $this->response($data); + } /** @@ -214,12 +220,13 @@ class PaymentApiController extends BaseAPIController $this->paymentRepo->delete($payment); + /* $invoice = Invoice::scope($invoiceId)->with('client', 'invoice_items', 'invitations')->with(['payments' => function($query) { $query->withTrashed(); }])->first(); - - $transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer')); - $data = $this->createItem($invoice, $transformer, 'invoice'); + */ + $transformer = new PaymentTransformer(\Auth::user()->account, Input::get('serializer')); + $data = $this->createItem($payment, $transformer, 'invoice'); return $this->response($data); } diff --git a/app/Http/Controllers/PublicClientController.php b/app/Http/Controllers/PublicClientController.php index 834a89beac59..9d806061d88c 100644 --- a/app/Http/Controllers/PublicClientController.php +++ b/app/Http/Controllers/PublicClientController.php @@ -162,6 +162,27 @@ class PublicClientController extends BaseController return $paymentTypes; } + public function download($invitationKey) + { + if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) { + return response()->view('error', [ + 'error' => trans('texts.invoice_not_found'), + 'hideHeader' => true, + ]); + } + + $invoice = $invitation->invoice; + $pdfString = $invoice->getPDFString(); + + header('Content-Type: application/pdf'); + header('Content-Length: ' . strlen($pdfString)); + header('Content-disposition: attachment; filename="' . $invoice->getFileName() . '"'); + header('Cache-Control: public, must-revalidate, max-age=0'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + + return $pdfString; + } + public function dashboard() { if (!$invitation = $this->getInvitation()) { diff --git a/app/Http/Controllers/VendorApiController.php b/app/Http/Controllers/VendorApiController.php index 80236226dda0..e2cec175bada 100644 --- a/app/Http/Controllers/VendorApiController.php +++ b/app/Http/Controllers/VendorApiController.php @@ -48,6 +48,7 @@ class VendorApiController extends BaseAPIController { $vendors = Vendor::scope() ->with($this->getIncluded()) + ->withTrashed() ->orderBy('created_at', 'desc') ->paginate(); diff --git a/app/Http/routes.php b/app/Http/routes.php index 04a34b268726..f7bed59f6879 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -1,6 +1,5 @@ 'auth'], function() { Route::get('api/payments/{client_id?}', array('as'=>'api.payments', 'uses'=>'PaymentController@getDatatable')); Route::post('payments/bulk', 'PaymentController@bulk'); - Route::get('credits/{id}/edit', function() { - return View::make('header'); - }); Route::resource('credits', 'CreditController'); Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create'); Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable')); @@ -236,6 +233,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() Route::resource('products', 'ProductApiController'); Route::resource('tax_rates', 'TaxRateApiController'); Route::resource('users', 'UserApiController'); + Route::resource('expenses','ExpenseApiController'); // Vendor Route::resource('vendors', 'VendorApiController'); @@ -245,6 +243,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'api/v1'], function() }); // Redirects for legacy links +/* Route::get('/rocksteady', function() { return Redirect::to(NINJA_WEB_URL, 301); }); @@ -272,6 +271,7 @@ Route::get('/compare-online-invoicing{sites?}', function() { Route::get('/forgot_password', function() { return Redirect::to(NINJA_APP_URL.'/forgot', 301); }); +*/ if (!defined('CONTACT_EMAIL')) { define('CONTACT_EMAIL', Config::get('mail.from.address')); @@ -509,7 +509,7 @@ if (!defined('CONTACT_EMAIL')) { define('NINJA_GATEWAY_CONFIG', 'NINJA_GATEWAY_CONFIG'); define('NINJA_WEB_URL', 'https://www.invoiceninja.com'); define('NINJA_APP_URL', 'https://app.invoiceninja.com'); - define('NINJA_VERSION', '2.5.0.2'); + define('NINJA_VERSION', '2.5.0.3'); define('NINJA_DATE', '2000-01-01'); define('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'); diff --git a/app/Libraries/Utils.php b/app/Libraries/Utils.php index 53a586f804be..027f512a47df 100644 --- a/app/Libraries/Utils.php +++ b/app/Libraries/Utils.php @@ -62,17 +62,17 @@ class Utils return true; } - return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'] == 'true'; + return env('NINJA_PROD') == 'true'; } public static function isNinjaDev() { - return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'] == 'true'; + return env('NINJA_DEV') == 'true'; } public static function requireHTTPS() { - if (Request::root() === 'http://ninja.dev') { + if (Request::root() === 'http://ninja.dev:8000') { return false; } diff --git a/app/Models/Account.php b/app/Models/Account.php index 876804bf42a7..d3f64bb2eec0 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -164,6 +164,15 @@ class Account extends Eloquent return $this->belongsTo('App\Models\TaxRate'); } + public function expenses() + { + return $this->hasMany('App\Models\Expense','account_id','id')->withTrashed(); + } + + public function payments() + { + return $this->hasMany('App\Models\Payment','account_id','id')->withTrashed(); + } public function setIndustryIdAttribute($value) { diff --git a/app/Models/Client.php b/app/Models/Client.php index eae0eb1c148f..2167af0ddfc6 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -139,6 +139,10 @@ class Client extends EntityModel return $this->hasMany('App\Models\Credit'); } + public function expenses() + { + return $this->hasMany('App\Models\Expense','client_id','id')->withTrashed(); + } public function addContact($data, $isPrimary = false) { diff --git a/app/Models/Expense.php b/app/Models/Expense.php index 9fbd5a485c7d..2d1b8041d7ee 100644 --- a/app/Models/Expense.php +++ b/app/Models/Expense.php @@ -12,7 +12,7 @@ class Expense extends EntityModel use SoftDeletes; use PresentableTrait; - protected $dates = ['deleted_at','expense_date']; + protected $dates = ['deleted_at']; protected $presenter = 'App\Ninja\Presenters\ExpensePresenter'; protected $fillable = [ @@ -76,19 +76,9 @@ class Expense extends EntityModel return ENTITY_EXPENSE; } - public function apply($amount) + public function isExchanged() { - if ($amount > $this->balance) { - $applied = $this->balance; - $this->balance = 0; - } else { - $applied = $amount; - $this->balance = $this->balance - $amount; - } - - $this->save(); - - return $applied; + return $this->invoice_currency_id != $this->expense_currency_id; } } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index e93c8d3b07f5..4e15934efeb3 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -197,6 +197,11 @@ class Invoice extends EntityModel implements BalanceAffecting return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id'); } + public function expenses() + { + return $this->hasMany('App\Models\Expense','invoice_id','id')->withTrashed(); + } + public function markInvitationsSent($notify = false) { foreach ($this->invitations as $invitation) { @@ -728,42 +733,24 @@ class Invoice extends EntityModel implements BalanceAffecting $invitation = $this->invitations[0]; $link = $invitation->getLink(); + $key = env('PHANTOMJS_CLOUD_KEY'); $curl = curl_init(); - $jsonEncodedData = json_encode([ - 'url' => "{$link}?phantomjs=true", - 'renderType' => 'html', - 'outputAsJson' => false, - 'renderSettings' => [ - 'passThroughHeaders' => true, - ], - // 'delayTime' => 1000, - ]); - - $opts = [ - CURLOPT_URL => PHANTOMJS_CLOUD . env('PHANTOMJS_CLOUD_KEY') . '/', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_CUSTOMREQUEST => 'POST', - CURLOPT_POST => 1, - CURLOPT_POSTFIELDS => $jsonEncodedData, - CURLOPT_HTTPHEADER => [ - 'Content-Type: application/json', - 'Content-Length: '.strlen($jsonEncodedData) - ], - ]; - - curl_setopt_array($curl, $opts); - $response = curl_exec($curl); - curl_close($curl); - - $encodedString = strip_tags($response); - $pdfString = Utils::decodePDF($encodedString); - - if ( ! $pdfString || strlen($pdfString) < 200) { - Utils::logError("PhantomJSCloud - failed to create pdf: {$encodedString}"); + if (Utils::isNinjaDev()) { + $link = env('TEST_LINK'); } - return $pdfString; + $url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%22,renderType:%22html%22%7D"; + + $pdfString = file_get_contents($url); + $pdfString = strip_tags($pdfString); + + if ( ! $pdfString || strlen($pdfString) < 200) { + Utils::logError("PhantomJSCloud - failed to create pdf: {$pdfString}"); + return false; + } + + return Utils::decodePDF($pdfString); } } diff --git a/app/Models/User.php b/app/Models/User.php index f62e4a5846c6..9f66f0d2af8f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -107,6 +107,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon return $this->account->isPro(); } + public function isPaidPro() + { + return $this->isPro() && ! $this->isTrial(); + } + public function isTrial() { return $this->account->isTrial(); diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index a93fc6e5d7b4..ce21a6446364 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -124,6 +124,11 @@ class Vendor extends EntityModel return $this->belongsTo('App\Models\Industry'); } + public function expenses() + { + return $this->hasMany('App\Models\Expense','vendor_id','id'); + } + public function addVendorContact($data, $isPrimary = false) { $publicId = isset($data['public_id']) ? $data['public_id'] : false; diff --git a/app/Ninja/Mailers/ContactMailer.php b/app/Ninja/Mailers/ContactMailer.php index 8959602ce6a2..dc513c3e8822 100644 --- a/app/Ninja/Mailers/ContactMailer.php +++ b/app/Ninja/Mailers/ContactMailer.php @@ -41,6 +41,8 @@ class ContactMailer extends Mailer $client = $invoice->client; $account = $invoice->account; + $response = null; + if ($client->trashed()) { return trans('texts.email_errors.inactive_client'); } elseif ($invoice->trashed()) { diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php index 442df6ef2979..cfd312622ad3 100644 --- a/app/Ninja/Repositories/ExpenseRepository.php +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -64,6 +64,7 @@ class ExpenseRepository extends BaseRepository ->orWhere('contacts.is_primary', '=', null); }) ->select( + DB::raw('COALESCE(expenses.invoice_id, expenses.should_be_invoiced) expense_status_id'), 'expenses.account_id', 'expenses.amount', 'expenses.deleted_at', diff --git a/app/Ninja/Repositories/InvoiceRepository.php b/app/Ninja/Repositories/InvoiceRepository.php index 47cbf82bbad0..3c4072122d1f 100644 --- a/app/Ninja/Repositories/InvoiceRepository.php +++ b/app/Ninja/Repositories/InvoiceRepository.php @@ -418,8 +418,7 @@ class InvoiceRepository extends BaseRepository $expense->save(); } - if ($item['product_key']) { - $productKey = trim($item['product_key']); + if ($productKey = trim($item['product_key'])) { if (\Auth::user()->account->update_products && ! strtotime($productKey)) { $product = Product::findProductByKey($productKey); if (!$product) { diff --git a/app/Ninja/Transformers/AccountTransformer.php b/app/Ninja/Transformers/AccountTransformer.php index eed474346550..6a1c32f30e09 100644 --- a/app/Ninja/Transformers/AccountTransformer.php +++ b/app/Ninja/Transformers/AccountTransformer.php @@ -12,10 +12,14 @@ class AccountTransformer extends EntityTransformer { protected $defaultIncludes = [ 'users', - // 'clients', - 'invoices', 'products', - 'taxRates' + 'taxRates', + 'payments' + ]; + + protected $availableIncludes = [ + 'clients', + 'invoices', ]; public function includeUsers(Account $account) @@ -48,6 +52,12 @@ class AccountTransformer extends EntityTransformer return $this->includeCollection($account->tax_rates, $transformer, 'taxRates'); } + public function includePayments(Account $account) + { + $transformer = new PaymentTransformer($account, $this->serializer); + return $this->includeCollection($account->payments, $transformer, 'payments'); + } + public function transform(Account $account) { return [ diff --git a/app/Ninja/Transformers/ClientTransformer.php b/app/Ninja/Transformers/ClientTransformer.php index 83ee7e56238b..f7d786a2ee25 100644 --- a/app/Ninja/Transformers/ClientTransformer.php +++ b/app/Ninja/Transformers/ClientTransformer.php @@ -47,6 +47,7 @@ class ClientTransformer extends EntityTransformer protected $availableIncludes = [ 'invoices', 'credits', + 'expenses', ]; public function includeContacts(Client $client) @@ -67,6 +68,13 @@ class ClientTransformer extends EntityTransformer return $this->includeCollection($client->credits, $transformer, ENTITY_CREDIT); } + public function includeExpenses(Client $client) + { + $transformer = new ExpenseTransformer($this->account, $this->serializer); + return $this->includeCollection($client->expenses, $transformer, ENTITY_EXPENSE); + } + + public function transform(Client $client) { return [ diff --git a/app/Ninja/Transformers/ExpenseTransformer.php b/app/Ninja/Transformers/ExpenseTransformer.php new file mode 100644 index 000000000000..a6d358d15766 --- /dev/null +++ b/app/Ninja/Transformers/ExpenseTransformer.php @@ -0,0 +1,33 @@ + (int) $expense->public_id, + 'private_notes' => $expense->private_notes, + 'public_notes' => $expense->public_notes, + 'should_be_invoiced' => (bool) $expense->should_be_invoiced, + 'updated_at' => $this->getTimestamp($expense->updated_at), + 'archived_at' => $this->getTimestamp($expense->deleted_at), + 'transaction_id' => $expense->transaction_id, + 'bank_id' => $expense->bank_id, + 'expense_currency_id' => (int) $expense->expense_currency_id, + 'account_key' => $this->account->account_key, + 'amount' => (float) $expense->amount, + 'expense_date' => $expense->expense_date, + 'exchange_rate' => (float) $expense->exchange_rate, + 'invoice_currency_id' => (int) $expense->invoice_currency_id, + 'is_deleted' => (bool) $expense->is_deleted, + 'client_id' => isset($expense->client->public_id) ? (int) $expense->client->public_id : null, + 'invoice_id' => isset($expense->invoice->public_id) ? (int) $expense->invoice->public_id : null, + 'vendor_id' => isset($expense->vendor->public_id) ? (int) $expense->vendor->public_id : null, + ]; + } +} \ No newline at end of file diff --git a/app/Ninja/Transformers/InvoiceTransformer.php b/app/Ninja/Transformers/InvoiceTransformer.php index 464fc5e3fad7..b67635386d8e 100644 --- a/app/Ninja/Transformers/InvoiceTransformer.php +++ b/app/Ninja/Transformers/InvoiceTransformer.php @@ -28,6 +28,7 @@ class InvoiceTransformer extends EntityTransformer 'invitations', 'payments', 'client', + 'expenses', ]; public function includeInvoiceItems(Invoice $invoice) @@ -51,9 +52,16 @@ class InvoiceTransformer extends EntityTransformer public function includeClient(Invoice $invoice) { $transformer = new ClientTransformer($this->account, $this->serializer); - return $this->includeItem($invoice->client, $transformer, 'client'); + return $this->includeItem($invoice->client, $transformer, ENTITY_CLIENT); } + public function includeExpenses(Invoice $invoice) + { + $transformer = new ExpenseTransformer($this->account, $this->serializer); + return $this->includeCollection($invoice->expenses, $transformer, ENTITY_EXPENSE); + } + + public function transform(Invoice $invoice) { return [ diff --git a/app/Ninja/Transformers/PaymentTransformer.php b/app/Ninja/Transformers/PaymentTransformer.php index 22642259751a..a1de09cd6ff4 100644 --- a/app/Ninja/Transformers/PaymentTransformer.php +++ b/app/Ninja/Transformers/PaymentTransformer.php @@ -57,6 +57,7 @@ class PaymentTransformer extends EntityTransformer 'archived_at' => $this->getTimestamp($payment->deleted_at), 'is_deleted' => (bool) $payment->is_deleted, 'payment_type_id' => (int) $payment->payment_type_id, + 'invoice_id' => (int) $payment->invoice->public_id, ]; } } \ No newline at end of file diff --git a/app/Ninja/Transformers/VendorContactTransformer.php b/app/Ninja/Transformers/VendorContactTransformer.php index 0166883aba4d..3b75aee53a28 100644 --- a/app/Ninja/Transformers/VendorContactTransformer.php +++ b/app/Ninja/Transformers/VendorContactTransformer.php @@ -17,7 +17,6 @@ class VendorContactTransformer extends EntityTransformer 'archived_at' => $this->getTimestamp($contact->deleted_at), 'is_primary' => (bool) $contact->is_primary, 'phone' => $contact->phone, - 'last_login' => $contact->last_login, 'account_key' => $this->account->account_key, ]; } diff --git a/app/Ninja/Transformers/VendorTransformer.php b/app/Ninja/Transformers/VendorTransformer.php index c1714b27a120..f0b8fd0415f0 100644 --- a/app/Ninja/Transformers/VendorTransformer.php +++ b/app/Ninja/Transformers/VendorTransformer.php @@ -36,14 +36,15 @@ class VendorTransformer extends EntityTransformer */ protected $availableIncludes = [ - 'contacts', + 'vendorContacts', 'invoices', + 'expenses', ]; - public function includeContacts(Vendor $vendor) + public function includeVendorContacts(Vendor $vendor) { - $transformer = new ContactTransformer($this->account, $this->serializer); - return $this->includeCollection($vendor->contacts, $transformer, ENTITY_CONTACT); + $transformer = new VendorContactTransformer($this->account, $this->serializer); + return $this->includeCollection($vendor->vendorContacts, $transformer, ENTITY_CONTACT); } public function includeInvoices(Vendor $vendor) @@ -52,6 +53,12 @@ class VendorTransformer extends EntityTransformer return $this->includeCollection($vendor->invoices, $transformer, ENTITY_INVOICE); } + public function includeExpenses(Vendor $vendor) + { + $transformer = new ExpenseTransformer($this->account, $this->serializer); + return $this->includeCollection($vendor->expenses, $transformer, ENTITY_EXPENSE); + } + public function transform(Vendor $vendor) { return [ diff --git a/app/Services/DatatableService.php b/app/Services/DatatableService.php index 5a1fd279aebb..63fe47005bab 100644 --- a/app/Services/DatatableService.php +++ b/app/Services/DatatableService.php @@ -41,6 +41,7 @@ class DatatableService private function createDropdown($entityType, $table, $actions) { $table->addColumn('dropdown', function ($model) use ($entityType, $actions) { + $hasAction = false; $str = '
'; if (property_exists($model, 'is_deleted') && $model->is_deleted) { @@ -70,6 +71,7 @@ class DatatableService if ($visible($model)) { $str .= "
  • {$value}
  • "; $lastIsDivider = false; + $hasAction = true; } } elseif ( ! $lastIsDivider) { $str .= "
  • "; @@ -77,6 +79,10 @@ class DatatableService } } + if ( ! $hasAction) { + return ''; + } + if ( ! $lastIsDivider) { $str .= "
  • "; } diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php index 491c0e7957d8..e8f4d591d75d 100644 --- a/app/Services/ExpenseService.php +++ b/app/Services/ExpenseService.php @@ -106,7 +106,7 @@ class ExpenseService extends BaseService } ], [ - 'invoice_id', + 'expense_status_id', function ($model) { return self::getStatusLabel($model->invoice_id, $model->should_be_invoiced); } diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 78e6a031c1f7..66e94e43dced 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -273,10 +273,13 @@ class PaymentService extends BaseService // submit purchase/get response $response = $gateway->purchase($details)->send(); - $ref = $response->getTransactionReference(); - - // create payment record - return $this->createPayment($invitation, $accountGateway, $ref); + + if ($response->isSuccessful()) { + $ref = $response->getTransactionReference(); + return $this->createPayment($invitation, $accountGateway, $ref); + } else { + return false; + } } public function getDatatable($clientPublicId, $search) diff --git a/codeception.yml b/codeception.yml index dfbb0d47bbb3..87ecbd1fc806 100644 --- a/codeception.yml +++ b/codeception.yml @@ -19,7 +19,7 @@ extensions: modules: config: Db: - dsn: 'mysql:dbname=ninja;host=localhost;' + dsn: 'mysql:dbname=ninja;host=127.0.0.1;' user: 'ninja' password: 'ninja' dump: tests/_data/dump.sql diff --git a/composer.json b/composer.json index a338165ea034..68d48cd64803 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "omnipay/bitpay": "dev-master", "guzzlehttp/guzzle": "~6.0", "laravelcollective/html": "~5.0", - "wildbit/laravel-postmark-provider": "dev-master", + "wildbit/laravel-postmark-provider": "2.0", "Dwolla/omnipay-dwolla": "dev-master", "laravel/socialite": "~2.0", "simshaun/recurr": "dev-master", @@ -69,7 +69,7 @@ "require-dev": { "phpunit/phpunit": "~4.0", "phpspec/phpspec": "~2.1", - "codeception/codeception": "2.1.2", + "codeception/codeception": "*", "codeception/c3": "~2.0", "fzaninotto/faker": "^1.5" }, diff --git a/composer.lock b/composer.lock index 28ae475dee18..59574931678e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "6e219bb4f5ffaf8423177bd6fccc89f8", - "content-hash": "4778ab164bfb93c4af1bb51c4320ea4c", + "hash": "fceb9a043eac244cb01d8e8378e6d66a", + "content-hash": "f717dc8e67caa65002f0f0689d4a5478", "packages": [ { "name": "agmscode/omnipay-agms", @@ -435,12 +435,12 @@ "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "aa8f772a46c35ecdcb7119f38772ac2ec952a941" + "reference": "4b8ba85b346fc9962521661aa639911535b2bb3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/aa8f772a46c35ecdcb7119f38772ac2ec952a941", - "reference": "aa8f772a46c35ecdcb7119f38772ac2ec952a941", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/4b8ba85b346fc9962521661aa639911535b2bb3f", + "reference": "4b8ba85b346fc9962521661aa639911535b2bb3f", "shasum": "" }, "require": { @@ -490,7 +490,7 @@ "phpstorm", "sublime" ], - "time": "2016-01-22 13:33:15" + "time": "2016-01-28 08:19:58" }, { "name": "cardgate/omnipay-cardgate", @@ -2555,12 +2555,12 @@ "source": { "type": "git", "url": "https://github.com/slampenny/Swaggervel.git", - "reference": "ea47fafde4984278e27a8044a1b1b0bcfd79130c" + "reference": "e026d72cacec8b2db8b2510179d73042f5e87bb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slampenny/Swaggervel/zipball/ea47fafde4984278e27a8044a1b1b0bcfd79130c", - "reference": "ea47fafde4984278e27a8044a1b1b0bcfd79130c", + "url": "https://api.github.com/repos/slampenny/Swaggervel/zipball/e026d72cacec8b2db8b2510179d73042f5e87bb9", + "reference": "e026d72cacec8b2db8b2510179d73042f5e87bb9", "shasum": "" }, "require": { @@ -2592,7 +2592,7 @@ "laravel", "swagger" ], - "time": "2015-08-18 15:33:39" + "time": "2016-01-25 15:38:17" }, { "name": "jsanc623/phpbenchtime", @@ -2786,16 +2786,16 @@ }, { "name": "laravel/framework", - "version": "v5.0.34", + "version": "v5.0.35", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "98dbaafe8e2781f86b1b858f8610be0d7318b153" + "reference": "37151cf533f468e2227605e4b9ac596154f6b92b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/98dbaafe8e2781f86b1b858f8610be0d7318b153", - "reference": "98dbaafe8e2781f86b1b858f8610be0d7318b153", + "url": "https://api.github.com/repos/laravel/framework/zipball/37151cf533f468e2227605e4b9ac596154f6b92b", + "reference": "37151cf533f468e2227605e4b9ac596154f6b92b", "shasum": "" }, "require": { @@ -2908,7 +2908,7 @@ "framework", "laravel" ], - "time": "2015-12-04 23:20:49" + "time": "2016-02-02 14:55:52" }, { "name": "laravel/socialite", @@ -3284,12 +3284,12 @@ "source": { "type": "git", "url": "https://github.com/lokielse/omnipay-alipay.git", - "reference": "f39ce21a5cbfe5c7cd4108d264b398dbd42be05b" + "reference": "0b091a199f1ffce95582f5e11efcf4a8ffd90ec8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lokielse/omnipay-alipay/zipball/f39ce21a5cbfe5c7cd4108d264b398dbd42be05b", - "reference": "f39ce21a5cbfe5c7cd4108d264b398dbd42be05b", + "url": "https://api.github.com/repos/lokielse/omnipay-alipay/zipball/0b091a199f1ffce95582f5e11efcf4a8ffd90ec8", + "reference": "0b091a199f1ffce95582f5e11efcf4a8ffd90ec8", "shasum": "" }, "require": { @@ -3325,7 +3325,7 @@ "payment", "purchase" ], - "time": "2016-01-19 15:08:12" + "time": "2016-01-27 17:06:44" }, { "name": "maatwebsite/excel", @@ -3701,23 +3701,23 @@ }, { "name": "mtdowling/cron-expression", - "version": "v1.0.4", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/mtdowling/cron-expression.git", - "reference": "fd92e883195e5dfa77720b1868cf084b08be4412" + "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/fd92e883195e5dfa77720b1868cf084b08be4412", - "reference": "fd92e883195e5dfa77720b1868cf084b08be4412", + "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/c9ee7886f5a12902b225a1a12f36bb45f9ab89e5", + "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5", "shasum": "" }, "require": { "php": ">=5.3.2" }, "require-dev": { - "phpunit/phpunit": "4.*" + "phpunit/phpunit": "~4.0|~5.0" }, "type": "library", "autoload": { @@ -3741,7 +3741,7 @@ "cron", "schedule" ], - "time": "2015-01-11 23:07:46" + "time": "2016-01-26 21:23:30" }, { "name": "nesbot/carbon", @@ -5279,16 +5279,16 @@ }, { "name": "omnipay/sagepay", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-sagepay.git", - "reference": "4208d23b33b2f8a59176e44ad22d304c461ecb62" + "reference": "ca992b28a0d7ec7dbf218852dab36ec309dee07e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-sagepay/zipball/4208d23b33b2f8a59176e44ad22d304c461ecb62", - "reference": "4208d23b33b2f8a59176e44ad22d304c461ecb62", + "url": "https://api.github.com/repos/thephpleague/omnipay-sagepay/zipball/ca992b28a0d7ec7dbf218852dab36ec309dee07e", + "reference": "ca992b28a0d7ec7dbf218852dab36ec309dee07e", "shasum": "" }, "require": { @@ -5334,7 +5334,7 @@ "sage pay", "sagepay" ], - "time": "2016-01-12 12:43:31" + "time": "2016-02-07 13:25:23" }, { "name": "omnipay/securepay", @@ -5509,16 +5509,16 @@ }, { "name": "omnipay/worldpay", - "version": "v2.1.1", + "version": "v2.2", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-worldpay.git", - "reference": "5353f02b7f800b93d8aeae606d6df09afa538457" + "reference": "26ead4ca2c6ec45c9a3b3dad0be8880e0b1df083" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-worldpay/zipball/5353f02b7f800b93d8aeae606d6df09afa538457", - "reference": "5353f02b7f800b93d8aeae606d6df09afa538457", + "url": "https://api.github.com/repos/thephpleague/omnipay-worldpay/zipball/26ead4ca2c6ec45c9a3b3dad0be8880e0b1df083", + "reference": "26ead4ca2c6ec45c9a3b3dad0be8880e0b1df083", "shasum": "" }, "require": { @@ -5562,20 +5562,20 @@ "payment", "worldpay" ], - "time": "2014-09-17 00:37:18" + "time": "2016-01-28 12:55:58" }, { "name": "paragonie/random_compat", - "version": "1.1.5", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7" + "reference": "b0e69d10852716b2ccbdff69c75c477637220790" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/dd8998b7c846f6909f4e7a5f67fabebfc412a4f7", - "reference": "dd8998b7c846f6909f4e7a5f67fabebfc412a4f7", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/b0e69d10852716b2ccbdff69c75c477637220790", + "reference": "b0e69d10852716b2ccbdff69c75c477637220790", "shasum": "" }, "require": { @@ -5610,7 +5610,7 @@ "pseudorandom", "random" ], - "time": "2016-01-06 13:31:20" + "time": "2016-02-06 03:52:05" }, { "name": "patricktalmadge/bootstrapper", @@ -6158,23 +6158,27 @@ }, { "name": "symfony/class-loader", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "6294f616bb9888ba2e13c8bfdcc4d306c1c95da7" + "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/6294f616bb9888ba2e13c8bfdcc4d306c1c95da7", - "reference": "6294f616bb9888ba2e13c8bfdcc4d306c1c95da7", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/92e7cf1af2bc1695daabb4ac972db169606e9030", + "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030", "shasum": "" }, "require": { "php": ">=5.5.9" }, "require-dev": { - "symfony/finder": "~2.8|~3.0" + "symfony/finder": "~2.8|~3.0", + "symfony/polyfill-apcu": "~1.1" + }, + "suggest": { + "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" }, "type": "library", "extra": { @@ -6206,7 +6210,7 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2015-12-05 17:45:07" + "time": "2016-02-03 09:33:23" }, { "name": "symfony/console", @@ -6268,25 +6272,25 @@ }, { "name": "symfony/css-selector", - "version": "v2.8.2", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ac06d8173bd80790536c0a4a634a7d705b91f54f" + "reference": "6605602690578496091ac20ec7a5cbd160d4dff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ac06d8173bd80790536c0a4a634a7d705b91f54f", - "reference": "ac06d8173bd80790536c0a4a634a7d705b91f54f", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/6605602690578496091ac20ec7a5cbd160d4dff4", + "reference": "6605602690578496091ac20ec7a5cbd160d4dff4", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -6317,7 +6321,7 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2016-01-03 15:33:41" + "time": "2016-01-27 05:14:46" }, { "name": "symfony/debug", @@ -6673,16 +6677,16 @@ }, { "name": "symfony/polyfill-php56", - "version": "v1.0.1", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "e2e77609a9e2328eb370fbb0e0d8b2000ebb488f" + "reference": "4d891fff050101a53a4caabb03277284942d1ad9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e2e77609a9e2328eb370fbb0e0d8b2000ebb488f", - "reference": "e2e77609a9e2328eb370fbb0e0d8b2000ebb488f", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/4d891fff050101a53a4caabb03277284942d1ad9", + "reference": "4d891fff050101a53a4caabb03277284942d1ad9", "shasum": "" }, "require": { @@ -6692,7 +6696,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "autoload": { @@ -6725,20 +6729,20 @@ "portable", "shim" ], - "time": "2015-12-18 15:10:25" + "time": "2016-01-20 09:13:37" }, { "name": "symfony/polyfill-util", - "version": "v1.0.1", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "4271c55cbc0a77b2641f861b978123e46b3da969" + "reference": "8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4271c55cbc0a77b2641f861b978123e46b3da969", - "reference": "4271c55cbc0a77b2641f861b978123e46b3da969", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4", + "reference": "8de62801aa12bc4dfcf85eef5d21981ae7bb3cc4", "shasum": "" }, "require": { @@ -6747,7 +6751,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "autoload": { @@ -6777,7 +6781,7 @@ "polyfill", "shim" ], - "time": "2015-11-04 20:28:58" + "time": "2016-01-20 09:13:37" }, { "name": "symfony/process", @@ -7374,7 +7378,7 @@ }, { "name": "wildbit/laravel-postmark-provider", - "version": "dev-master", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/wildbit/laravel-postmark-provider.git", @@ -7448,16 +7452,16 @@ }, { "name": "zircote/swagger-php", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "c19af4edcc13c00e82fabeee926335b1fe1d92e9" + "reference": "0dfc289d53bad4a2bd193adc8d4bd058029ab417" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/c19af4edcc13c00e82fabeee926335b1fe1d92e9", - "reference": "c19af4edcc13c00e82fabeee926335b1fe1d92e9", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/0dfc289d53bad4a2bd193adc8d4bd058029ab417", + "reference": "0dfc289d53bad4a2bd193adc8d4bd058029ab417", "shasum": "" }, "require": { @@ -7466,6 +7470,7 @@ "symfony/finder": "*" }, "require-dev": { + "squizlabs/php_codesniffer": "2.*", "zendframework/zend-form": "*" }, "bin": [ @@ -7504,26 +7509,26 @@ "rest", "service discovery" ], - "time": "2016-01-15 09:39:28" + "time": "2016-02-13 15:39:11" } ], "packages-dev": [ { "name": "codeception/c3", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/Codeception/c3.git", - "reference": "b866ca528474ddcf74147531cc4e50b80257a5f8" + "reference": "dc4d39b36d585c2eda58129407e78855ea67b1ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/c3/zipball/b866ca528474ddcf74147531cc4e50b80257a5f8", - "reference": "b866ca528474ddcf74147531cc4e50b80257a5f8", + "url": "https://api.github.com/repos/Codeception/c3/zipball/dc4d39b36d585c2eda58129407e78855ea67b1ca", + "reference": "dc4d39b36d585c2eda58129407e78855ea67b1ca", "shasum": "" }, "require": { - "composer-plugin-api": "1.0.0", + "composer-plugin-api": "^1.0", "php": ">=5.4.0" }, "type": "composer-plugin", @@ -7556,37 +7561,37 @@ "code coverage", "codecoverage" ], - "time": "2015-11-28 10:17:10" + "time": "2016-02-09 23:31:08" }, { "name": "codeception/codeception", - "version": "2.1.2", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "521adbb2ee34e9debdd8508a2c41ab2b5c2f042b" + "reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/521adbb2ee34e9debdd8508a2c41ab2b5c2f042b", - "reference": "521adbb2ee34e9debdd8508a2c41ab2b5c2f042b", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/b199941f5e59d1e7fd32d78296c8ab98db873d89", + "reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", "facebook/webdriver": ">=1.0.1", - "guzzlehttp/guzzle": ">=4.0|<7.0", + "guzzlehttp/guzzle": ">=4.1.4 <7.0", "guzzlehttp/psr7": "~1.0", "php": ">=5.4.0", "phpunit/phpunit": "~4.8.0", - "symfony/browser-kit": "~2.4", - "symfony/console": "~2.4", - "symfony/css-selector": "~2.4", - "symfony/dom-crawler": "~2.4,!=2.4.5", - "symfony/event-dispatcher": "~2.4", - "symfony/finder": "~2.4", - "symfony/yaml": "~2.4" + "symfony/browser-kit": ">=2.4|<3.1", + "symfony/console": ">=2.4|<3.1", + "symfony/css-selector": ">=2.4|<3.1", + "symfony/dom-crawler": ">=2.4|<3.1", + "symfony/event-dispatcher": ">=2.4|<3.1", + "symfony/finder": ">=2.4|<3.1", + "symfony/yaml": ">=2.4|<3.1" }, "require-dev": { "codeception/specify": "~0.3", @@ -7636,7 +7641,7 @@ "functional testing", "unit testing" ], - "time": "2015-08-09 13:48:55" + "time": "2016-02-09 22:27:48" }, { "name": "doctrine/instantiator", @@ -7901,22 +7906,24 @@ }, { "name": "phpspec/prophecy", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", - "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1" + "sebastian/comparator": "~1.1", + "sebastian/recursion-context": "~1.0" }, "require-dev": { "phpspec/phpspec": "~2.0" @@ -7924,7 +7931,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.5.x-dev" } }, "autoload": { @@ -7957,7 +7964,7 @@ "spy", "stub" ], - "time": "2015-08-13 10:07:40" + "time": "2016-02-15 07:46:21" }, { "name": "phpunit/php-code-coverage", @@ -8201,16 +8208,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.21", + "version": "4.8.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ea76b17bced0500a28098626b84eda12dbcf119c" + "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c", - "reference": "ea76b17bced0500a28098626b84eda12dbcf119c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483", + "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483", "shasum": "" }, "require": { @@ -8269,7 +8276,7 @@ "testing", "xunit" ], - "time": "2015-12-12 07:45:58" + "time": "2016-02-11 14:56:33" }, { "name": "phpunit/phpunit-mock-objects", @@ -8700,25 +8707,25 @@ }, { "name": "symfony/browser-kit", - "version": "v2.8.2", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "a93dffaf763182acad12a4c42c7efc372899891e" + "reference": "dde849a0485b70a24b36f826ed3fb95b904d80c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a93dffaf763182acad12a4c42c7efc372899891e", - "reference": "a93dffaf763182acad12a4c42c7efc372899891e", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/dde849a0485b70a24b36f826ed3fb95b904d80c3", + "reference": "dde849a0485b70a24b36f826ed3fb95b904d80c3", "shasum": "" }, "require": { - "php": ">=5.3.9", - "symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0" + "php": ">=5.5.9", + "symfony/dom-crawler": "~2.8|~3.0" }, "require-dev": { - "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0", - "symfony/process": "~2.3.34|~2.7,>=2.7.6|~3.0.0" + "symfony/css-selector": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" }, "suggest": { "symfony/process": "" @@ -8726,7 +8733,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -8753,28 +8760,28 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2016-01-12 17:46:01" + "time": "2016-01-27 11:34:55" }, { "name": "symfony/dom-crawler", - "version": "v2.8.2", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "650d37aacb1fa0dcc24cced483169852b3a0594e" + "reference": "b693a9650aa004576b593ff2e91ae749dc90123d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/650d37aacb1fa0dcc24cced483169852b3a0594e", - "reference": "650d37aacb1fa0dcc24cced483169852b3a0594e", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d", + "reference": "b693a9650aa004576b593ff2e91ae749dc90123d", "shasum": "" }, "require": { - "php": ">=5.3.9", + "php": ">=5.5.9", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/css-selector": "~2.8|~3.0.0" + "symfony/css-selector": "~2.8|~3.0" }, "suggest": { "symfony/css-selector": "" @@ -8782,7 +8789,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -8809,20 +8816,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2016-01-03 15:33:41" + "time": "2016-01-25 09:56:57" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.0.1", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25" + "reference": "1289d16209491b584839022f29257ad859b8532d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25", - "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d", + "reference": "1289d16209491b584839022f29257ad859b8532d", "shasum": "" }, "require": { @@ -8834,7 +8841,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "autoload": { @@ -8868,29 +8875,29 @@ "portable", "shim" ], - "time": "2015-11-20 09:19:13" + "time": "2016-01-20 09:13:37" }, { "name": "symfony/yaml", - "version": "v2.8.2", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8" + "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/34c8a4b51e751e7ea869b8262f883d008a2b81b8", - "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8", + "url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a", + "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -8917,7 +8924,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-01-13 10:28:07" + "time": "2016-02-02 13:44:19" } ], "aliases": [], @@ -8935,7 +8942,6 @@ "alfaproject/omnipay-neteller": 20, "alfaproject/omnipay-skrill": 20, "omnipay/bitpay": 20, - "wildbit/laravel-postmark-provider": 20, "dwolla/omnipay-dwolla": 20, "simshaun/recurr": 20, "meebio/omnipay-creditcall": 20, diff --git a/database/seeds/PaymentLibrariesSeeder.php b/database/seeds/PaymentLibrariesSeeder.php index 58015b073206..d4d01c76fd92 100644 --- a/database/seeds/PaymentLibrariesSeeder.php +++ b/database/seeds/PaymentLibrariesSeeder.php @@ -126,6 +126,7 @@ class PaymentLibrariesSeeder extends Seeder ['name' => 'Bulgarian Lev', 'code' => 'BGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'], ['name' => 'Aruban Florin', 'code' => 'AWG', 'symbol' => 'Afl. ', 'precision' => '2', 'thousand_separator' => ' ', 'decimal_separator' => '.'], ['name' => 'Turkish Lira', 'code' => 'TRY', 'symbol' => 'TL ', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','], + ['name' => 'Romanian New Leu', 'code' => 'RON', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ]; foreach ($currencies as $currency) { diff --git a/public/.htaccess b/public/.htaccess index 77827ae70510..c9a6c555964e 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -12,4 +12,8 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] + + # In case of running InvoiceNinja in a Subdomain like invoiceninja.example.com, + # you have to enablel the following line: + # RewriteBase / diff --git a/readme.md b/readme.md index d9f7f05a2bbc..1addcb2fd9d1 100644 --- a/readme.md +++ b/readme.md @@ -5,15 +5,12 @@ # Invoice Ninja ### [https://www.invoiceninja.com](https://www.invoiceninja.com) +[![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=develop)](https://travis-ci.org/invoiceninja/invoiceninja) [![Join the chat at https://gitter.im/hillelcoren/invoice-ninja](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -### Referral Program -* $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/) - -### Reseller Program -There are two options: -* 10% of revenue -* $1,000 for a site limited to 1,000 users +### Affiliates Programs +* Referral program (we pay you): $100 per signup paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/) +* White-label reseller (you pay us): 10% of revenue with a $100 sign up fee ### Installation Options * [Self-Host Zip](https://www.invoiceninja.com/knowledgebase/self-host/) - Free @@ -26,6 +23,10 @@ There are two options: * MCrypt PHP Extension * MySQL +### Recommended Providers +* [Stripe](https://stripe.com/) +* [Postmark](https://postmarkapp.com/) + ### Features * Built using Laravel 5 * Live PDF generation using [pdfmake](http://pdfmake.org/) @@ -41,10 +42,6 @@ There are two options: * Custom email templates * [D3.js](http://d3js.org/) visualizations -### Recommended Providers -* [Stripe](https://stripe.com/) -* [Postmark](https://postmarkapp.com/) - ### Documentation * [Ubuntu and Apache](http://blog.technerdservices.com/index.php/2015/04/techpop-how-to-install-invoice-ninja-on-ubuntu-14-04/) * [Debian and Nginx](https://www.rosehosting.com/blog/install-invoice-ninja-on-a-debian-7-vps/) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 114022662c69..0c9bfac16536 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1,8 +1,6 @@ 'Organization', 'name' => 'Name', 'website' => 'Website', @@ -25,8 +23,6 @@ return array( 'size_id' => 'Company Size', 'industry_id' => 'Industry', 'private_notes' => 'Private Notes', - - // invoice 'invoice' => 'Invoice', 'client' => 'Client', 'invoice_date' => 'Invoice Date', @@ -50,7 +46,6 @@ return array( 'invoice_design_id' => 'Design', 'terms' => 'Terms', 'your_invoice' => 'Your Invoice', - 'remove_contact' => 'Remove contact', 'add_contact' => 'Add contact', 'create_new_client' => 'Create new client', @@ -74,8 +69,6 @@ return array( 'settings' => 'Settings', 'enable_invoice_tax' => 'Enable specifying an invoice tax', 'enable_line_item_tax' => 'Enable specifying line item taxes', - - // navigation 'dashboard' => 'Dashboard', 'clients' => 'Clients', 'invoices' => 'Invoices', @@ -100,8 +93,6 @@ return array( 'provide_email' => 'Please provide a valid email address', 'powered_by' => 'Powered by', 'no_items' => 'No items', - - // recurring invoices 'recurring_invoices' => 'Recurring Invoices', 'recurring_help' => '

    Automatically send clients the same invoices weekly, bi-monthly, monthly, quarterly or annually.

    Use :MONTH, :QUARTER or :YEAR for dynamic dates. Basic math works as well, for example :MONTH-1.

    @@ -111,8 +102,6 @@ return array(
  • ":YEAR+1 yearly subscription" => "2015 Yearly Subscription"
  • "Retainer payment for :QUARTER+1" => "Retainer payment for Q2"
  • ', - - // dashboard 'in_total_revenue' => 'in total revenue', 'billed_client' => 'billed client', 'billed_clients' => 'billed clients', @@ -121,8 +110,6 @@ return array( 'invoices_past_due' => 'Invoices Past Due', 'upcoming_invoices' => 'Upcoming Invoices', 'average_invoice' => 'Average Invoice', - - // list pages 'archive' => 'Archive', 'delete' => 'Delete', 'archive_client' => 'Archive Client', @@ -158,8 +145,6 @@ return array( 'select' => 'Select', 'edit_client' => 'Edit Client', 'edit_invoice' => 'Edit Invoice', - - // client view page 'create_invoice' => 'Create Invoice', 'enter_credit' => 'Enter Credit', 'last_logged_in' => 'Last logged in', @@ -171,12 +156,8 @@ return array( 'message' => 'Message', 'adjustment' => 'Adjustment', 'are_you_sure' => 'Are you sure?', - - // payment pages 'payment_type_id' => 'Payment Type', 'amount' => 'Amount', - - // account/company pages 'work_email' => 'Email', 'language_id' => 'Language', 'timezone_id' => 'Timezone', @@ -206,13 +187,9 @@ return array( 'client_view_styling' => 'Client View Styling', 'pdf_email_attachment' => 'Attach PDFs', 'custom_css' => 'Custom CSS', - - //import CSV data pages 'import_clients' => 'Import Client Data', 'csv_file' => 'CSV file', 'export_clients' => 'Export Client Data', - - // application messages 'created_client' => 'Successfully created client', 'created_clients' => 'Successfully created :count client(s)', 'updated_settings' => 'Successfully updated settings', @@ -223,14 +200,12 @@ return array( 'payment_error' => 'There was an error processing your payment. Please try again later.', 'registration_required' => 'Please sign up to email an invoice', 'confirmation_required' => 'Please confirm your email address', - 'updated_client' => 'Successfully updated client', 'created_client' => 'Successfully created client', 'archived_client' => 'Successfully archived client', 'archived_clients' => 'Successfully archived :count clients', 'deleted_client' => 'Successfully deleted client', 'deleted_clients' => 'Successfully deleted :count clients', - 'updated_invoice' => 'Successfully updated invoice', 'created_invoice' => 'Successfully created invoice', 'cloned_invoice' => 'Successfully cloned invoice', @@ -240,7 +215,6 @@ return array( 'archived_invoices' => 'Successfully archived :count invoices', 'deleted_invoice' => 'Successfully deleted invoice', 'deleted_invoices' => 'Successfully deleted :count invoices', - 'created_payment' => 'Successfully created payment', 'created_payments' => 'Successfully created :count payment(s)', 'archived_payment' => 'Successfully archived payment', @@ -248,22 +222,18 @@ return array( 'deleted_payment' => 'Successfully deleted payment', 'deleted_payments' => 'Successfully deleted :count payments', 'applied_payment' => 'Successfully applied payment', - 'created_credit' => 'Successfully created credit', 'archived_credit' => 'Successfully archived credit', 'archived_credits' => 'Successfully archived :count credits', 'deleted_credit' => 'Successfully deleted credit', 'deleted_credits' => 'Successfully deleted :count credits', 'imported_file' => 'Successfully imported file', - 'updated_vendor' => 'Successfully updated vendor', 'created_vendor' => 'Successfully created vendor', 'archived_vendor' => 'Successfully archived vendor', 'archived_vendors' => 'Successfully archived :count vendors', 'deleted_vendor' => 'Successfully deleted vendor', 'deleted_vendors' => 'Successfully deleted :count vendors', - - // Emails 'confirmation_subject' => 'Invoice Ninja Account Confirmation', 'confirmation_header' => 'Account Confirmation', 'confirmation_message' => 'Please access the link below to confirm your account.', @@ -274,7 +244,6 @@ return array( 'email_salutation' => 'Dear :name,', 'email_signature' => 'Regards,', 'email_from' => 'The Invoice Ninja Team', - 'user_email_footer' => 'To adjust your email notification settings please visit '.SITE_URL.'/settings/notifications', 'invoice_link_message' => 'To view the invoice click the link below:', 'notification_invoice_paid_subject' => 'Invoice :invoice was paid by :client', 'notification_invoice_sent_subject' => 'Invoice :invoice was sent to :client', @@ -283,32 +252,11 @@ return array( 'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.', 'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.', 'reset_password' => 'You can reset your account password by clicking the following button:', - 'reset_password_footer' => 'If you did not request this password reset please email our support: '.CONTACT_EMAIL, - - // Payment page 'secure_payment' => 'Secure Payment', 'card_number' => 'Card Number', 'expiration_month' => 'Expiration Month', 'expiration_year' => 'Expiration Year', 'cvv' => 'CVV', - - // Security alerts - 'security' => [ - 'too_many_attempts' => 'Too many attempts. Try again in few minutes.', - 'wrong_credentials' => 'Incorrect email or password.', - 'confirmation' => 'Your account has been confirmed!', - 'wrong_confirmation' => 'Wrong confirmation code.', - 'password_forgot' => 'The information regarding password reset was sent to your email.', - 'password_reset' => 'Your password has been changed successfully.', - 'wrong_password_reset' => 'Invalid password. Try again', - ], - - // Pro Plan - 'pro_plan' => [ - 'remove_logo' => ':link to remove the Invoice Ninja logo by joining the Pro Plan', - 'remove_logo_link' => 'Click here', - ], - 'logout' => 'Log Out', 'sign_up_to_save' => 'Sign up to save your work', 'agree_to_terms' => 'I agree to the Invoice Ninja :terms', @@ -319,7 +267,6 @@ return array( 'success_message' => 'You have successfully registered! Please visit the link in the account confirmation email to verify your email address.', 'erase_data' => 'This will permanently erase your data.', 'password' => 'Password', - 'pro_plan_product' => 'Pro Plan', 'pro_plan_description' => 'One year enrollment in the Invoice Ninja Pro Plan.', 'pro_plan_success' => 'Thanks for choosing Invoice Ninja\'s Pro plan!

     
    @@ -329,7 +276,6 @@ return array( for a year of Pro-level invoicing.

    Can\'t find the invoice? Need further assistance? We\'re happy to help -- email us at contact@invoiceninja.com', - 'unsaved_changes' => 'You have unsaved changes', 'custom_fields' => 'Custom Fields', 'company_fields' => 'Company Fields', @@ -339,8 +285,6 @@ return array( 'edit' => 'Edit', 'set_name' => 'Set your company name', 'view_as_recipient' => 'View as recipient', - - // product management 'product_library' => 'Product Library', 'product' => 'Product', 'products' => 'Product Library', @@ -355,18 +299,14 @@ return array( 'created_product' => 'Successfully created product', 'archived_product' => 'Successfully archived product', 'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan', - 'advanced_settings' => 'Advanced Settings', 'pro_plan_advanced_settings' => ':link to enable the advanced settings by joining the Pro Plan', 'invoice_design' => 'Invoice Design', 'specify_colors' => 'Specify colors', 'specify_colors_label' => 'Select the colors used in the invoice', - 'chart_builder' => 'Chart Builder', 'ninja_email_footer' => 'Use :site to invoice your clients and get paid online for free!', 'go_pro' => 'Go Pro', - - // Quotes 'quote' => 'Quote', 'quotes' => 'Quotes', 'quote_number' => 'Quote Number', @@ -376,7 +316,6 @@ return array( 'your_quote' => 'Your Quote', 'total' => 'Total', 'clone' => 'Clone', - 'new_quote' => 'New Quote', 'create_quote' => 'Create Quote', 'edit_quote' => 'Edit Quote', @@ -389,7 +328,6 @@ return array( 'view_invoice' => 'View Invoice', 'view_client' => 'View Client', 'view_quote' => 'View Quote', - 'updated_quote' => 'Successfully updated quote', 'created_quote' => 'Successfully created quote', 'cloned_quote' => 'Successfully cloned quote', @@ -399,7 +337,6 @@ return array( 'deleted_quote' => 'Successfully deleted quote', 'deleted_quotes' => 'Successfully deleted :count quotes', 'converted_to_invoice' => 'Successfully converted quote to invoice', - 'quote_subject' => 'New quote $quote from :account', 'quote_message' => 'To view your quote for :amount, click the link below.', 'quote_link_message' => 'To view your client quote click the link below:', @@ -407,16 +344,13 @@ return array( 'notification_quote_viewed_subject' => 'Quote :invoice was viewed by :client', 'notification_quote_sent' => 'The following client :client was emailed Quote :invoice for :amount.', 'notification_quote_viewed' => 'The following client :client viewed Quote :invoice for :amount.', - 'session_expired' => 'Your session has expired.', - 'invoice_fields' => 'Invoice Fields', 'invoice_options' => 'Invoice Options', 'hide_quantity' => 'Hide Quantity', 'hide_quantity_help' => 'If your line items quantities are always 1, then you can declutter invoices by no longer displaying this field.', 'hide_paid_to_date' => 'Hide Paid to Date', 'hide_paid_to_date_help' => 'Only display the "Paid to Date" area on your invoices once a payment has been received.', - 'charge_taxes' => 'Charge taxes', 'user_management' => 'User Management', 'add_user' => 'Add User', @@ -431,21 +365,16 @@ return array( 'active' => 'Active', 'pending' => 'Pending', 'deleted_user' => 'Successfully deleted user', - 'limit_users' => 'Sorry, this will exceed the limit of '.MAX_NUM_USERS.' users', - 'confirm_email_invoice' => 'Are you sure you want to email this invoice?', 'confirm_email_quote' => 'Are you sure you want to email this quote?', 'confirm_recurring_email_invoice' => 'Are you sure you want this invoice emailed?', - 'cancel_account' => 'Cancel Account', 'cancel_account_message' => 'Warning: This will permanently erase all of your data, there is no undo.', 'go_back' => 'Go Back', - 'data_visualizations' => 'Data Visualizations', 'sample_data' => 'Sample data shown', 'hide' => 'Hide', 'new_version_available' => 'A new version of :releases_link is available. You\'re running v:user_version, the latest is v:latest_version', - 'invoice_settings' => 'Invoice Settings', 'invoice_number_prefix' => 'Invoice Number Prefix', 'invoice_number_counter' => 'Invoice Number Counter', @@ -455,59 +384,48 @@ return array( 'invoice_issued_to' => 'Invoice issued to', 'invalid_counter' => 'To prevent a possible conflict please set either an invoice or quote number prefix', 'mark_sent' => 'Mark Sent', - 'gateway_help_1' => ':link to sign up for Authorize.net.', 'gateway_help_2' => ':link to sign up for Authorize.net.', 'gateway_help_17' => ':link to get your PayPal API signature.', 'gateway_help_27' => ':link to sign up for TwoCheckout.', - 'more_designs' => 'More designs', 'more_designs_title' => 'Additional Invoice Designs', 'more_designs_cloud_header' => 'Go Pro for more invoice designs', 'more_designs_cloud_text' => '', - 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE, 'more_designs_self_host_text' => '', 'buy' => 'Buy', 'bought_designs' => 'Successfully added additional invoice designs', 'sent' => 'sent', - 'vat_number' => 'VAT Number', 'timesheets' => 'Timesheets', - 'payment_title' => 'Enter Your Billing Address and Credit Card information', 'payment_cvv' => '*This is the 3-4 digit number onthe back of your card', 'payment_footer1' => '*Billing address must match address associated with credit card.', 'payment_footer2' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', - 'id_number' => 'ID Number', 'white_label_link' => 'White label', 'white_label_header' => 'White Label', 'bought_white_label' => 'Successfully enabled white label license', 'white_labeled' => 'White labeled', - 'restore' => 'Restore', 'restore_invoice' => 'Restore Invoice', 'restore_quote' => 'Restore Quote', 'restore_client' => 'Restore Client', 'restore_credit' => 'Restore Credit', 'restore_payment' => 'Restore Payment', - 'restored_invoice' => 'Successfully restored invoice', 'restored_quote' => 'Successfully restored quote', 'restored_client' => 'Successfully restored client', 'restored_payment' => 'Successfully restored payment', 'restored_credit' => 'Successfully restored credit', - 'reason_for_canceling' => 'Help us improve our site by telling us why you\'re leaving.', 'discount_percent' => 'Percent', 'discount_amount' => 'Amount', - 'invoice_history' => 'Invoice History', 'quote_history' => 'Quote History', 'current_version' => 'Current version', 'select_versiony' => 'Select version', 'view_history' => 'View History', - 'edit_payment' => 'Edit Payment', 'updated_payment' => 'Successfully updated payment', 'deleted' => 'Deleted', @@ -520,7 +438,6 @@ return array( 'quote_email' => 'Quote Email', 'reset_all' => 'Reset All', 'approve' => 'Approve', - 'token_billing_type_id' => 'Token Billing', 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', 'token_billing_1' => 'Disabled', @@ -533,7 +450,6 @@ return array( 'edit_payment_details' => 'Edit payment details', 'token_billing' => 'Save card details', 'token_billing_secure' => 'The data is stored securely by :stripe_link', - 'support' => 'Support', 'contact_information' => 'Contact Information', '256_encryption' => '256-Bit Encryption', @@ -543,10 +459,8 @@ return array( 'order_overview' => 'Order overview', 'match_address' => '*Address must match address associated with credit card.', 'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.', - 'invoice_footer' => 'Invoice Footer', 'save_as_default_footer' => 'Save as default footer', - 'token_management' => 'Token Management', 'tokens' => 'Tokens', 'add_token' => 'Add Token', @@ -557,7 +471,6 @@ return array( 'edit_token' => 'Edit Token', 'delete_token' => 'Delete Token', 'token' => 'Token', - 'add_gateway' => 'Add Gateway', 'delete_gateway' => 'Delete Gateway', 'edit_gateway' => 'Edit Gateway', @@ -566,7 +479,6 @@ return array( 'deleted_gateway' => 'Successfully deleted gateway', 'pay_with_paypal' => 'PayPal', 'pay_with_card' => 'Credit Card', - 'change_password' => 'Change password', 'current_password' => 'Current password', 'new_password' => 'New password', @@ -574,7 +486,6 @@ return array( 'password_error_incorrect' => 'The current password is incorrect.', 'password_error_invalid' => 'The new password is invalid.', 'updated_password' => 'Successfully updated password', - 'api_tokens' => 'API Tokens', 'users_and_tokens' => 'Users & Tokens', 'account_login' => 'Account Login', @@ -582,18 +493,15 @@ return array( 'forgot_password' => 'Forgot your password?', 'email_address' => 'Email address', 'lets_go' => 'Let\'s go', - //'lets_go' => 'Login', 'password_recovery' => 'Password Recovery', 'send_email' => 'Send email', 'set_password' => 'Set Password', 'converted' => 'Converted', - 'email_approved' => 'Email me when a quote is approved', 'notification_quote_approved_subject' => 'Quote :invoice was approved by :client', 'notification_quote_approved' => 'The following client :client approved Quote :invoice for :amount.', 'resend_confirmation' => 'Resend confirmation email', 'confirmation_resent' => 'The confirmation email was resent', - 'gateway_help_42' => ':link to sign up for BitPay.
    Note: use a Legacy API Key, not an API token.', 'payment_type_credit_card' => 'Credit Card', 'payment_type_paypal' => 'PayPal', @@ -601,7 +509,6 @@ return array( 'knowledge_base' => 'Knowledge Base', 'partial' => 'Partial', 'partial_remaining' => ':partial of :balance', - 'more_fields' => 'More Fields', 'less_fields' => 'Less Fields', 'client_name' => 'Client Name', @@ -612,7 +519,6 @@ return array( 'view_documentation' => 'View Documentation', '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.', - 'rows' => 'rows', 'www' => 'www', 'logo' => 'Logo', @@ -632,7 +538,6 @@ return array( 'zapier' => 'Zapier', 'recurring' => 'Recurring', 'last_invoice_sent' => 'Last invoice sent :date', - 'processed_updates' => 'Successfully completed update', 'tasks' => 'Tasks', 'new_task' => 'New Task', @@ -678,12 +583,10 @@ return array( 'invoice_labels' => 'Invoice Labels', 'prefix' => 'Prefix', 'counter' => 'Counter', - 'payment_type_dwolla' => 'Dwolla', 'gateway_help_43' => ':link to sign up for Dwolla', 'partial_value' => 'Must be greater than zero and less than the total', 'more_actions' => 'More Actions', - 'pro_plan_title' => 'NINJA PRO', 'pro_plan_call_to_action' => 'Upgrade Now!', 'pro_plan_feature1' => 'Create Unlimited Clients', @@ -694,14 +597,12 @@ return array( 'pro_plan_feature6' => 'Create Quotes & Pro-forma Invoices', 'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering', 'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails', - 'resume' => 'Resume', 'break_duration' => 'Break', 'edit_details' => 'Edit Details', 'work' => 'Work', 'timezone_unset' => 'Please :link to set your timezone', 'click_here' => 'click here', - 'email_receipt' => 'Email payment receipt to the client', 'created_payment_emailed_client' => 'Successfully created payment and emailed client', 'add_company' => 'Add Company', @@ -711,10 +612,8 @@ return array( 'unlinked_account' => 'Successfully unlinked accounts', 'login' => 'Login', 'or' => 'or', - 'email_error' => 'There was a problem sending the email', 'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.', - 'old_browser' => 'Please use a newer browser', 'payment_terms_help' => 'Sets the default invoice due date', 'unlink_account' => 'Unlink Account', 'unlink' => 'Unlink', @@ -735,7 +634,6 @@ return array( 'primary_color' => 'Primary Color', 'secondary_color' => 'Secondary Color', 'customize_design' => 'Customize Design', - 'content' => 'Content', 'styles' => 'Styles', 'defaults' => 'Defaults', @@ -749,7 +647,6 @@ return array( 'outstanding' => 'Outstanding', 'manage_companies' => 'Manage Companies', 'total_revenue' => 'Total Revenue', - 'current_user' => 'Current User', 'new_recurring_invoice' => 'New Recurring Invoice', 'recurring_invoice' => 'Recurring Invoice', @@ -761,7 +658,6 @@ return array(

    You can access any invoice field by adding Value to the end. For example $invoiceNumberValue displays the invoice number.

    To access a child property using dot notation. For example to show the client name you could use $client.nameValue.

    If you need help figuring something out post a question to our support forum.

    ', - 'invoice_due_date' => 'Due Date', 'quote_due_date' => 'Valid Until', 'valid_until' => 'Valid Until', @@ -774,15 +670,12 @@ return array( 'status_partial' => 'Partial', 'status_paid' => 'Paid', 'show_line_item_tax' => 'Display line item taxes inline', - 'iframe_url' => 'Website', 'iframe_url_help1' => 'Copy the following code to a page on your site.', 'iframe_url_help2' => 'You can test the feature by clicking \'View as recipient\' for an invoice.', - 'auto_bill' => 'Auto Bill', 'military_time' => '24 Hour Time', 'last_sent' => 'Last Sent', - 'reminder_emails' => 'Reminder Emails', 'templates_and_reminders' => 'Templates & Reminders', 'subject' => 'Subject', @@ -794,15 +687,12 @@ return array( 'reminder_subject' => 'Reminder: Invoice :invoice from :account', 'reset' => 'Reset', 'invoice_not_found' => 'The requested invoice is not available', - 'referral_program' => 'Referral Program', 'referral_code' => 'Referral URL', 'last_sent_on' => 'Sent Last: :date', - 'page_expire' => 'This page will expire soon, :click_here to keep working', 'upcoming_quotes' => 'Upcoming Quotes', 'expired_quotes' => 'Expired Quotes', - 'sign_up_using' => 'Sign up using', 'invalid_credentials' => 'These credentials do not match our records', 'show_all_options' => 'Show all options', @@ -811,18 +701,10 @@ return array( 'disable' => 'Disable', 'invoice_quote_number' => 'Invoice and Quote Numbers', 'invoice_charges' => 'Invoice Charges', - - 'invitation_status' => [ - 'sent' => 'Email Sent', - 'opened' => 'Email Openend', - 'viewed' => 'Invoice Viewed', - ], - 'notification_invoice_bounced' => 'We were unable to deliver Invoice :invoice to :contact.', 'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice', 'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.', 'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice', - 'custom_invoice_link' => 'Custom Invoice Link', 'total_invoiced' => 'Total Invoiced', 'open_balance' => 'Open Balance', @@ -830,15 +712,12 @@ return array( 'basic_settings' => 'Basic Settings', 'pro' => 'Pro', 'gateways' => 'Payment Gateways', - 'next_send_on' => 'Send Next: :date', 'no_longer_running' => 'This invoice is not scheduled to run', 'general_settings' => 'General Settings', 'customize' => 'Customize', - 'oneclick_login_help' => 'Connect an account to login without a password', 'referral_code_help' => 'Earn money by sharing our app online', - 'enable_with_stripe' => 'Enable | Requires Stripe', 'tax_settings' => 'Tax Settings', 'create_tax_rate' => 'Add Tax Rate', @@ -859,7 +738,6 @@ return array( 'invoice_counter' => 'Invoice Counter', 'quote_counter' => 'Quote Counter', 'type' => 'Type', - 'activity_1' => ':user created client :client', 'activity_2' => ':user archived client :client', 'activity_3' => ':user deleted client :client', @@ -897,7 +775,6 @@ return array( 'activity_35' => ':user created :vendor', 'activity_36' => ':user created :vendor', 'activity_37' => ':user created :vendor', - 'payment' => 'Payment', 'system' => 'System', 'signature' => 'Email Signature', @@ -908,7 +785,6 @@ return array( 'default_invoice_footer' => 'Default Invoice Footer', 'quote_footer' => 'Quote Footer', 'free' => 'Free', - 'quote_is_approved' => 'This quote is approved', 'apply_credit' => 'Apply Credit', 'system_settings' => 'System Settings', @@ -926,7 +802,6 @@ return array( 'restored_recurring_invoice' => 'Successfully restored recurring invoice', 'archived' => 'Archived', 'untitled_account' => 'Untitled Company', - 'before' => 'Before', 'after' => 'After', 'reset_terms_help' => 'Reset to the default account terms', @@ -935,7 +810,6 @@ return array( 'user' => 'User', 'country' => 'Country', 'include' => 'Include', - 'logo_too_large' => 'Your logo is :size, for better PDF performance we suggest uploading an image file less than 200KB', 'import_freshbooks' => 'Import From FreshBooks', 'import_data' => 'Import Data', @@ -946,16 +820,6 @@ return array( 'task_file' => 'Task File', 'no_mapper' => 'No valid mapping for file', 'invalid_csv_header' => 'Invalid CSV Header', - - 'email_errors' => [ - 'inactive_client' => 'Emails can not be sent to inactive clients', - 'inactive_contact' => 'Emails can not be sent to inactive contacts', - 'inactive_invoice' => 'Emails can not be sent to inactive invoices', - 'user_unregistered' => 'Please register your account to send emails', - 'user_unconfirmed' => 'Please confirm your account to send emails', - 'invalid_contact_email' => 'Invalid contact email', - ], - 'client_portal' => 'Client Portal', 'admin' => 'Admin', 'disabled' => 'Disabled', @@ -964,11 +828,9 @@ return array( 'invoice_will_create' => 'client will be created', 'invoices_will_create' => 'invoices will be created', 'failed_to_import' => 'The following records failed to import', - 'publishable_key' => 'Publishable Key', 'secret_key' => 'Secret Key', 'missing_publishable_key' => 'Set your Stripe publishable key for an improved checkout process', - 'email_design' => 'Email Design', 'due_by' => 'Due by :date', 'enable_email_markup' => 'Enable Markup', @@ -980,7 +842,6 @@ return array( 'plain' => 'Plain', 'light' => 'Light', 'dark' => 'Dark', - 'industry_help' => 'Used to provide comparisons against the averages of companies of similar size and industry.', 'subdomain_help' => 'Customize the invoice link subdomain or display the invoice on your own website.', 'invoice_number_help' => 'Specify a prefix or use a custom pattern to dynamically set the invoice number.', @@ -989,7 +850,6 @@ return array( 'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.', 'custom_invoice_fields_helps' => 'Add a text input to the invoice create/edit page and display the label and value on the PDF.', 'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.', - 'token_expired' => 'Validation token was expired. Please try again.', 'invoice_link' => 'Invoice Link', 'button_confirmation_message' => 'Click to confirm your email address.', @@ -998,7 +858,6 @@ return array( 'created_invoices' => 'Successfully created :count invoice(s)', 'next_invoice_number' => 'The next invoice number is :number.', 'next_quote_number' => 'The next quote number is :number.', - 'days_before' => 'days before', 'days_after' => 'days after', 'field_due_date' => 'due date', @@ -1006,11 +865,7 @@ return array( 'schedule' => 'Schedule', 'email_designs' => 'Email Designs', 'assigned_when_sent' => 'Assigned when sent', - - 'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.', 'white_label_purchase_link' => 'Purchase a white label license', - - // Expense / vendor 'expense' => 'Expense', 'expenses' => 'Expenses', 'new_expense' => 'Enter Expense', @@ -1027,8 +882,6 @@ return array( 'archived_expense' => 'Successfully archived expense', 'deleted_expenses' => 'Successfully deleted expenses', 'archived_expenses' => 'Successfully archived expenses', - - // Expenses 'expense_amount' => 'Expense Amount', 'expense_balance' => 'Expense Balance', 'expense_date' => 'Expense Date', @@ -1053,15 +906,11 @@ return array( 'expense_error_multiple_clients' => 'The expenses can\'t belong to different clients', 'expense_error_invoiced' => 'Expense has already been invoiced', 'convert_currency' => 'Convert currency', - - // Payment terms 'num_days' => 'Number of days', 'create_payment_term' => 'Create Payment Term', 'edit_payment_terms' => 'Edit Payment Term', 'edit_payment_term' => 'Edit Payment Term', 'archive_payment_term' => 'Archive Payment Term', - - // recurring due dates 'recurring_due_dates' => 'Recurring Invoice Due Dates', 'recurring_due_date_help' => '

    Automatically sets a due date for the invoice.

    Invoices on a monthly or yearly cycle set to be due on or before the day they are created will be due the next month. Invoices set to be due on the 29th or 30th in months that don\'t have that day will be due the last day of the month.

    @@ -1089,15 +938,11 @@ return array( 'thursday' => 'Thursday', 'friday' => 'Friday', 'saturday' => 'Saturday', - - // Fonts 'header_font_id' => 'Header Font', 'body_font_id' => 'Body Font', 'color_font_help' => 'Note: the primary color and fonts are also used in the client portal and custom email designs.', - 'live_preview' => 'Live Preview', 'invalid_mail_config' => 'Unable to send email, please check that the mail settings are correct.', - 'invoice_message_button' => 'To view your invoice for :amount, click the button below.', 'quote_message_button' => 'To view your quote for :amount, click the button below.', 'payment_message_button' => 'Thank you for your payment of :amount.', @@ -1114,7 +959,6 @@ return array( 'archived_bank_account' => 'Successfully archived bank account', 'created_bank_account' => 'Successfully created bank account', 'validate_bank_account' => 'Validate Bank Account', - 'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and 400+ US banks.', 'bank_password_help' => 'Note: your password is transmitted securely and never stored on our servers.', 'bank_password_warning' => 'Warning: your password may be transmitted in plain text, consider enabling HTTPS.', 'username' => 'Username', @@ -1128,7 +972,6 @@ return array( 'validate' => 'Validate', 'info' => 'Info', 'imported_expenses' => 'Successfully created :count_vendors vendor(s) and :count_expenses expense(s)', - 'iframe_url_help3' => 'Note: if you plan on accepting credit cards details we strongly recommend enabling HTTPS on your site.', 'expense_error_multiple_currencies' => 'The expenses can\'t have different currencies.', 'expense_error_mismatch_currencies' => 'The client\'s currency does not match the expense currency.', @@ -1149,6 +992,44 @@ return array( 'trial_call_to_action' => 'Start Free Trial', 'trial_success' => 'Successfully enabled two week free pro plan trial', 'overdue' => 'Overdue', + + 'white_label_text' => 'Purchase a ONE YEAR white label license for $'.WHITE_LABEL_PRICE.' to remove the Invoice Ninja branding from the client portal and help support our project.', - + 'user_email_footer' => 'To adjust your email notification settings please visit '.SITE_URL.'/settings/notifications', + 'reset_password_footer' => 'If you did not request this password reset please email our support: '.CONTACT_EMAIL, + 'limit_users' => 'Sorry, this will exceed the limit of '.MAX_NUM_USERS.' users', + 'more_designs_self_host_header' => 'Get 6 more invoice designs for just $'.INVOICE_DESIGNS_PRICE, + 'old_browser' => 'Please use a newer browser', + 'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.', + 'bank_accounts_help' => 'Connect a bank account to automatically import expenses and create vendors. Supports American Express and 400+ US banks.', + 'security' => [ + 'too_many_attempts' => 'Too many attempts. Try again in few minutes.', + 'wrong_credentials' => 'Incorrect email or password.', + 'confirmation' => 'Your account has been confirmed!', + 'wrong_confirmation' => 'Wrong confirmation code.', + 'password_forgot' => 'The information regarding password reset was sent to your email.', + 'password_reset' => 'Your password has been changed successfully.', + 'wrong_password_reset' => 'Invalid password. Try again', + ], + 'pro_plan' => [ + 'remove_logo' => ':link to remove the Invoice Ninja logo by joining the Pro Plan', + 'remove_logo_link' => 'Click here', + ], + 'invitation_status' => [ + 'sent' => 'Email Sent', + 'opened' => 'Email Openend', + 'viewed' => 'Invoice Viewed', + ], + 'email_errors' => [ + 'inactive_client' => 'Emails can not be sent to inactive clients', + 'inactive_contact' => 'Emails can not be sent to inactive contacts', + 'inactive_invoice' => 'Emails can not be sent to inactive invoices', + 'user_unregistered' => 'Please register your account to send emails', + 'user_unconfirmed' => 'Please confirm your account to send emails', + 'invalid_contact_email' => 'Invalid contact email', + ], ); + +return $LANG; + +?>. \ No newline at end of file diff --git a/resources/views/accounts/api_tokens.blade.php b/resources/views/accounts/api_tokens.blade.php index efbd41e283e1..4612b1cbfedd 100644 --- a/resources/views/accounts/api_tokens.blade.php +++ b/resources/views/accounts/api_tokens.blade.php @@ -42,9 +42,13 @@ function setTrashVisible() { var checked = $('#trashed').is(':checked'); - window.location = '{!! URL::to('view_archive/token') !!}' + (checked ? '/true' : '/false'); + var url = '{{ URL::to('view_archive/token') }}' + (checked ? '/true' : '/false'); + + $.get(url, function(data) { + refreshDatatable(); + }) } - + @stop diff --git a/resources/views/accounts/user_management.blade.php b/resources/views/accounts/user_management.blade.php index 0fc17a7a3fa4..a7f70f925b7a 100644 --- a/resources/views/accounts/user_management.blade.php +++ b/resources/views/accounts/user_management.blade.php @@ -39,7 +39,11 @@ function setTrashVisible() { var checked = $('#trashed').is(':checked'); - window.location = '{!! URL::to('view_archive/user') !!}' + (checked ? '/true' : '/false'); + var url = '{{ URL::to('view_archive/user') }}' + (checked ? '/true' : '/false'); + + $.get(url, function(data) { + refreshDatatable(); + }) } diff --git a/resources/views/clients/show.blade.php b/resources/views/clients/show.blade.php index e4209fdd39ee..c29fffe1df4a 100644 --- a/resources/views/clients/show.blade.php +++ b/resources/views/clients/show.blade.php @@ -22,7 +22,7 @@ @section('content')
    -
    +
    {{ $client->getDisplayName() }} @if ($client->trashed()) @@ -30,7 +30,7 @@ @endif
    -
    +
    {!! Former::open('clients/bulk')->addClass('mainForm') !!}
    diff --git a/resources/views/datatable.blade.php b/resources/views/datatable.blade.php index 2d0b78d9faa4..4c73a7d24875 100644 --- a/resources/views/datatable.blade.php +++ b/resources/views/datatable.blade.php @@ -42,8 +42,12 @@ }); @endif + function refreshDatatable() { + window.dataTable.api().ajax.reload(); + } + function load_{{ $class }}() { - jQuery('.{{ $class }}').dataTable({ + window.dataTable = jQuery('.{{ $class }}').dataTable({ "fnRowCallback": function(row, data) { if (data[0].indexOf('ENTITY_DELETED') > 0) { $(row).addClass('entityDeleted'); diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index 8d413e16b69c..589c8d8cda8f 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -59,40 +59,50 @@ ->data_bind('combobox: client_id') ->addGroupClass('client-select') !!} - @if (!$expense || ($expense && !$expense->invoice_id)) + @if (!$expense || ($expense && !$expense->invoice_id && !$expense->client_id)) {!! Former::checkbox('should_be_invoiced') ->text(trans('texts.should_be_invoiced')) ->data_bind('checked: should_be_invoiced() || client_id(), enable: !client_id()') - ->label(' ') !!}
    + ->label(' ') !!} @endif - - {!! Former::select('invoice_currency_id')->addOption('','') - ->label(trans('texts.invoice_currency')) - ->data_placeholder(Utils::getFromCache($account->getCurrencyId(), 'currencies')->name) - ->data_bind('combobox: invoice_currency_id, disable: true') - ->fromQuery($currencies, 'name', 'id') !!} - - - {!! Former::plaintext('test') - ->value('') - ->style('min-height:46px') - ->label(trans('texts.invoice_currency')) !!} - + @if (!$expense || ($expense && ! $expense->isExchanged())) + {!! Former::checkbox('convert_currency') + ->text(trans('texts.convert_currency')) + ->data_bind('checked: convert_currency') + ->label(' ') !!} + @endif +
    - {!! Former::text('exchange_rate') - ->data_bind("value: exchange_rate, enable: enableExchangeRate, valueUpdate: 'afterkeydown'") !!} +
    + + {!! Former::select('invoice_currency_id')->addOption('','') + ->label(trans('texts.invoice_currency')) + ->data_placeholder(Utils::getFromCache($account->getCurrencyId(), 'currencies')->name) + ->data_bind('combobox: invoice_currency_id, disable: true') + ->fromQuery($currencies, 'name', 'id') !!} + + + {!! Former::plaintext('test') + ->value('') + ->style('min-height:46px') + ->label(trans('texts.invoice_currency')) !!} + - {!! Former::text('invoice_amount') - ->addGroupClass('converted-amount') - ->data_bind("value: convertedAmount, enable: enableExchangeRate") - ->append('') !!} + {!! Former::text('exchange_rate') + ->data_bind("value: exchange_rate, enable: enableExchangeRate, valueUpdate: 'afterkeydown'") !!} + {!! Former::text('invoice_amount') + ->addGroupClass('converted-amount') + ->data_bind("value: convertedAmount, enable: enableExchangeRate") + ->append('') !!} +
    - {!! Former::textarea('public_notes')->style('height:255px') !!} - {!! Former::textarea('private_notes')->style('height:255px') !!} + {!! Former::textarea('public_notes')->rows(8) !!} + {!! Former::textarea('private_notes')->rows(8) !!} +
    @@ -150,7 +160,7 @@ } $vendorSelect.combobox(); - $('#expense_date').datepicker('update', new Date()); + $('#expense_date').datepicker('update', '{{ $expense ? $expense->expense_date : 'new Date()' }}'); $('.expense_date .input-group-addon').click(function() { toggleDatePicker('expense_date'); @@ -194,6 +204,7 @@ self.amount = ko.observable(); self.exchange_rate = ko.observable(1); self.should_be_invoiced = ko.observable(); + self.convert_currency = ko.observable(false); if (data) { ko.mapping.fromJS(data, {}, this); @@ -230,9 +241,14 @@ }); self.enableExchangeRate = ko.computed(function() { + if (self.convert_currency()) { + return true; + } var expenseCurrencyId = self.expense_currency_id() || self.account_currency_id(); var invoiceCurrencyId = self.invoice_currency_id() || self.account_currency_id(); - return expenseCurrencyId != invoiceCurrencyId; + return expenseCurrencyId != invoiceCurrencyId + || invoiceCurrencyId != self.account_currency_id() + || expenseCurrencyId != self.account_currency_id(); }) }; diff --git a/resources/views/header.blade.php b/resources/views/header.blade.php index a007d34328df..7cadbb7a6a45 100644 --- a/resources/views/header.blade.php +++ b/resources/views/header.blade.php @@ -259,8 +259,10 @@ } function showSearch() { - $('#search-form').show(); + $('#search').typeahead('setQuery', ''); $('#navbar-options').hide(); + $('#search-form').show(); + if (window.hasOwnProperty('searchData')) { $('#search').focus(); } else { @@ -289,7 +291,6 @@ } function hideSearch() { - $('#search').typeahead('setQuery', ''); $('#search-form').hide(); $('#navbar-options').show(); } @@ -299,7 +300,7 @@ $(".alert-hide").fadeOut(); }, 3000); - $('#search').blur(function(){ + $('#search').blur(function(event){ hideSearch(); }); @@ -331,7 +332,7 @@ showSignUp(); @endif - $('ul.navbar-settings, ul.navbar-history').hover(function () { + $('ul.navbar-settings, ul.navbar-search').hover(function () { if ($('.user-accounts').css('display') == 'block') { $('.user-accounts').dropdown('toggle'); } @@ -351,6 +352,14 @@ }); @endif + // Focus the search input if the user clicks forward slash + $('body').keypress(function(event) { + if (event.which == 47) { + event.preventDefault(); + showSearch(); + } + }); + }); @@ -396,7 +405,7 @@