1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 12:12:48 +01:00

Improvments to multi-account switching

This commit is contained in:
Hillel Coren 2015-07-07 23:08:16 +03:00
parent 39ba8d1287
commit c42dfb8b96
33 changed files with 400 additions and 102 deletions

View File

@ -65,7 +65,7 @@ module.exports = function(grunt) {
'public/js/lightbox.min.js',
'public/js/bootstrap-combobox.js',
'public/js/script.js',
'public/js/pdf.pdfmake.js'
'public/js/pdf.pdfmake.js',
],
dest: 'public/js/built.js',
nonull: true

View File

@ -84,9 +84,10 @@ class AccountController extends BaseController
}
$user = false;
$guestKey = Input::get('guest_key');
$guestKey = Input::get('guest_key'); // local storage key to login until registered
$prevUserId = Session::pull(PREV_USER_ID); // last user id used to link to new account
if ($guestKey) {
if ($guestKey && !$prevUserId) {
$user = User::where('password', '=', $guestKey)->first();
if ($user && $user->registered) {
@ -99,6 +100,11 @@ class AccountController extends BaseController
$user = $account->users()->first();
Session::forget(RECENTLY_VIEWED);
if ($prevUserId) {
$users = $this->accountRepo->associateAccounts($user->id, $prevUserId);
Session::put(SESSION_USER_ACCOUNTS, $users);
}
}
Auth::login($user, true);
@ -154,6 +160,7 @@ class AccountController extends BaseController
'currencies' => Cache::get('currencies'),
'languages' => Cache::get('languages'),
'showUser' => Auth::user()->id === Auth::user()->account->users()->first()->id,
'title' => trans('texts.company_details'),
];
return View::make('accounts.details', $data);
@ -166,21 +173,26 @@ class AccountController extends BaseController
if ($count == 0) {
return Redirect::to('gateways/create');
} else {
return View::make('accounts.payments', ['showAdd' => $count < 3]);
return View::make('accounts.payments', [
'showAdd' => $count < 3,
'title' => trans('texts.online_payments')
]);
}
} elseif ($section == ACCOUNT_NOTIFICATIONS) {
$data = [
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
'title' => trans('texts.notifications'),
];
return View::make('accounts.notifications', $data);
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
return View::make('accounts.import_export');
return View::make('accounts.import_export', ['title' => trans('texts.import_export')]);
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
$account = Auth::user()->account;
$data = [
'account' => $account,
'feature' => $subSection,
'title' => trans('texts.invoice_settings'),
];
if ($subSection == ACCOUNT_INVOICE_DESIGN) {
@ -212,17 +224,22 @@ class AccountController extends BaseController
$data['invoice'] = $invoice;
$data['invoiceDesigns'] = InvoiceDesign::availableDesigns();
$data['invoiceLabels'] = json_decode($account->invoice_labels) ?: [];
$data['title'] = trans('texts.invoice_design');
} else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
$data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE);
$data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE);
$data['paymentEmail'] = $account->getEmailTemplate(ENTITY_PAYMENT);
$data['emailFooter'] = $account->getEmailFooter();
$data['title'] = trans('texts.email_templates');
} else if ($subSection == ACCOUNT_USER_MANAGEMENT) {
$data['title'] = trans('texts.users_and_tokens');
}
return View::make("accounts.{$subSection}", $data);
} elseif ($section == ACCOUNT_PRODUCTS) {
$data = [
'account' => Auth::user()->account,
'title' => trans('texts.product_library'),
];
return View::make('accounts.products', $data);
@ -704,8 +721,6 @@ class AccountController extends BaseController
if (Utils::isNinja()) {
$this->userMailer->sendConfirmation($user);
} else {
$this->accountRepo->registerUser($user);
}
$activities = Activity::scope()->get();
@ -761,6 +776,7 @@ class AccountController extends BaseController
}
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
$account->forceDelete();
Auth::logout();

View File

@ -37,14 +37,12 @@ class AppController extends BaseController
return Redirect::to('/');
}
$view = View::make('setup');
return Response::make($view);
return View::make('setup');
}
public function doSetup()
{
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
if (Utils::isNinja() || (Utils::isDatabaseSetup() && Account::count() > 0)) {
return Redirect::to('/');
}
@ -109,8 +107,6 @@ class AppController extends BaseController
$account = $this->accountRepo->create($firstName, $lastName, $email, $password);
$user = $account->users()->first();
//Auth::login($user, true);
return Redirect::to('/login');
}

View File

@ -60,24 +60,32 @@ class AuthController extends Controller {
public function postLoginWrapper(Request $request)
{
$userId = Auth::check() ? Auth::user()->id : null;
$user = User::where('email', '=', $request->input('email'))->first();
if ($user->failed_logins >= 3) {
Session::flash('error', 'These credentials do not match our records.');
return redirect()->to('login');
}
$response = self::postLogin($request);
if (Auth::check()) {
Event::fire(new UserLoggedIn());
if (Utils::isPro()) {
$users = false;
// we're linking a new account
if ($userId && Auth::user()->id != $userId) {
$users = $this->accountRepo->associateAccounts($userId, Auth::user()->id);
Session::flash('message', trans('texts.associated_accounts'));
// check if other accounts are linked
} else {
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
}
Session::put(SESSION_USER_ACCOUNTS, $users);
$users = false;
// we're linking a new account
if ($userId && Auth::user()->id != $userId) {
$users = $this->accountRepo->associateAccounts($userId, Auth::user()->id);
Session::flash('message', trans('texts.associated_accounts'));
// check if other accounts are linked
} else {
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
}
Session::put(SESSION_USER_ACCOUNTS, $users);
} elseif ($user) {
$user->failed_logins = $user->failed_logins + 1;
$user->save();
}
return $response;
@ -85,6 +93,12 @@ class AuthController extends Controller {
public function getLogoutWrapper()
{
if (Auth::check() && !Auth::user()->registered) {
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
$account->forceDelete();
}
$response = self::getLogout();
Session::flush();

View File

@ -83,6 +83,7 @@ class DashboardController extends BaseController
'activities' => $activities,
'pastDue' => $pastDue,
'upcoming' => $upcoming,
'title' => trans('texts.dashboard'),
];
return View::make('dashboard', $data);

View File

@ -43,7 +43,8 @@ class HomeController extends BaseController
public function invoiceNow()
{
if (Auth::check() && Input::get('logout')) {
if (Auth::check() && Input::get('new_account')) {
Session::put(PREV_USER_ID, Auth::user()->id);
Auth::user()->clearSession();
Auth::logout();
}

View File

@ -460,10 +460,11 @@ class PaymentController extends BaseController
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, $affiliate->price, $license->license_key, $license->product_id);
if (Session::has('return_url')) {
return Redirect::away(Session::get('return_url')."?license_key={$license->license_key}&product_id=".Session::get('product_id'));
} else {
return View::make('public.license', $data);
$data['redirectTo'] = Session::get('return_url')."?license_key={$license->license_key}&product_id=".Session::get('product_id');
$data['message'] = "Redirecting to " . Session::get('return_url');
}
return View::make('public.license', $data);
} catch (\Exception $e) {
$errorMessage = trans('texts.payment_error');
Session::flash('error', $errorMessage);

View File

@ -243,6 +243,7 @@ class ReportController extends BaseController
'reportType' => $reportType,
'enableChart' => $enableChart,
'enableReport' => $enableReport,
'title' => trans('texts.charts_and_reports'),
];
return View::make('reports.chart_builder', $params);

View File

@ -301,11 +301,13 @@ class UserController extends BaseController
* Log the user out of the application.
*
*/
/*
public function logout()
{
if (Auth::check()) {
if (!Auth::user()->registered) {
$account = Auth::user()->account;
$this->accountRepo->unlinkAccount($account);
$account->forceDelete();
}
}
@ -315,7 +317,8 @@ class UserController extends BaseController
return Redirect::to('/')->with('clearGuestKey', true);
}
*/
public function changePassword()
{
// check the current password is correct
@ -352,6 +355,10 @@ class UserController extends BaseController
if ($account->hasUserId($newUserId) && $account->hasUserId($oldUserId)) {
Auth::loginUsingId($newUserId);
Auth::user()->account->loadLocalizationSettings();
// regenerate token to prevent open pages
// from saving under the wrong account
Session::put('_token', str_random(40));
}
}
@ -360,7 +367,7 @@ class UserController extends BaseController
public function unlinkAccount($userAccountId, $userId)
{
$this->accountRepo->unlinkAccount($userAccountId, $userId);
$this->accountRepo->unlinkUser($userAccountId, $userId);
$referer = Request::header('referer');
$users = $this->accountRepo->loadAccounts(Auth::user()->id);

View File

@ -157,6 +157,14 @@ class StartupCheck
}
}
return $next($request);
if (preg_match('/(?i)msie [2-8]/', $_SERVER['HTTP_USER_AGENT'])) {
Session::flash('error', trans('texts.old_browser'));
}
// for security prevent displaying within an iframe
$response = $next($request);
$response->headers->set('X-Frame-Options', 'DENY');
return $response;
}
}

View File

@ -354,6 +354,7 @@ define('EVENT_CREATE_PAYMENT', 4);
define('REQUESTED_PRO_PLAN', 'REQUESTED_PRO_PLAN');
define('DEMO_ACCOUNT_ID', 'DEMO_ACCOUNT_ID');
define('PREV_USER_ID', 'PREV_USER_ID');
define('NINJA_ACCOUNT_KEY', 'zg4ylmzDkdkPOT8yoKQw9LTWaoZJx79h');
define('NINJA_GATEWAY_ID', GATEWAY_STRIPE);
define('NINJA_GATEWAY_CONFIG', '');
@ -364,6 +365,7 @@ define('NINJA_DATE', '2000-01-01');
define('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com');
define('RELEASES_URL', 'https://github.com/hillelcoren/invoice-ninja/releases/');
define('ZAPIER_URL', 'https://zapier.com/developer/invite/11276/85cf0ee4beae8e802c6c579eb4e351f1/');
define('OUTDATE_BROWSER_URL', 'http://browsehappy.com/');
define('COUNT_FREE_DESIGNS', 4);
define('PRODUCT_ONE_CLICK_INSTALL', 1);

View File

@ -61,7 +61,7 @@ class Utils
public static function allowNewAccounts()
{
return isset($_ENV['ALLOW_NEW_ACCOUNTS']) && $_ENV['ALLOW_NEW_ACCOUNTS'] == 'true';
return Utils::isNinja() || (isset($_ENV['ALLOW_NEW_ACCOUNTS']) && $_ENV['ALLOW_NEW_ACCOUNTS'] == 'true');
}
public static function isPro()

View File

@ -3,6 +3,7 @@
use Utils;
use Auth;
use Carbon;
use Session;
use App\Events\UserLoggedIn;
use App\Ninja\Repositories\AccountRepository;
use Illuminate\Queue\InteractsWithQueue;
@ -32,13 +33,16 @@ class HandleUserLoggedIn {
{
$account = Auth::user()->account;
if (!Utils::isNinja() && empty($account->last_login)) {
if (!Utils::isNinja() && Auth::user()->id == 1 && empty($account->last_login)) {
$this->accountRepo->registerUser(Auth::user());
}
$account->last_login = Carbon::now()->toDateTimeString();
$account->save();
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
$account->loadLocalizationSettings();
}

View File

@ -133,6 +133,13 @@ class Account extends Eloquent
return false;
}
/*
public function hasLogo()
{
file_exists($this->getLogoPath());
}
*/
public function getLogoPath()
{
return 'logo/'.$this->account_key.'.jpg';
@ -250,6 +257,7 @@ class Account extends Eloquent
'date',
'rate',
'hours',
'balance',
];
foreach ($fields as $field) {

View File

@ -43,6 +43,11 @@ class Invoice extends EntityModel
return $this->belongsTo('App\Models\InvoiceDesign');
}
public function recurring_invoice()
{
return $this->belongsTo('App\Models\Invoice');
}
public function invitations()
{
return $this->hasMany('App\Models\Invitation')->orderBy('invitations.contact_id');

View File

@ -201,4 +201,15 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
}
}
public static function updateUser($user)
{
if ($user->password != !$user->getOriginal('password')) {
$user->failed_logins = 0;
}
}
}
User::updating(function ($user) {
User::updateUser($user);
});

View File

@ -16,9 +16,10 @@ class Mailer
try {
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject, $data) {
$replyEmail = $fromEmail;
$fromEmail = NINJA_FROM_EMAIL;
$fromEmail = CONTACT_EMAIL;
if(isset($data['invoice_id'])) {
$invoice = Invoice::with('account')->where('id', '=', $data['invoice_id'])->get()->first();
if($invoice->account->pdf_email_attachment && file_exists($invoice->getPDFPath())) {
@ -31,7 +32,7 @@ class Mailer
$message->to($toEmail)->from($fromEmail, $fromName)->replyTo($replyEmail, $fromName)->subject($subject);
});
return true;
} catch (Exception $e) {
$response = $e->getResponse()->getBody()->getContents();

View File

@ -294,6 +294,7 @@ class AccountRepository
$item->account_id = $user->account->id;
$item->account_name = $user->account->getDisplayName();
$item->pro_plan_paid = $user->account->pro_plan_paid;
$item->account_key = file_exists($user->account->getLogoPath()) ? $user->account->account_key : null;
$data[] = $item;
}
@ -363,11 +364,19 @@ class AccountRepository
return $users;
}
public function unlinkAccount($userAccountId, $userId) {
public function unlinkAccount($account) {
foreach ($account->users as $user) {
if ($userAccount = self::findUserAccounts($user->id)) {
$userAccount->removeUserId($user->id);
$userAccount->save();
}
}
}
public function unlinkUser($userAccountId, $userId) {
$userAccount = UserAccount::whereId($userAccountId)->first();
if ($userAccount->hasUserId(Auth::user()->id)) {
if ($userAccount->hasUserId($userId)) {
$userAccount->removeUserId($userId);
$userAccount->save();
}

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SupportLockingAccount extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function($table)
{
$table->smallInteger('failed_logins')->nullable();
});
Schema::table('account_gateways', function($table)
{
$table->boolean('show_address')->default(true)->nullable();
$table->boolean('update_address')->default(true)->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function($table)
{
$table->dropColumn('failed_logins');
});
Schema::table('account_gateways', function($table)
{
$table->dropColumn('show_address');
$table->dropColumn('update_address');
});
}
}

Binary file not shown.

View File

@ -32341,8 +32341,12 @@ function getInvoiceDetails(invoice) {
{'due_date': invoice.due_date},
];
if (NINJA.parseFloat(invoice.balance) < NINJA.parseFloat(invoice.amount)) {
fields.push({'total': formatMoney(invoice.amount, invoice.client.currency_id)});
}
if (NINJA.parseFloat(invoice.partial)) {
fields.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)});
fields.push({'balance': formatMoney(invoice.total_amount, invoice.client.currency_id)});
}
fields.push({'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)})
@ -32398,12 +32402,16 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX)
}
var paid = invoice.amount - invoice.balance;
if (paid) {
data.push({'total': formatMoney(invoice.amount, invoice.client.currency_id)});
}
if (invoice.account.hide_paid_to_date != '1' || paid) {
data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)});
}
if (NINJA.parseFloat(invoice.partial) && invoice.total_amount != invoice.subtotal_amount) {
data.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)});
data.push({'balance': formatMoney(invoice.total_amount, invoice.client.currency_id)});
}
var options = {

View File

@ -705,8 +705,12 @@ function getInvoiceDetails(invoice) {
{'due_date': invoice.due_date},
];
if (NINJA.parseFloat(invoice.balance) < NINJA.parseFloat(invoice.amount)) {
fields.push({'total': formatMoney(invoice.amount, invoice.client.currency_id)});
}
if (NINJA.parseFloat(invoice.partial)) {
fields.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)});
fields.push({'balance': formatMoney(invoice.total_amount, invoice.client.currency_id)});
}
fields.push({'balance_due': formatMoney(invoice.balance_amount, invoice.client.currency_id)})
@ -762,12 +766,16 @@ function displaySubtotals(doc, layout, invoice, y, rightAlignTitleX)
}
var paid = invoice.amount - invoice.balance;
if (paid) {
data.push({'total': formatMoney(invoice.amount, invoice.client.currency_id)});
}
if (invoice.account.hide_paid_to_date != '1' || paid) {
data.push({'paid_to_date': formatMoney(paid, invoice.client.currency_id)});
}
if (NINJA.parseFloat(invoice.partial) && invoice.total_amount != invoice.subtotal_amount) {
data.push({'total': formatMoney(invoice.total_amount, invoice.client.currency_id)});
data.push({'balance': formatMoney(invoice.total_amount, invoice.client.currency_id)});
}
var options = {

View File

@ -705,5 +705,11 @@ return array(
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
);

View File

@ -79,11 +79,11 @@
{!! Former::hidden('remember')->raw() !!}
</p>
<p>{!! Button::success(trans(Utils::allowNewAccounts() ? 'texts.login' : 'texts.lets_go'))->large()->submit()->block() !!}</p>
<p>{!! Button::success(trans(Input::get('new_account') && Utils::allowNewAccounts() ? 'texts.login' : 'texts.lets_go'))->large()->submit()->block() !!}</p>
@if (Utils::allowNewAccounts())
@if (Input::get('new_account') && Utils::allowNewAccounts())
<center><p>- {{ trans('texts.or') }} -</p></center>
<p>{!! Button::primary(trans('texts.new_account'))->asLinkTo(URL::to('/invoice_now?logout=true'))->large()->submit()->block() !!}</p>
<p>{!! Button::primary(trans('texts.new_account'))->asLinkTo(URL::to('/invoice_now?new_account=true'))->large()->submit()->block() !!}</p>
@endif
@ -110,12 +110,9 @@
@endif
@if (Session::has('error'))
<div class="alert alert-danger">{{ Session::get('error') }}</div>
<div class="alert alert-danger"><li>{{ Session::get('error') }}</li></div>
@endif
</div>
{!! Former::close() !!}

View File

@ -8,7 +8,11 @@
@section('content')
<div class="row">
{!! Former::open($url)->addClass('col-md-12 warn-on-exit')->method($method) !!}
{!! Former::open($url)
->rules(
['email' => 'email']
)->addClass('col-md-12 warn-on-exit')
->method($method) !!}
@if ($client)
{!! Former::populate($client) !!}
@ -100,7 +104,8 @@
{!! Former::select('currency_id')->addOption('','')
->fromQuery($currencies, 'name', 'id') !!}
{!! Former::select('payment_terms')->addOption('','')
->fromQuery($paymentTerms, 'name', 'num_days') !!}
->fromQuery($paymentTerms, 'name', 'num_days')
->help(trans('texts.payment_terms_help')) !!}
{!! Former::select('size_id')->addOption('','')
->fromQuery($sizes, 'name', 'id') !!}
{!! Former::select('industry_id')->addOption('','')

View File

@ -186,13 +186,19 @@
});
}
function unlinkAccount(userAccountId, userId) {
if (confirm('{!! trans("texts.are_you_sure") !!}')) {
window.location = '{{ URL::to('/unlink_account') }}' + '/' + userAccountId + '/' + userId;
}
function showUnlink(userAccountId, userId) {
NINJA.unlink = {
'userAccountId': userAccountId,
'userId': userId
};
$('#unlinkModal').modal('show');
return false;
}
function unlinkAccount() {
window.location = '{{ URL::to('/unlink_account') }}' + '/' + NINJA.unlink.userAccountId + '/' + NINJA.unlink.userId;
}
function wordWrapText(value, width)
{
@if (Auth::user()->account->auto_wrap)
@ -242,7 +248,14 @@
$(".alert-hide").fadeOut(500);
}, 2000);
$('#search').blur(function(){
$('#search').css('width', '150px');
$('ul.navbar-right').show();
});
$('#search').focus(function(){
$('#search').css('width', '256px');
$('ul.navbar-right').hide();
if (!window.hasOwnProperty('searchData')) {
$.get('{{ URL::route('getSearchData') }}', function(data) {
window.searchData = true;
@ -316,7 +329,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="{{ URL::to(NINJA_WEB_URL) }}" class='navbar-brand'>
<a href="{{ URL::to(NINJA_WEB_URL) }}" class='navbar-brand' target="_blank">
<img src="{{ asset('images/invoiceninja-logo.png') }}" style="height:18px;width:auto"/>
</a>
</div>
@ -353,29 +366,42 @@
<ul class="dropdown-menu user-accounts" role="menu">
@if (session(SESSION_USER_ACCOUNTS))
@foreach (session(SESSION_USER_ACCOUNTS) as $item)
<li><a href='{{ URL::to("/switch_account/{$item->user_id}") }}'>
@if ($item->user_id == Auth::user()->id)
<b>
@endif
@if (count(session(SESSION_USER_ACCOUNTS)) > 1)
<div class="pull-right glyphicon glyphicon-remove remove" onclick="return unlinkAccount({{ $item->id }}, {{ $item->user_id }})"></div>
@endif
<div class="account" style="padding-right:28px">{{ $item->account_name }}</div>
<div class="user">{{ $item->user_name }}</div>
@if ($item->user_id == Auth::user()->id)
</b>
@endif
</a></li>
@if ($item->user_id == Auth::user()->id)
@include('user_account', [
'user_account_id' => $item->id,
'user_id' => $item->user_id,
'account_name' => $item->account_name,
'user_name' => $item->user_name,
'account_key' => $item->account_key,
'selected' => true,
'show_remove' => count(session(SESSION_USER_ACCOUNTS)) > 1,
])
@endif
@endforeach
@foreach (session(SESSION_USER_ACCOUNTS) as $item)
@if ($item->user_id != Auth::user()->id)
@include('user_account', [
'user_account_id' => $item->id,
'user_id' => $item->user_id,
'account_name' => $item->account_name,
'user_name' => $item->user_name,
'account_key' => $item->account_key,
'selected' => false,
'show_remove' => count(session(SESSION_USER_ACCOUNTS)) > 1,
])
@endif
@endforeach
@else
<li><a href='#'><b>
<div class="account">{{ Auth::user()->account->name ?: trans('texts.untitled') }}</div>
<div class="user">{{ Auth::user()->getDisplayName() }}</div>
</b></a></li>
@include('user_account', [
'account_name' => Auth::user()->account->name ?: trans('texts.untitled'),
'user_name' => Auth::user()->getDisplayName(),
'account_key' => Auth::user()->account->account_key,
'selected' => true,
])
@endif
<li class="divider"></li>
@if (Auth::user()->isPro() && (!session(SESSION_USER_ACCOUNTS) || count(session(SESSION_USER_ACCOUNTS)) < 5))
<li>{!! link_to('/login', trans('texts.add_account')) !!}</li>
@if (!session(SESSION_USER_ACCOUNTS) || count(session(SESSION_USER_ACCOUNTS)) < 5)
<li>{!! link_to('/login?new_account=true', trans('texts.add_account')) !!}</li>
@endif
<li>{!! link_to('#', trans('texts.logout'), array('onclick'=>'logout()')) !!}</li>
</ul>
@ -419,7 +445,7 @@
<form class="navbar-form navbar-right" role="search">
<div class="form-group">
<input type="text" id="search" style="width: {{ Session::get(SESSION_LOCALE) == 'en' ? 180 : 140 }}px"
<input type="text" id="search" style="width: 150px"
class="form-control" placeholder="{{ trans('texts.search') }}">
</div>
</form>
@ -549,6 +575,28 @@
</div>
@endif
@if (Auth::check() && session(SESSION_USER_ACCOUNTS) && count(session(SESSION_USER_ACCOUNTS)))
<div class="modal fade" id="unlinkModal" tabindex="-1" role="dialog" aria-labelledby="unlinkModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{{ trans('texts.unlink_account') }}</h4>
</div>
<div class="container">
<h3>{{ trans('texts.are_you_sure') }}</h3>
</div>
<div class="modal-footer" id="signUpFooter">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
<button type="button" class="btn btn-primary" onclick="unlinkAccount()">{{ trans('texts.unlink') }}</button>
</div>
</div>
</div>
</div>
@endif
@if (Auth::check() && !Auth::user()->isPro())
<div class="modal fade" id="proPlanModal" tabindex="-1" role="dialog" aria-labelledby="proPlanModalLabel" aria-hidden="true">
<div class="modal-dialog large-dialog">
@ -588,7 +636,7 @@
@endif
{{-- Per our license, please do not remove or modify this link. --}}
{{-- Per our license, please do not remove or modify this section. --}}
@if (!Utils::isNinja())
<div class="container">
{{ trans('texts.powered_by') }} <a href="https://www.invoiceninja.com/?utm_source=powered_by" target="_blank">InvoiceNinja.com</a> |

View File

@ -96,7 +96,7 @@
</div>
@if ($invoice && $invoice->recurring_invoice_id)
<div class="pull-right" style="padding-top: 6px">
Created by a {!! link_to('/invoices/'.$invoice->recurring_invoice_id, 'recurring invoice') !!}
{!! trans('texts.created_by_recurring', ['invoice' => link_to('/invoices/'.$invoice->recurring_invoice->public_id, $invoice->recurring_invoice->invoice_number)]) !!}
</div>
@else
<div data-bind="visible: invoice_status_id() === 0">
@ -438,7 +438,8 @@
<span data-bind="visible: $root.showMore">
{!! Former::select('payment_terms')->addOption('','')->data_bind('value: payment_terms')
->fromQuery($paymentTerms, 'name', 'num_days') !!}
->fromQuery($paymentTerms, 'name', 'num_days')
->help(trans('texts.payment_terms_help')) !!}
{!! Former::select('size_id')->addOption('','')->data_bind('value: size_id')
->fromQuery($sizes, 'name', 'id') !!}
{!! Former::select('industry_id')->addOption('','')->data_bind('value: industry_id')
@ -738,14 +739,24 @@
}
function onEmailClick() {
if (confirm('{!! trans("texts.confirm_email_$entityType") !!}')) {
if (!NINJA.isRegistered) {
alert("{{ trans('texts.registration_required') }}");
return;
}
if (confirm('{!! trans("texts.confirm_email_$entityType") !!}' + '\n\n' + getSendToEmails())) {
preparePdfData('email');
}
}
function onSaveClick() {
if (model.invoice().is_recurring()) {
if (confirm('{!! trans("texts.confirm_recurring_email_$entityType") !!}')) {
if (!NINJA.isRegistered) {
alert("{{ trans('texts.registration_required') }}");
return;
}
if (confirm('{!! trans("texts.confirm_recurring_email_$entityType") !!}' + '\n\n' + getSendToEmails() + '\n' + '{!! trans("texts.confirm_recurring_timing") !!}')) {
submitAction('');
}
} else {
@ -753,6 +764,20 @@
}
}
function getSendToEmails() {
var client = model.invoice().client();
var parts = [];
for (var i=0; i<client.contacts().length; i++) {
var contact = client.contacts()[i];
if (contact.send_invoice()) {
parts.push(contact.displayName());
}
}
return parts.join('\n');
}
function preparePdfData(action) {
var invoice = createInvoiceModel();
var design = getDesignJavascript();
@ -1430,6 +1455,22 @@
self.send_invoice = ko.observable(false);
self.invitation_link = ko.observable('');
if (data) {
ko.mapping.fromJS(data, {}, this);
}
self.displayName = ko.computed(function() {
var str = '';
if (self.first_name() || self.last_name()) {
str += self.first_name() + ' ' + self.last_name() + '\n';
}
if (self.email()) {
str += self.email() + '\n';
}
return str;
});
self.email.display = ko.computed(function() {
var str = '';
if (self.first_name() || self.last_name()) {
@ -1447,10 +1488,6 @@
return str;
});
if (data) {
ko.mapping.fromJS(data, {}, this);
}
}
function TaxRateModel(data) {

View File

@ -1,6 +1,7 @@
<iframe id="theFrame" style="display:none" frameborder="1" width="100%" height="{{ isset($pdfHeight) ? $pdfHeight : 1180 }}px"></iframe>
<canvas id="theCanvas" style="display:none;width:100%;border:solid 1px #CCCCCC;"></canvas>
@if (!Utils::isNinja() || !Utils::isPro())
<div class="modal fade" id="moreDesignsModal" tabindex="-1" role="dialog" aria-labelledby="moreDesignsModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -43,7 +44,7 @@
</div>
</div>
</div>
@endif
<script type="text/javascript">

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Invoice Ninja | {{ isset($title) ? $title : ' ' . trans('texts.app_title') }}</title>
<title>{{ isset($title) ? ($title . ' | Invoice Ninja') : ('Invoice Ninja | ' . trans('texts.app_title')) }} | </title>
<meta name="description" content="{{ isset($description) ? $description : trans('texts.app_description') }}" />
<!-- Source: https://github.com/hillelcoren/invoice-ninja -->

View File

@ -181,7 +181,7 @@ table.table thead .sorting_desc_disabled:after { content: '' !important }
</button>
@if (!isset($hideLogo) || !$hideLogo)
{{-- Per our license, please do not remove or modify this link. --}}
<a class="navbar-brand" href="https://www.invoiceninja.com/"><img src="{{ asset('images/invoiceninja-logo.png') }}"></a>
<a class="navbar-brand" href="{{ URL::to(NINJA_WEB_URL) }}" target="_blank"><img src="{{ asset('images/invoiceninja-logo.png') }}"></a>
@endif
</div>
<div id="navbar" class="collapse navbar-collapse">

View File

@ -77,7 +77,11 @@ header h3 em {
<div class="row">
<div class="col-md-7">
<header>
<h2>License Key<br/><small>{{ $message }}</small></h2>
@if (isset($redirectTo))
<h2>Payment Complete</h2>
@else
<h2>License Key<br/><small>{{ $message }}</small></h2>
@endif
</header>
</div>
</div>
@ -87,9 +91,16 @@ header h3 em {
<div class="row">
<div class="col-md-12">
<h2 style="text-align:center">{{ $license }}</h2>
</div>
</div>
<h2 style="text-align:center">
@if (isset($redirectTo))
{{ $message }}
@else
{{ $license }}
@endif
</h2>
</div>
</div>
</div>
</div>
@ -102,6 +113,13 @@ header h3 em {
$(function() {
trackEvent('/license', '/product_{{ $productId }}');
@if (isset($redirectTo))
setTimeout(function() {
location.href = "{!! $redirectTo !!}";
}, 3000);
@endif
})
</script>

View File

@ -36,7 +36,7 @@
<pre>sudo chown yourname:www-data /path/to/ninja</pre>
</div>
@endif
If you need help you can either post to our <a href="https://groups.google.com/forum/#!forum/invoiceninja" target="_blank">Google Group</a>
If you need help you can either post to our <a href="https://www.invoiceninja.com/forums/forum/support/" target="_blank">support forum</a>
or email us at <a href="mailto:contact@invoiceninja.com" target="_blank">contact@invoiceninja.com</a>.
<p>
<pre>-- Commands to create a MySQL database and user
@ -94,13 +94,20 @@ FLUSH PRIVILEGES;</pre>
<h3 class="panel-title">Email Settings</h3>
</div>
<div class="panel-body">
{!! Former::select('mail[driver]')->label('Driver')->options(['smtp' => 'SMTP', 'mail' => 'Mail', 'sendmail' => 'Sendmail']) !!}
{!! Former::text('mail[host]')->label('Host')->value('localhost') !!}
{!! Former::text('mail[port]')->label('Port')->value('587') !!}
{!! Former::select('mail[encryption]')->label('Encryption')->options(['tls' => 'TLS', 'ssl' => 'SSL']) !!}
{!! Former::text('mail[from][name]')->label('From Name') !!}
{!! Former::text('mail[username]')->label('Email') !!}
{!! Former::password('mail[password]')->label('Password') !!}
{!! Former::select('mail[driver]')->label('Driver')->options(['smtp' => 'SMTP', 'mail' => 'Mail', 'sendmail' => 'Sendmail'])
->value(isset($_ENV['MAIL_DRIVER']) ? $_ENV['MAIL_DRIVER'] : 'smtp') !!}
{!! Former::text('mail[host]')->label('Host')
->value(isset($_ENV['MAIL_HOST']) ? $_ENV['MAIL_HOST'] : '') !!}
{!! Former::text('mail[port]')->label('Port')
->value(isset($_ENV['MAIL_PORT']) ? $_ENV['MAIL_PORT'] : '587') !!}
{!! Former::select('mail[encryption]')->label('Encryption')->options(['tls' => 'TLS', 'ssl' => 'SSL'])
->value(isset($_ENV['MAIL_ENCRYPTION']) ? $_ENV['MAIL_ENCRYPTION'] : 'tls') !!}
{!! Former::text('mail[from][name]')->label('From Name')
->value(isset($_ENV['MAIL_FROM_NAME']) ? $_ENV['MAIL_FROM_NAME'] : '') !!}
{!! Former::text('mail[username]')->label('Email')
->value(isset($_ENV['MAIL_USERNAME']) ? $_ENV['MAIL_USERNAME'] : '') !!}
{!! Former::password('mail[password]')->label('Password')
->value(isset($_ENV['MAIL_PASSWORD']) ? $_ENV['MAIL_PASSWORD'] : '') !!}
{!! Former::actions( Button::primary('Send test email')->small()->withAttributes(['onclick' => 'testMail()']), '&nbsp;&nbsp;<span id="mailTestResult"/>' ) !!}
</div>
</div>
@ -118,6 +125,7 @@ FLUSH PRIVILEGES;</pre>
</div>
</div>
{!! Former::checkbox('terms_checkbox')->label(' ')->text(trans('texts.agree_to_terms', ['terms' => '<a href="'.NINJA_APP_URL.'/terms" target="_blank">'.trans('texts.terms_of_service').'</a>'])) !!}
{!! Former::actions( Button::primary('Submit')->large()->submit() ) !!}
{!! Former::close() !!}

View File

@ -0,0 +1,31 @@
<li style="margin-top: 4px; margin-bottom: 4px; min-width: 220px; cursor: pointer">
@if (isset($user_id) && $show_remove)
<a href='{{ URL::to("/switch_account/{$user_id}") }}'>
@else
<a href='#' onclick="return false;">
@endif
@if (isset($show_remove) && $show_remove)
<div class="pull-right glyphicon glyphicon-remove remove" onclick="return showUnlink({{ $user_account_id }}, {{ $user_id }})"></div>
@endif
@if (file_exists('logo/'.$account_key.'.jpg'))
<img class="pull-left" style="width: 40px; min-height: 40px; margin-right: 16px" src="{{ asset('logo/'.$account_key.'.jpg') }}"/>
@else
<div class="pull-left" style="width: 40px; min-height: 40px; margin-right: 16px">&nbsp;</div>
@endif
@if (isset($selected) && $selected)
<b>
@endif
<div class="account" style="padding-right:90px">{{ $account_name }}</div>
<div class="user" style="padding-right:90px">{{ $user_name }}</div>
@if (isset($selected) && $selected)
</b>
@endif
</a>
</li>