1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-19 16:01:34 +02:00

Merge remote-tracking branch 'remotes/ninja/master'

This commit is contained in:
trigras 2015-07-10 22:48:04 +03:00
commit ae557645fc
58 changed files with 1045 additions and 519 deletions

View File

@ -51,7 +51,7 @@ module.exports = function(grunt) {
'public/vendor/knockout-mapping/build/output/knockout.mapping-latest.js',
'public/vendor/knockout-sortable/build/knockout-sortable.min.js',
'public/vendor/underscore/underscore.js',
'public/vendor/bootstrap-datepicker/js/bootstrap-datepicker.js',
'public/vendor/bootstrap-datepicker/dist/js/bootstrap-datepicker.js',
'public/vendor/typeahead.js/dist/typeahead.min.js',
'public/vendor/accounting/accounting.min.js',
'public/vendor/spectrum/spectrum.js',
@ -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
@ -79,8 +79,8 @@ module.exports = function(grunt) {
'public/js/simpleexpand.js',
*/
'public/vendor/bootstrap/dist/js/bootstrap.min.js',
'public/js/bootstrap-combobox.js',
'public/js/bootstrap-combobox.js',
],
dest: 'public/js/built.public.js',
nonull: true
@ -91,7 +91,7 @@ module.exports = function(grunt) {
'public/vendor/datatables/media/css/jquery.dataTables.css',
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
'public/vendor/font-awesome/css/font-awesome.min.css',
'public/vendor/bootstrap-datepicker/css/datepicker3.css',
'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css',
'public/vendor/spectrum/spectrum.css',
'public/css/bootstrap-combobox.css',
'public/css/typeahead.js-bootstrap.css',
@ -115,7 +115,7 @@ module.exports = function(grunt) {
*/
'public/css/bootstrap-combobox.css',
'public/vendor/datatables/media/css/jquery.dataTables.css',
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
],
dest: 'public/css/built.public.css',
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

@ -200,7 +200,7 @@ class AccountGatewayController extends BaseController
$fields = $gateway->getFields();
$optional = array_merge(Gateway::$hiddenFields, Gateway::$optionalFields);
if (Utils::isNinja() && $gatewayId == GATEWAY_DWOLLA) {
if ($gatewayId == GATEWAY_DWOLLA) {
$optional = array_merge($optional, ['key', 'secret']);
}

View File

@ -13,6 +13,8 @@ use Session;
use Cookie;
use Response;
use App\Models\User;
use App\Models\Account;
use App\Models\Industry;
use App\Ninja\Mailers\Mailer;
use App\Ninja\Repositories\AccountRepository;
use Redirect;
@ -32,24 +34,16 @@ class AppController extends BaseController
public function showSetup()
{
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
if (Utils::isNinja() || (Utils::isDatabaseSetup() && Account::count() > 0)) {
return Redirect::to('/');
}
$view = View::make('setup');
/*
$cookie = Cookie::forget('ninja_session', '/', 'www.ninja.dev');
Cookie::queue($cookie);
return Response::make($view)->withCookie($cookie);
*/
return Response::make($view);
return View::make('setup');
}
public function doSetup()
{
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
if (Utils::isNinja() || (Utils::isDatabaseSetup() && Account::count() > 0)) {
return Redirect::to('/');
}
@ -104,7 +98,9 @@ class AppController extends BaseController
// == DB Migrate & Seed == //
// Artisan::call('migrate:rollback', array('--force' => true)); // Debug Purposes
Artisan::call('migrate', array('--force' => true));
Artisan::call('db:seed', array('--force' => true));
if (Industry::count() == 0) {
Artisan::call('db:seed', array('--force' => true));
}
Artisan::call('optimize', array('--force' => true));
$firstName = trim(Input::get('first_name'));
@ -114,8 +110,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');
}
@ -168,7 +162,9 @@ class AppController extends BaseController
if (!Utils::isNinja() && !Utils::isDatabaseSetup()) {
try {
Artisan::call('migrate', array('--force' => true));
Artisan::call('db:seed', array('--force' => true));
if (Industry::count() == 0) {
Artisan::call('db:seed', array('--force' => true));
}
Artisan::call('optimize', array('--force' => true));
} catch (Exception $e) {
Response::make($e->getMessage(), 500);

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

@ -24,8 +24,11 @@ class ClientApiController extends Controller
public function index()
{
$clients = Client::scope()->with('contacts')->orderBy('created_at', 'desc')->get();
$clients = Utils::remapPublicIds($clients->toArray());
$clients = Client::scope()
->with('country', 'contacts', 'industry', 'size', 'currency')
->orderBy('created_at', 'desc')
->get();
$clients = Utils::remapPublicIds($clients);
$response = json_encode($clients, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($clients));
@ -43,9 +46,9 @@ class ClientApiController extends Controller
return Response::make($error, 500, $headers);
} else {
$client = $this->clientRepo->save(false, $data, false);
$client->load('contacts');
$client = Utils::remapPublicIds($client->toArray());
$client = $this->clientRepo->save(isset($data['id']) ? $data['id'] : false, $data, false);
$client = Client::scope($client->public_id)->with('country', 'contacts', 'industry', 'size', 'currency')->first();
$client = Utils::remapPublicIds([$client]);
$response = json_encode($client, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();

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

@ -27,10 +27,8 @@ class HomeController extends BaseController
{
Session::reflash();
if (!Utils::isDatabaseSetup()) {
if (!Utils::isNinja() && (!Utils::isDatabaseSetup() || Account::count() == 0)) {
return Redirect::to('/setup');
} elseif (Account::count() == 0) {
return Redirect::to('/invoice_now');
} elseif (Auth::check()) {
return Redirect::to('/dashboard');
} else {
@ -45,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

@ -26,7 +26,7 @@ class InvoiceApiController extends Controller
public function index()
{
$invoices = Invoice::scope()->with('invitations')->where('invoices.is_quote', '=', false)->orderBy('created_at', 'desc')->get();
$invoices = Invoice::scope()->with('client', 'invitations.account')->where('invoices.is_quote', '=', false)->orderBy('created_at', 'desc')->get();
// Add the first invitation link to the data
foreach ($invoices as $key => $invoice) {
@ -36,7 +36,7 @@ class InvoiceApiController extends Controller
unset($invoice['invitations']);
}
$invoices = Utils::remapPublicIds($invoices->toArray());
$invoices = Utils::remapPublicIds($invoices);
$response = json_encode($invoices, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($invoices));
@ -99,7 +99,6 @@ class InvoiceApiController extends Controller
$data = self::prepareData($data);
$data['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save(false, $data, false);
$invoice->load('invoice_items');
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
@ -112,13 +111,9 @@ class InvoiceApiController extends Controller
}
// prepare the return data
$invoice = $invoice->toArray();
$invoice['link'] = $invitation->getLink();
unset($invoice['account']);
unset($invoice['client']);
$invoice = Utils::remapPublicIds($invoice);
$invoice['client_id'] = $client->public_id;
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->first();
$invoice = Utils::remapPublicIds([$invoice]);
$response = json_encode($invoice, JSON_PRETTY_PRINT);
}

View File

@ -236,6 +236,11 @@ class InvoiceController extends BaseController
}
}
$paymentURL = '';
if (count($paymentTypes)) {
$paymentURL = $paymentTypes[0]['url'];
}
$data = array(
'isConverted' => $invoice->quote_invoice_id ? true : false,
'showBreadcrumbs' => false,
@ -244,7 +249,8 @@ class InvoiceController extends BaseController
'invitation' => $invitation,
'invoiceLabels' => $account->getInvoiceLabels(),
'contact' => $contact,
'paymentTypes' => $paymentTypes
'paymentTypes' => $paymentTypes,
'paymentURL' => $paymentURL
);
return View::make('invoices.view', $data);
@ -505,9 +511,13 @@ class InvoiceController extends BaseController
return $this->convertQuote($publicId);
} elseif ($action == 'email') {
if (Auth::user()->confirmed && !Auth::user()->isDemo()) {
$message = trans("texts.emailed_{$entityType}");
$this->mailer->sendInvoice($invoice);
Session::flash('message', $message);
$response = $this->mailer->sendInvoice($invoice);
if ($response === true) {
$message = trans("texts.emailed_{$entityType}");
Session::flash('message', $message);
} else {
Session::flash('error', $response);
}
} else {
$errorMessage = trans(Auth::user()->registered ? 'texts.confirmation_required' : 'texts.registration_required');
Session::flash('error', $errorMessage);

View File

@ -1,8 +1,10 @@
<?php namespace App\Http\Controllers;
use Input;
use Utils;
use Response;
use App\Models\Payment;
use App\Models\Invoice;
use App\Ninja\Repositories\PaymentRepository;
class PaymentApiController extends Controller
@ -16,24 +18,50 @@ class PaymentApiController extends Controller
public function index()
{
$payments = Payment::scope()->orderBy('created_at', 'desc')->get();
$payments = Utils::remapPublicIds($payments->toArray());
$payments = Payment::scope()
->with('client', 'contact', 'invitation', 'user', 'invoice')
->orderBy('created_at', 'desc')
->get();
$payments = Utils::remapPublicIds($payments);
$response = json_encode($payments, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($payments));
return Response::make($response, 200, $headers);
}
/*
public function store()
{
$data = Input::all();
$invoice = $this->invoiceRepo->save(false, $data, false);
$response = json_encode($invoice, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
*/
public function store()
{
$data = Input::all();
$error = false;
if (isset($data['invoice_id'])) {
$invoice = Invoice::scope($data['invoice_id'])->with('client')->first();
if ($invoice) {
$data['invoice'] = $invoice->public_id;
$data['client'] = $invoice->client->public_id;
} else {
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
}
} else {
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
}
if (!isset($data['transaction_reference'])) {
$data['transaction_reference'] = '';
}
if (!$error) {
$payment = $this->paymentRepo->save(false, $data);
$payment = Payment::scope($payment->public_id)->with('client', 'contact', 'user', 'invoice')->first();
$payment = Utils::remapPublicIds([$payment]);
}
$response = json_encode($error ?: $payment, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders();
return Response::make($response, 200, $headers);
}
}

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);
@ -685,6 +686,14 @@ class PaymentController extends BaseController
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
$gateway = self::createGateway($accountGateway);
// Check for Dwolla payment error
if ($accountGateway->isGateway(GATEWAY_DWOLLA) && Input::get('error')) {
$errorMessage = trans('texts.payment_error')."\n\n".Input::get('error_description');
Session::flash('error', $errorMessage);
Utils::logError($errorMessage);
return Redirect::to('view/'.$invitation->invitation_key);
}
try {
if (method_exists($gateway, 'completePurchase')) {
$details = self::getPaymentDetails($invitation);

View File

@ -16,8 +16,8 @@ class QuoteApiController extends Controller
public function index()
{
$invoices = Invoice::scope()->where('invoices.is_quote', '=', true)->orderBy('created_at', 'desc')->get();
$invoices = Utils::remapPublicIds($invoices->toArray());
$invoices = Invoice::scope()->with('client', 'user')->where('invoices.is_quote', '=', true)->orderBy('created_at', 'desc')->get();
$invoices = Utils::remapPublicIds($invoices);
$response = json_encode($invoices, JSON_PRETTY_PRINT);
$headers = Utils::getApiHeaders(count($invoices));

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()
@ -568,14 +568,15 @@ class Utils
{
$curl = curl_init();
$jsonEncodedData = json_encode($data->toJson());
$jsonEncodedData = json_encode($data->toPublicArray());
$opts = [
CURLOPT_URL => $subscription->target_url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $jsonEncodedData,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
CURLOPT_URL => $subscription->target_url,
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);
@ -591,27 +592,39 @@ class Utils
}
public static function remapPublicIds(array $data)
public static function remapPublicIds($items)
{
$return = [];
foreach ($data as $key => $val) {
if ($key === 'public_id') {
$key = 'id';
} elseif (strpos($key, '_id')) {
continue;
}
if (is_array($val)) {
$val = Utils::remapPublicIds($val);
}
$return[$key] = $val;
foreach ($items as $item) {
$return[] = $item->toPublicArray();
}
return $return;
}
public static function hideIds($data)
{
$publicId = null;
foreach ($data as $key => $val) {
if (is_array($val)) {
$data[$key] = Utils::hideIds($val);
} else if ($key == 'id' || strpos($key, '_id')) {
if ($key == 'public_id') {
$publicId = $val;
}
unset($data[$key]);
}
}
if ($publicId) {
$data['id'] = $publicId;
}
return $data;
}
public static function getApiHeaders($count = 0)
{
return [

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

@ -34,5 +34,9 @@ class AccountGateway extends EntityModel
public function isPaymentType($type) {
return $this->getPaymentType() == $type;
}
public function isGateway($gatewayId) {
return $this->gateway_id == $gatewayId;
}
}

View File

@ -74,7 +74,7 @@ class Client extends EntityModel
public function getName()
{
return $this->getDisplayName();
return $this->name;
}
public function getDisplayName()
@ -82,8 +82,9 @@ class Client extends EntityModel
if ($this->name) {
return $this->name;
}
$this->load('contacts');
$contact = $this->contacts()->first();
return $contact->getDisplayName();

View File

@ -38,6 +38,11 @@ class Contact extends EntityModel
}
*/
public function getName()
{
return $this->getDisplayName();
}
public function getDisplayName()
{
if ($this->getFullName()) {

View File

@ -4,7 +4,12 @@ use Eloquent;
class Country extends Eloquent
{
public $timestamps = false;
public $timestamps = false;
protected $visible = ['id', 'name'];
public function getName()
{
return $this->name;
}
}

View File

@ -5,4 +5,9 @@ use Eloquent;
class Currency extends Eloquent
{
public $timestamps = false;
public function getName()
{
return $this->name;
}
}

View File

@ -77,4 +77,34 @@ class EntityModel extends Eloquent
return $query;
}
public function getName()
{
return $this->public_id;
}
// Remap ids to public_ids and show name
public function toPublicArray()
{
$data = $this->toArray();
foreach ($this->attributes as $key => $val) {
if (strpos($key, '_id')) {
list($field, $id) = explode('_', $key);
if ($field == 'account') {
// do nothing
} else {
$entity = @$this->$field;
if ($entity) {
$data["{$field}_name"] = $entity->getName();
}
}
}
}
$data = Utils::hideIds($data);
return $data;
}
}

View File

@ -5,4 +5,9 @@ use Eloquent;
class Industry extends Eloquent
{
public $timestamps = false;
public function getName()
{
return $this->name;
}
}

View File

@ -29,7 +29,10 @@ class Invitation extends EntityModel
public function getLink()
{
$this->load('account');
if (!$this->account) {
$this->load('account');
}
$url = SITE_URL;
if ($this->account->subdomain) {
@ -41,4 +44,9 @@ class Invitation extends EntityModel
return "{$url}/view/{$this->invitation_key}";
}
public function getName()
{
return $this->invitation_key;
}
}

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

@ -5,4 +5,9 @@ use Eloquent;
class Size extends Eloquent
{
public $timestamps = false;
public function getName()
{
return $this->name;
}
}

View File

@ -48,6 +48,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->belongsTo('App\Models\Theme');
}
public function getName()
{
return $this->getDisplayName();
}
public function getPersonType()
{
return PERSON_USER;
@ -196,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

@ -54,7 +54,11 @@ class ContactMailer extends Mailer
$data['invoice_id'] = $invoice->id;
$fromEmail = $invitation->user->email;
$this->sendTo($invitation->contact->email, $fromEmail, $accountName, $subject, $view, $data);
$response = $this->sendTo($invitation->contact->email, $fromEmail, $accountName, $subject, $view, $data);
if ($response !== true) {
return $response;
}
Activity::emailInvoice($invitation);
}

View File

@ -1,5 +1,6 @@
<?php namespace App\Ninja\Mailers;
use Exception;
use Mail;
use Utils;
use App\Models\Invoice;
@ -13,26 +14,30 @@ class Mailer
'emails.'.$view.'_text',
];
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject, $data) {
$replyEmail = $fromEmail;
try {
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject, $data) {
// http://stackoverflow.com/questions/2421234/gmail-appearing-to-ignore-reply-to
if (Utils::isNinja() && $toEmail != CONTACT_EMAIL) {
$fromEmail = NINJA_FROM_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())) {
$message->attach(
$invoice->getPDFPath(),
array('as' => $invoice->getFileName(), 'mime' => 'application/pdf')
);
$replyEmail = $fromEmail;
$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())) {
$message->attach(
$invoice->getPDFPath(),
array('as' => $invoice->getFileName(), 'mime' => 'application/pdf')
);
}
}
}
//$message->setEncoder(\Swift_Encoding::get8BitEncoding());
$message->to($toEmail)->from($fromEmail, $fromName)->replyTo($replyEmail, $fromName)->subject($subject);
});
$message->to($toEmail)->from($fromEmail, $fromName)->replyTo($replyEmail, $fromName)->subject($subject);
});
return true;
} catch (Exception $e) {
$response = $e->getResponse()->getBody()->getContents();
$response = json_decode($response);
return nl2br($response->Message);
}
}
}

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

@ -108,7 +108,14 @@ class PaymentRepository
$payment->payment_type_id = $paymentTypeId;
}
$payment->payment_date = Utils::toSqlDate($input['payment_date']);
if (isset($input['payment_date_sql'])) {
$payment->payment_date = $input['payment_date_sql'];
} elseif (isset($input['payment_date'])) {
$payment->payment_date = Utils::toSqlDate($input['payment_date']);
} else {
$payment->payment_date = date('Y-m-d');
}
$payment->transaction_reference = trim($input['transaction_reference']);
if (!$publicId) {

View File

@ -2,22 +2,22 @@
"name": "hillelcoren/invoice-ninja",
"version": "0.9.0",
"dependencies": {
"jquery": "~1.11",
"bootstrap": "~3.*",
"jquery-ui": "~1.*",
"jquery": "1.11.3",
"bootstrap": "3.3.1",
"jquery-ui": "1.11.2",
"datatables": "1.10.4",
"datatables-bootstrap3": "*",
"knockout.js": "~3.*",
"knockout-mapping": "*",
"knockout-sortable": "*",
"knockout.js": "3.1.0",
"knockout-mapping": "2.4.1",
"knockout-sortable": "0.9.3",
"font-awesome": "~4.*",
"underscore": "~1.*",
"jspdf": "*",
"bootstrap-datepicker": "~1.*",
"typeahead.js": "~0.9.3",
"accounting": "~0.*",
"spectrum": "~1.3.4",
"d3": "~3.4.11",
"underscore": "1.7.0",
"jspdf": "1.0.272",
"bootstrap-datepicker": "1.4.0",
"typeahead.js": "0.9.3",
"accounting": "0.3.2",
"spectrum": "1.3.4",
"d3": "3.4.11",
"handsontable": "*",
"pdfmake": "*",
"moment": "*"

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');
});
}
}

60
public/css/built.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -27152,31 +27152,13 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
}
}.call(this));
/* =========================================================
* bootstrap-datepicker.js
* Repo: https://github.com/eternicode/bootstrap-datepicker/
* Demo: http://eternicode.github.io/bootstrap-datepicker/
* Docs: http://bootstrap-datepicker.readthedocs.org/
* Forked from http://www.eyecon.ro/bootstrap-datepicker
* =========================================================
* Started by Stefan Petre; improvements by Andrew Rowls + contributors
/*!
* Datepicker for Bootstrap v1.4.0 (https://github.com/eternicode/bootstrap-datepicker)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
(function($, undefined){
var $window = $(window);
* Copyright 2012 Stefan Petre
* Improvements by Andrew Rowls
* Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/(function($, undefined){
function UTCDate(){
return new Date(Date.UTC.apply(Date, arguments));
@ -27185,6 +27167,13 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
var today = new Date();
return UTCDate(today.getFullYear(), today.getMonth(), today.getDate());
}
function isUTCEquals(date1, date2) {
return (
date1.getUTCFullYear() === date2.getUTCFullYear() &&
date1.getUTCMonth() === date2.getUTCMonth() &&
date1.getUTCDate() === date2.getUTCDate()
);
}
function alias(method){
return function(){
return this[method].apply(this, arguments);
@ -27238,16 +27227,16 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
// Picker object
var Datepicker = function(element, options){
this.dates = new DateArray();
this.viewDate = UTCToday();
this.focusDate = null;
this._process_options(options);
this.dates = new DateArray();
this.viewDate = this.o.defaultViewDate;
this.focusDate = null;
this.element = $(element);
this.isInline = false;
this.isInput = this.element.is('input');
this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false;
this.component = this.element.hasClass('date') ? this.element.find('.add-on, .input-group-addon, .btn') : false;
this.hasInput = this.component && this.element.find('input').length;
if (this.component && this.component.length === 0)
this.component = false;
@ -27270,7 +27259,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this.viewMode = this.o.startView;
if (this.o.calendarWeeks)
this.picker.find('tfoot th.today, tfoot th.clear')
this.picker.find('tfoot .today, tfoot .clear')
.attr('colspan', function(i, val){
return parseInt(val) + 1;
});
@ -27280,6 +27269,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this.setStartDate(this._o.startDate);
this.setEndDate(this._o.endDate);
this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
this.setDatesDisabled(this.o.datesDisabled);
this.fillDow();
this.fillMonths();
@ -27346,8 +27336,6 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
o.multidate = Number(o.multidate) || false;
if (o.multidate !== false)
o.multidate = Math.max(0, o.multidate);
else
o.multidate = 1;
}
o.multidateSeparator = String(o.multidateSeparator);
@ -27385,10 +27373,20 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
return parseInt(d, 10);
});
o.datesDisabled = o.datesDisabled||[];
if (!$.isArray(o.datesDisabled)) {
var datesDisabled = [];
datesDisabled.push(DPGlobal.parseDate(o.datesDisabled, format, o.language));
o.datesDisabled = datesDisabled;
}
o.datesDisabled = $.map(o.datesDisabled,function(d){
return DPGlobal.parseDate(d, format, o.language);
});
var plc = String(o.orientation).toLowerCase().split(/\s+/g),
_plc = o.orientation.toLowerCase();
plc = $.grep(plc, function(word){
return (/^auto|left|right|top|bottom$/).test(word);
return /^auto|left|right|top|bottom$/.test(word);
});
o.orientation = {x: 'auto', y: 'auto'};
if (!_plc || _plc === 'auto')
@ -27407,15 +27405,24 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
}
else {
_plc = $.grep(plc, function(word){
return (/^left|right$/).test(word);
return /^left|right$/.test(word);
});
o.orientation.x = _plc[0] || 'auto';
_plc = $.grep(plc, function(word){
return (/^top|bottom$/).test(word);
return /^top|bottom$/.test(word);
});
o.orientation.y = _plc[0] || 'auto';
}
if (o.defaultViewDate) {
var year = o.defaultViewDate.year || new Date().getFullYear();
var month = o.defaultViewDate.month || 0;
var day = o.defaultViewDate.day || 1;
o.defaultViewDate = UTCDate(year, month, day);
} else {
o.defaultViewDate = UTCToday();
}
o.showOnFocus = o.showOnFocus !== undefined ? o.showOnFocus : true;
},
_events: [],
_secondaryEvents: [],
@ -27448,34 +27455,32 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
}
},
_buildEvents: function(){
if (this.isInput){ // single input
this._events = [
[this.element, {
focus: $.proxy(this.show, this),
keyup: $.proxy(function(e){
if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1)
this.update();
}, this),
keydown: $.proxy(this.keydown, this)
}]
];
}
else if (this.component && this.hasInput){ // component: input + button
this._events = [
// For components that are not readonly, allow keyboard nav
[this.element.find('input'), {
focus: $.proxy(this.show, this),
keyup: $.proxy(function(e){
if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1)
this.update();
}, this),
keydown: $.proxy(this.keydown, this)
}],
[this.component, {
click: $.proxy(this.show, this)
}]
];
}
var events = {
keyup: $.proxy(function(e){
if ($.inArray(e.keyCode, [27, 37, 39, 38, 40, 32, 13, 9]) === -1)
this.update();
}, this),
keydown: $.proxy(this.keydown, this)
};
if (this.o.showOnFocus === true) {
events.focus = $.proxy(this.show, this);
}
if (this.isInput) { // single input
this._events = [
[this.element, events]
];
}
else if (this.component && this.hasInput) { // component: input + button
this._events = [
// For components that are not readonly, allow keyboard nav
[this.element.find('input'), events],
[this.component, {
click: $.proxy(this.show, this)
}]
];
}
else if (this.element.is('div')){ // inline datepicker
this.isInline = true;
}
@ -27562,19 +27567,25 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
},
show: function(){
if (this.element.attr('readonly') && this.o.enableOnReadonly === false)
return;
if (!this.isInline)
this.picker.appendTo('body');
this.picker.show();
this.picker.appendTo(this.o.container);
this.place();
this.picker.show();
this._attachSecondaryEvents();
this._trigger('show');
if ((window.navigator.msMaxTouchPoints || 'ontouchstart' in document) && this.o.disableTouchKeyboard) {
$(this.element).blur();
}
return this;
},
hide: function(){
if (this.isInline)
return;
return this;
if (!this.picker.is(':visible'))
return;
return this;
this.focusDate = null;
this.picker.hide().detach();
this._detachSecondaryEvents();
@ -27590,6 +27601,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
)
this.setValue();
this._trigger('hide');
return this;
},
remove: function(){
@ -27601,6 +27613,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
if (!this.isInput){
delete this.element.data().date;
}
return this;
},
_utc_to_local: function(utc){
@ -27631,14 +27644,39 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
},
getUTCDate: function(){
return new Date(this.dates.get(-1));
var selected_date = this.dates.get(-1);
if (typeof selected_date !== 'undefined') {
return new Date(selected_date);
} else {
return null;
}
},
clearDates: function(){
var element;
if (this.isInput) {
element = this.element;
} else if (this.component) {
element = this.element.find('input');
}
if (element) {
element.val('').change();
}
this.update();
this._trigger('changeDate');
if (this.o.autoclose) {
this.hide();
}
},
setDates: function(){
var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
this.update.apply(this, args);
this._trigger('changeDate');
this.setValue();
return this;
},
setUTCDates: function(){
@ -27646,6 +27684,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this.update.apply(this, $.map(args, this._utc_to_local));
this._trigger('changeDate');
this.setValue();
return this;
},
setDate: alias('setDates'),
@ -27661,6 +27700,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
else {
this.element.val(formatted).change();
}
return this;
},
getFormattedDate: function(format){
@ -27677,41 +27717,51 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this._process_options({startDate: startDate});
this.update();
this.updateNavArrows();
return this;
},
setEndDate: function(endDate){
this._process_options({endDate: endDate});
this.update();
this.updateNavArrows();
return this;
},
setDaysOfWeekDisabled: function(daysOfWeekDisabled){
this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
this.update();
this.updateNavArrows();
return this;
},
setDatesDisabled: function(datesDisabled){
this._process_options({datesDisabled: datesDisabled});
this.update();
this.updateNavArrows();
},
place: function(){
if (this.isInline)
return;
return this;
var calendarWidth = this.picker.outerWidth(),
calendarHeight = this.picker.outerHeight(),
visualPadding = 10,
windowWidth = $window.width(),
windowHeight = $window.height(),
scrollTop = $window.scrollTop();
windowWidth = $(this.o.container).width(),
windowHeight = $(this.o.container).height(),
scrollTop = $(this.o.container).scrollTop(),
appendOffset = $(this.o.container).offset();
var parentsZindex = [];
this.element.parents().each(function() {
this.element.parents().each(function(){
var itemZIndex = $(this).css('z-index');
if ( itemZIndex !== 'auto' && itemZIndex !== 0 ) parentsZindex.push( parseInt( itemZIndex ) );
if (itemZIndex !== 'auto' && itemZIndex !== 0) parentsZindex.push(parseInt(itemZIndex));
});
var zIndex = Math.max.apply( Math, parentsZindex ) + 10;
var zIndex = Math.max.apply(Math, parentsZindex) + 10;
var offset = this.component ? this.component.parent().offset() : this.element.offset();
var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false);
var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false);
var left = offset.left,
top = offset.top;
var left = offset.left - appendOffset.left,
top = offset.top - appendOffset.top;
this.picker.removeClass(
'datepicker-orient-top datepicker-orient-bottom '+
@ -27726,12 +27776,18 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
// auto x orientation is best-placement: if it crosses a window
// edge, fudge it sideways
else {
// Default to left
this.picker.addClass('datepicker-orient-left');
if (offset.left < 0)
if (offset.left < 0) {
// component is outside the window on the left side. Move it into visible range
this.picker.addClass('datepicker-orient-left');
left -= offset.left - visualPadding;
else if (offset.left + calendarWidth > windowWidth)
left = windowWidth - calendarWidth - visualPadding;
} else if (left + calendarWidth > windowWidth) {
// the calendar passes the widow right edge. Align it to component right side
this.picker.addClass('datepicker-orient-right');
left = offset.left + width - calendarWidth;
} else {
// Default to left
this.picker.addClass('datepicker-orient-left');
}
}
// auto y orientation is best-situation: top or bottom, no fudging,
@ -27739,8 +27795,8 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
var yorient = this.o.orientation.y,
top_overflow, bottom_overflow;
if (yorient === 'auto'){
top_overflow = -scrollTop + offset.top - calendarHeight;
bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight);
top_overflow = -scrollTop + top - calendarHeight;
bottom_overflow = scrollTop + windowHeight - (top + height + calendarHeight);
if (Math.max(top_overflow, bottom_overflow) === bottom_overflow)
yorient = 'top';
else
@ -27752,17 +27808,27 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
else
top -= calendarHeight + parseInt(this.picker.css('padding-top'));
this.picker.css({
top: top,
left: left,
zIndex: zIndex
});
if (this.o.rtl) {
var right = windowWidth - (left + width);
this.picker.css({
top: top,
right: right,
zIndex: zIndex
});
} else {
this.picker.css({
top: top,
left: left,
zIndex: zIndex
});
}
return this;
},
_allow_update: true,
update: function(){
if (!this._allow_update)
return;
return this;
var oldDates = this.dates.copy(),
dates = [],
@ -27818,15 +27884,19 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this._trigger('clearDate');
this.fill();
return this;
},
fillDow: function(){
var dowCnt = this.o.weekStart,
html = '<tr>';
if (this.o.calendarWeeks){
var cell = '<th class="cw">&nbsp;</th>';
this.picker.find('.datepicker-days thead tr:first-child .datepicker-switch')
.attr('colspan', function(i, val){
return parseInt(val) + 1;
});
var cell = '<th class="cw">&#160;</th>';
html += cell;
this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
}
while (dowCnt < this.o.weekStart + 7){
html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
@ -27880,6 +27950,12 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){
cls.push('disabled');
}
if (this.o.datesDisabled.length > 0 &&
$.grep(this.o.datesDisabled, function(d){
return isUTCEquals(date, d); }).length > 0) {
cls.push('disabled', 'disabled-date');
}
if (this.range){
if (date > this.range[0] && date < this.range[this.range.length-1]){
cls.push('range');
@ -27902,13 +27978,14 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
todaytxt = dates[this.o.language].today || dates['en'].today || '',
cleartxt = dates[this.o.language].clear || dates['en'].clear || '',
tooltip;
if (isNaN(year) || isNaN(month)) return;
this.picker.find('.datepicker-days thead th.datepicker-switch')
if (isNaN(year) || isNaN(month))
return;
this.picker.find('.datepicker-days thead .datepicker-switch')
.text(dates[this.o.language].months[month]+' '+year);
this.picker.find('tfoot th.today')
this.picker.find('tfoot .today')
.text(todaytxt)
.toggle(this.o.todayBtn !== false);
this.picker.find('tfoot th.clear')
this.picker.find('tfoot .clear')
.text(cleartxt)
.toggle(this.o.clearBtn !== false);
this.updateNavArrows();
@ -27991,6 +28068,18 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
months.slice(endMonth+1).addClass('disabled');
}
if (this.o.beforeShowMonth !== $.noop){
var that = this;
$.each(months, function(i, month){
if (!$(month).hasClass('disabled')) {
var moDate = new Date(year, i, 1);
var before = that.o.beforeShowMonth(moDate);
if (before === false)
$(month).addClass('disabled');
}
});
}
html = '';
year = parseInt(year/10, 10) * 10;
var yearCont = this.picker.find('.datepicker-years')
@ -28013,7 +28102,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
classes.push('active');
if (year < startYear || year > endYear)
classes.push('disabled');
html += '<span class="' + classes.join(' ') + '">'+year+'</span>';
html += '<span class="' + classes.join(' ') + '">' + year + '</span>';
year += 1;
}
yearCont.html(html);
@ -28096,24 +28185,14 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this._setDate(date, which);
break;
case 'clear':
var element;
if (this.isInput)
element = this.element;
else if (this.component)
element = this.element.find('input');
if (element)
element.val("").change();
this.update();
this._trigger('changeDate');
if (this.o.autoclose)
this.hide();
this.clearDates();
break;
}
break;
case 'span':
if (!target.is('.disabled')){
if (!target.hasClass('disabled')){
this.viewDate.setUTCDate(1);
if (target.is('.month')){
if (target.hasClass('month')){
day = 1;
month = target.parent().find('span').index(target);
year = this.viewDate.getUTCFullYear();
@ -28138,11 +28217,11 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
}
break;
case 'td':
if (target.is('.day') && !target.is('.disabled')){
if (target.hasClass('day') && !target.hasClass('disabled')){
day = parseInt(target.text(), 10)||1;
year = this.viewDate.getUTCFullYear();
month = this.viewDate.getUTCMonth();
if (target.is('.old')){
if (target.hasClass('old')){
if (month === 0){
month = 11;
year -= 1;
@ -28151,7 +28230,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
month -= 1;
}
}
else if (target.is('.new')){
else if (target.hasClass('new')){
if (month === 11){
month = 0;
year += 1;
@ -28176,15 +28255,19 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
if (!date){
this.dates.clear();
}
if (this.o.multidate === 1 && ix === 0){
// single datepicker, don't remove selected date
}
else if (ix !== -1){
this.dates.remove(ix);
if (ix !== -1){
if (this.o.multidate === true || this.o.multidate > 1 || this.o.toggleActive){
this.dates.remove(ix);
}
} else if (this.o.multidate === false) {
this.dates.clear();
this.dates.push(date);
}
else {
this.dates.push(date);
}
if (typeof this.o.multidate === 'number')
while (this.dates.length > this.o.multidate)
this.dates.remove(0);
@ -28198,7 +28281,9 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this.fill();
this.setValue();
this._trigger('changeDate');
if (!which || which !== 'view') {
this._trigger('changeDate');
}
var element;
if (this.isInput){
element = this.element;
@ -28273,7 +28358,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
},
keydown: function(e){
if (this.picker.is(':not(:visible)')){
if (!this.picker.is(':visible')){
if (e.keyCode === 27) // allow escape to hide and re-show picker
this.show();
return;
@ -28313,7 +28398,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
newViewDate = new Date(focusDate);
newViewDate.setUTCDate(focusDate.getUTCDate() + dir);
}
if (this.dateWithinRange(newDate)){
if (this.dateWithinRange(newViewDate)){
this.focusDate = this.viewDate = newViewDate;
this.setValue();
this.fill();
@ -28341,7 +28426,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
newViewDate = new Date(focusDate);
newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7);
}
if (this.dateWithinRange(newDate)){
if (this.dateWithinRange(newViewDate)){
this.focusDate = this.viewDate = newViewDate;
this.setValue();
this.fill();
@ -28364,6 +28449,11 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this.fill();
if (this.picker.is(':visible')){
e.preventDefault();
if (typeof e.stopPropagation === 'function') {
e.stopPropagation(); // All modern browsers, IE9+
} else {
e.cancelBubble = true; // IE6,7,8 ignore "stopPropagation"
}
if (this.o.autoclose)
this.hide();
}
@ -28398,9 +28488,9 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
}
this.picker
.find('>div')
.children('div')
.hide()
.filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName)
.filter('.datepicker-' + DPGlobal.modes[this.viewMode].clsName)
.css('display', 'block');
this.updateNavArrows();
}
@ -28413,8 +28503,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
});
delete options.inputs;
$(this.inputs)
.datepicker(options)
datepickerPlugin.call($(this.inputs), options)
.bind('changeDate', $.proxy(this.dateUpdated, this));
this.pickers = $.map(this.inputs, function(i){
@ -28448,6 +28537,8 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
var dp = $(e.target).data('datepicker'),
new_date = dp.getUTCDate(),
i = $.inArray(e.target, this.inputs),
j = i - 1,
k = i + 1,
l = this.inputs.length;
if (i === -1)
return;
@ -28457,16 +28548,16 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
p.setUTCDate(new_date);
});
if (new_date < this.dates[i]){
if (new_date < this.dates[j]){
// Date being moved earlier/left
while (i >= 0 && new_date < this.dates[i]){
this.pickers[i--].setUTCDate(new_date);
while (j >= 0 && new_date < this.dates[j]){
this.pickers[j--].setUTCDate(new_date);
}
}
else if (new_date > this.dates[i]){
else if (new_date > this.dates[k]){
// Date being moved later/right
while (i < l && new_date > this.dates[i]){
this.pickers[i++].setUTCDate(new_date);
while (k < l && new_date > this.dates[k]){
this.pickers[k++].setUTCDate(new_date);
}
}
this.updateDates();
@ -28515,7 +28606,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
}
var old = $.fn.datepicker;
$.fn.datepicker = function(option){
var datepickerPlugin = function(option){
var args = Array.apply(null, arguments);
args.shift();
var internal_return;
@ -28530,7 +28621,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
locopts = opts_from_locale(xopts.language),
// Options priority: js args, data-attrs, locales, defaults
opts = $.extend({}, defaults, locopts, elopts, options);
if ($this.is('.input-daterange') || opts.inputs){
if ($this.hasClass('input-daterange') || opts.inputs){
var ropts = {
inputs: opts.inputs || $this.find('input').toArray()
};
@ -28551,13 +28642,17 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
else
return this;
};
$.fn.datepicker = datepickerPlugin;
var defaults = $.fn.datepicker.defaults = {
autoclose: false,
beforeShowDay: $.noop,
beforeShowMonth: $.noop,
calendarWeeks: false,
clearBtn: false,
toggleActive: false,
daysOfWeekDisabled: [],
datesDisabled: [],
endDate: Infinity,
forceParse: true,
format: 'mm/dd/yyyy',
@ -28572,7 +28667,10 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
startView: 0,
todayBtn: false,
todayHighlight: false,
weekStart: 0
weekStart: 0,
disableTouchKeyboard: false,
enableOnReadonly: true,
container: 'body'
};
var locale_opts = $.fn.datepicker.locale_opts = [
'format',
@ -28700,7 +28798,7 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
function match_part(){
var m = this.slice(0, parts[i].length),
p = parts[i].slice(0, m.length);
return m === p;
return m.toLowerCase() === p.toLowerCase();
}
if (parts.length === fparts.length){
var cnt;
@ -28762,9 +28860,9 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
},
headTemplate: '<thead>'+
'<tr>'+
'<th class="prev">&laquo;</th>'+
'<th class="prev">&#171;</th>'+
'<th colspan="5" class="datepicker-switch"></th>'+
'<th class="next">&raquo;</th>'+
'<th class="next">&#187;</th>'+
'</tr>'+
'</thead>',
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
@ -28812,6 +28910,9 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
return this;
};
/* DATEPICKER VERSION
* =================== */
$.fn.datepicker.version = "1.4.0";
/* DATEPICKER DATA-API
* ================== */
@ -28825,11 +28926,11 @@ d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.p
return;
e.preventDefault();
// component click requires us to explicitly show it
$this.datepicker('show');
datepickerPlugin.call($this, 'show');
}
);
$(function(){
$('[data-provide="datepicker-inline"]').datepicker();
datepickerPlugin.call($('[data-provide="datepicker-inline"]'));
});
}(window.jQuery));
@ -32240,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)})
@ -32297,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 = {

File diff suppressed because one or more lines are too long

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

@ -419,7 +419,7 @@ return array(
'confirm_email_quote' => 'Bist du sicher, dass du dieses Angebot per E-Mail versenden möchtest',
'confirm_recurring_email_invoice' => 'Wiederkehrende Rechnung ist aktiv. Bis du sicher, dass du diese Rechnung weiterhin als E-Mail verschicken möchtest?',
'cancel_account' => 'Account Kündigen',
'cancel_account' => 'Konto Kündigen',
'cancel_account_message' => 'Warnung: Alle Daten werden unwiderruflich und vollständig gelöscht, es gibt kein zurück.',
'go_back' => 'Zurück',
@ -481,7 +481,7 @@ return array(
'restored_client' => 'Kunde erfolgreich wiederhergestellt',
'restored_payment' => 'Zahlung erfolgreich wiederhergestellt',
'restored_credit' => 'Guthaben erfolgreich wiederhergestellt',
'reason_for_canceling' => 'Hilf uns, unser Angebot zu verbessern, indem du uns mitteilst, weswegen du dich dazu entschieden hast, unseren Service nicht länger zu nutzen.',
'discount_percent' => 'Prozent',
'discount_amount' => 'Wert',
@ -562,7 +562,7 @@ return array(
'api_tokens' => 'API Token',
'users_and_tokens' => 'Benutzer & Token',
'account_login' => 'Account Login',
'account_login' => 'Konto Login',
'recover_password' => 'Passwort wiederherstellen',
'forgot_password' => 'Passwort vergessen?',
'email_address' => 'E-Mail-Adresse',
@ -597,7 +597,7 @@ return array(
'view_documentation' => 'Dokumentation anzeigen',
'app_title' => 'Kostenlose Online Open-Source Rechnungsausstellung',
'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' => 'Zeilen',
'www' => 'www',
'logo' => 'Logo',
@ -660,7 +660,7 @@ return array(
'create_task' => 'Aufgabe erstellen',
'stopped_task' => 'Aufgabe erfolgreich angehalten',
'invoice_task' => 'Aufgabe in Rechnung stellen',
'invoice_labels' => 'Rechnung Etiketten',
'invoice_labels' => 'Rechnung Spaltenüberschriften',
'prefix' => 'Präfix',
'counter' => 'Zähler',
@ -670,32 +670,32 @@ return array(
'more_actions' => 'More Actions',
'pro_plan_title' => 'NINJA PRO',
'pro_plan_call_to_action' => 'Upgrade Now!',
'pro_plan_feature1' => 'Create Unlimited Clients',
'pro_plan_feature2' => 'Access to 10 Beautiful Invoice Designs',
'pro_plan_feature3' => 'Custom URLs - "YourBrand.InvoiceNinja.com"',
'pro_plan_feature4' => 'Remove "Created by Invoice Ninja"',
'pro_plan_feature5' => 'Multi-user Access & Activity Tracking',
'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',
'pro_plan_call_to_action' => 'Jetzt Upgraden!',
'pro_plan_feature1' => 'Unlimitierte Anzahl Kunden erstellen',
'pro_plan_feature2' => 'Zugriff ui 10 schönen Rechnungsdesigns',
'pro_plan_feature3' => 'Benutzerdefinierte URLs - "DeineFirma.InvoiceNinja.com"',
'pro_plan_feature4' => '"Created by Invoice Ninja" entfernen',
'pro_plan_feature5' => 'Multi-Benutzer Zugriff & Aktivitätstracking',
'pro_plan_feature6' => 'Angebote & pro-forma Rechnungen erstellen',
'pro_plan_feature7' => 'Rechungstitelfelder und Nummerierung anpassen',
'pro_plan_feature8' => 'PDFs an E-Mails zu Kunden anhängen',
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'resume' => 'Fortfahren',
'break_duration' => 'Pause',
'edit_details' => 'Details bearbeiten',
'work' => 'Arbeiten',
'timezone_unset' => 'Bitte :link um deine Zeitzone zu setzen',
'click_here' => 'hier klicken',
'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'email_receipt' => 'Zahlungsbestätigung an Kunden per E-Mail senden',
'created_payment_emailed_client' => 'Zahlung erfolgreich erstellt und Kunde per E-Mail benachrichtigt',
'add_account' => 'Konto hinzufügen',
'untitled' => 'Unbenannt',
'new_account' => 'Neues Konto',
'associated_accounts' => 'Konten erfolgreich verlinkt',
'unlinked_account' => 'Konten erfolgreich getrennt',
'login' => 'Login',
'or' => 'or',
'or' => 'oder',
);

View File

@ -704,4 +704,12 @@ return array(
'login' => 'Login',
'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

@ -14,7 +14,7 @@ return array(
'state' => 'Région/Département',
'postal_code' => 'Code postal',
'country_id' => 'Pays',
'contacts' => 'Informations de contact', //if you speak about contact details
'contacts' => 'Informations de contact',
'first_name' => 'Prénom',
'last_name' => 'Nom',
'phone' => 'Téléphone',
@ -23,7 +23,7 @@ return array(
'payment_terms' => 'Conditions de paiement',
'currency_id' => 'Devise',
'size_id' => 'Taille',
'industry_id' => 'Secteur', // literal translation : Industrie
'industry_id' => 'Secteur',
'private_notes' => 'Note personnelle',
// invoice
@ -45,11 +45,11 @@ return array(
'quantity' => 'Quantité',
'line_total' => 'Total',
'subtotal' => 'Total',
'paid_to_date' => 'Versé à ce jour',//this one is not very used in France
'balance_due' => 'Montant total',//can be "Montant à verser" or "Somme totale"
'invoice_design_id' => 'Design', //if you speak about invoice's design -> "Modèle"
'paid_to_date' => 'Versé à ce jour',
'balance_due' => 'Montant total',
'invoice_design_id' => 'Design',
'terms' => 'Conditions',
'your_invoice' => 'Votre Facture',
'your_invoice' => 'Votre facture',
'remove_contact' => 'Supprimer un contact',
'add_contact' => 'Ajouter un contact',
@ -211,8 +211,8 @@ return array(
// application messages
'created_client' => 'Client créé avec succès',
'created_clients' => ':count clients créés avec csuccès',
'updated_settings' => 'paramètres mis à jour avec succès',
'created_clients' => ':count clients créés avec succès',
'updated_settings' => 'Paramètres mis à jour avec succès',
'removed_logo' => 'Logo supprimé avec succès',
'sent_message' => 'Message envoyé avec succès',
'invoice_error' => 'Veuillez vous assurer de sélectionner un client et de corriger les erreurs',
@ -261,7 +261,7 @@ return array(
'payment_message' => 'Merci pour votre paiement d\'un montant de :amount',
'email_salutation' => 'Cher :name,',
'email_signature' => 'Cordialement,',
'email_from' => 'L\'équipe InvoiceNinja',
'email_from' => 'L\'équipe Invoice Ninja',
'user_email_footer' => 'Pour modifier vos paramètres de notification par courriel, veuillez visiter '.SITE_URL.'/company/notifications',
'invoice_link_message' => 'Pour voir la facture de votre client cliquez sur le lien ci-après :',
'notification_invoice_paid_subject' => 'La facture :invoice a été payée par le client :client',
@ -293,7 +293,7 @@ return array(
// Pro Plan
'pro_plan' => [
'remove_logo' => ':link pour supprimer le logo Invoice Ninja en souscrivant au plan Pro',
'remove_logo' => ':link pour supprimer le logo Invoice Ninja en souscrivant au Plan Pro',
'remove_logo_link' => 'Cliquez ici',
],
@ -456,10 +456,10 @@ return array(
'sent' => 'envoyé',
'timesheets' => 'Feuilles de temps',
'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.',
'payment_title' => 'Entrez votre adresse de facturation et vos informations bancaires',
'payment_cvv' => '*Numéro à 3 ou 4 chiffres au dos de votre carte',
'payment_footer1' => '*L\'adresse de facturation doit correspondre à celle enregistrée avec votre carte bancaire',
'payment_footer2' => '*Merci de cliquer sur "Payer maintenant" une seule fois. Le processus peut prendre jusqu\'à 1 minute.',
'vat_number' => 'Numéro de TVA',
'id_number' => 'Numéro ID',
@ -492,18 +492,18 @@ return array(
'select_versiony' => 'Choix de la verison',
'view_history' => 'Consulter l\'historique',
'edit_payment' => 'Edit Payment',
'updated_payment' => 'Successfully updated payment',
'deleted' => 'Deleted',
'restore_user' => 'Restore User',
'restored_user' => 'Successfully restored user',
'show_deleted_users' => 'Show deleted users',
'email_templates' => 'Email Templates',
'invoice_email' => 'Invoice Email',
'payment_email' => 'Payment Email',
'quote_email' => 'Quote Email',
'reset_all' => 'Reset All',
'approve' => 'Approve',
'edit_payment' => 'Editer le paiement',
'updated_payment' => 'Paiement édité avec succès',
'deleted' => 'Supprimé',
'restore_user' => 'Restaurer l\'utilisateur',
'restored_user' => 'Restaurer la commande',
'show_deleted_users' => 'Voir les utilisateurs supprimés',
'email_templates' => 'Templates de mail',
'invoice_email' => 'Templates de facture',
'payment_email' => 'Email de paiement',
'quote_email' => 'Email de déclaration',
'reset_all' => 'Réinitialiser',
'approve' => 'Accepter',
'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.',
@ -519,18 +519,18 @@ return array(
'token_billing_secure' => 'The data is stored securely by :stripe_link',
'support' => 'Support',
'contact_information' => 'Contact information',
'contact_information' => 'Information de contact',
'256_encryption' => '256-Bit Encryption',
'amount_due' => 'Amount due',
'amount_due' => 'Montant dû',
'billing_address' => 'Billing address',
'billing_method' => 'Billing method',
'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.',
'default_invoice_footer' => 'Set default invoice footer',
'invoice_footer' => 'Invoice footer',
'save_as_default_footer' => 'Save as default footer',
'default_invoice_footer' => 'Définir par défaut',
'invoice_footer' => 'Pied de facture',
'save_as_default_footer' => 'Définir comme pied de facture par défatu',
'token_management' => 'Token Management',
'tokens' => 'Tokens',
@ -543,12 +543,12 @@ return array(
'delete_token' => 'Delete Token',
'token' => 'Token',
'add_gateway' => 'Add Gateway',
'delete_gateway' => 'Delete Gateway',
'edit_gateway' => 'Edit Gateway',
'updated_gateway' => 'Successfully updated gateway',
'created_gateway' => 'Successfully created gateway',
'deleted_gateway' => 'Successfully deleted gateway',
'add_gateway' => 'Ajouter passerelle',
'delete_gateway' => 'Supprimer passerelle',
'edit_gateway' => 'Editer passerelle',
'updated_gateway' => 'Passerelle mise à jour avec succès',
'created_gateway' => 'Passerelle crée avec succès',
'deleted_gateway' => 'Passerelle supprimée avec succès',
'pay_with_paypal' => 'PayPal',
'pay_with_card' => 'Carte bancaire',
@ -579,44 +579,44 @@ return array(
'confirmation_resent' => 'The confirmation email was resent',
'gateway_help_42' => ':link to sign up for BitPay.<br/>Note: use a Legacy API Key, not an API token.',
'payment_type_credit_card' => 'Credit card',
'payment_type_credit_card' => 'Carte de crédit',
'payment_type_paypal' => 'PayPal',
'payment_type_bitcoin' => 'Bitcoin',
'knowledge_base' => 'Knowledge Base',
'partial' => 'Partial',
'partial_remaining' => ':partial of :balance',
'knowledge_base' => 'Base de connaissances',
'partial' => 'Partiel',
'partial_remaining' => ':partial de :balance',
'more_fields' => 'More Fields',
'less_fields' => 'Less Fields',
'client_name' => 'Client Name',
'pdf_settings' => 'PDF Settings',
'more_fields' => 'Plus de champs',
'less_fields' => 'Moins de champs',
'client_name' => 'Nom du client',
'pdf_settings' => 'Réglages PDF',
'utf8_invoices' => 'Cyrillic Support <sup>Beta</sup>',
'product_settings' => 'Product Settings',
'product_settings' => 'Réglages du produit',
'auto_wrap' => 'Auto Line Wrap',
'duplicate_post' => 'Warning: the previous page was submitted twice. The second submission had been ignored.',
'view_documentation' => 'View Documentation',
'view_documentation' => 'Voir 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',
'rows' => 'lignes',
'www' => 'www',
'logo' => 'Logo',
'subdomain' => 'Subdomain',
'provide_name_or_email' => 'Please provide a contact name or email',
'subdomain' => 'Sous domaine',
'provide_name_or_email' => 'Merci d\'indiquer un nom ou une adresse email',
'charts_and_reports' => 'Charts & Reports',
'chart' => 'Chart',
'report' => 'Report',
'group_by' => 'Group by',
'paid' => 'Paid',
'group_by' => 'Grouper par',
'paid' => 'Pa',
'enable_report' => 'Report',
'enable_chart' => 'Chart',
'totals' => 'Totals',
'run' => 'Run',
'export' => 'Export',
'export' => 'Exporter',
'documentation' => 'Documentation',
'zapier' => 'Zapier <sup>Beta</sup>',
'recurring' => 'Recurring',
'last_invoice_sent' => 'Last invoice sent :date',
'recurring' => 'Récurrent',
'last_invoice_sent' => 'Dernière facture envoyée le :date',
'processed_updates' => 'Mise à jour effectuée avec succès',
'tasks' => 'Tâches',
@ -641,7 +641,7 @@ return array(
'minute' => 'minute',
'minutes' => 'minutes',
'hour' => 'heure',
'hours' => 'houres',
'hours' => 'heures',
'task_details' => 'Détails tâche',
'duration' => 'Durée',
'end_time' => 'Heure de fin',
@ -655,8 +655,8 @@ return array(
'restored_task' => 'Tâche restaurée avec succès',
'archived_task' => 'Tâche archivée avec succès',
'archived_tasks' => ':count tâches archivées avec succès',
'deleted_task' => 'Tâche supprimée avec succès,'
'deleted_tasks' => ':count tâches supprimées avec syccès',
'deleted_task' => 'Tâche supprimée avec succès',
'deleted_tasks' => ':count tâches supprimées avec succès',
'create_task' => 'Créer tâche',
'stopped_task' => 'Tâche stoppée avec succès',
'invoice_task' => 'Tâche facturation',
@ -682,20 +682,20 @@ return array(
'resume' => 'Resume',
'break_duration' => 'Break',
'edit_details' => 'Edit Details',
'work' => 'Work',
'edit_details' => 'Editer détails',
'work' => 'Travail',
'timezone_unset' => 'Please :link to set your timezone',
'click_here' => 'click here',
'click_here' => 'cliquer ici',
'email_receipt' => 'Email payment receipt to the client',
'created_payment_emailed_client' => 'Successfully created payment and emailed client',
'add_account' => 'Add Account',
'untitled' => 'Untitled',
'new_account' => 'New Account',
'created_payment_emailed_client' => 'Paiement crée avec succès et envoyé au client',
'add_account' => 'Ajouter compte',
'untitled' => 'Sans titre',
'new_account' => 'Nouveau compte',
'associated_accounts' => 'Successfully linked accounts',
'unlinked_account' => 'Successfully unlinked accounts',
'login' => 'Login',
'or' => 'or',
'login' => 'Connexion',
'or' => 'ou',

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

@ -112,7 +112,7 @@
<i class="fa fa-envelope" style="width: 20px"></i>{!! HTML::mailto($contact->email, $contact->email) !!}<br/>
@endif
@if ($contact->phone)
<i class="fa fa-phone" style="width: 20px"></i>{!! Utils::formatPhoneNumber($contact->phone) !!}
<i class="fa fa-phone" style="width: 20px"></i>{!! Utils::formatPhoneNumber($contact->phone) !!}<br/>
@endif
@endforeach
</div>
@ -305,4 +305,4 @@
</script>
@stop
@stop

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>
@ -454,7 +480,7 @@
@endif
@if (Session::has('error'))
<div class="alert alert-danger">{{ Session::get('error') }}</div>
<div class="alert alert-danger">{!! Session::get('error') !!}</div>
@endif
@if (!isset($showBreadcrumbs) || $showBreadcrumbs)
@ -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

@ -36,7 +36,7 @@
@if (count($paymentTypes) > 1)
{!! DropdownButton::success(trans('texts.pay_now'))->withContents($paymentTypes)->large() !!}
@else
{!! Button::success(trans('texts.pay_now'))->asLinkTo(URL::to('/payment/' . $invitation->invitation_key))->large() !!}
{!! Button::success(trans('texts.pay_now'))->asLinkTo(URL::to($paymentURL))->large() !!}
@endif
@else
{!! Button::normal('Download PDF')->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="{{App::getLocale()}}">
<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

@ -6,10 +6,11 @@
<meta name="csrf-token" content="<?= csrf_token() ?>">
<script src="{{ asset('js/built.js') }}?no_cache={{ NINJA_VERSION }}" type="text/javascript"></script>
<link href="{{ asset('css/built.public.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
<link href="{{ asset('css/built.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
<style type="text/css">
body {
background-color: #f8f8f8;
background-color: #FEFEFE;
}
</style>
@ -35,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
@ -64,7 +65,7 @@ FLUSH PRIVILEGES;</pre>
<h3 class="panel-title">Application Settings</h3>
</div>
<div class="panel-body">
{!! Former::text('app[url]')->label('URL')->value(Request::root()) !!}
{!! Former::text('app[url]')->label('URL')->value(isset($_ENV['APP_URL']) ? $_ENV['APP_URL'] : Request::root()) !!}
</div>
</div>
@ -73,12 +74,17 @@ FLUSH PRIVILEGES;</pre>
<h3 class="panel-title">Database Connection</h3>
</div>
<div class="panel-body">
{!! Former::select('database[default]')->label('Driver')->options(['mysql' => 'MySQL', 'pgsql' => 'PostgreSQL', 'sqlite' => 'SQLite']) !!}
{!! Former::text('database[type][host]')->label('Host')->value('localhost') !!}
{!! Former::text('database[type][database]')->label('Database')->value('ninja') !!}
{!! Former::text('database[type][username]')->label('Username')->value('ninja') !!}
{!! Former::password('database[type][password]')->label('Password')->value('ninja') !!}
{!! Former::actions( Button::normal('Test connection')->withAttributes(['onclick' => 'testDatabase()']), '&nbsp;&nbsp;<span id="dbTestResult"/>' ) !!}
{!! Former::select('database[default]')->label('Driver')->options(['mysql' => 'MySQL', 'pgsql' => 'PostgreSQL', 'sqlite' => 'SQLite'])
->value(isset($_ENV['DB_TYPE']) ? $_ENV['DB_TYPE'] : 'mysql') !!}
{!! Former::text('database[type][host]')->label('Host')->value('localhost')
->value(isset($_ENV['DB_HOST']) ? $_ENV['DB_HOST'] : '') !!}
{!! Former::text('database[type][database]')->label('Database')->value('ninja')
->value(isset($_ENV['DB_DATABASE']) ? $_ENV['DB_DATABASE'] : '') !!}
{!! Former::text('database[type][username]')->label('Username')->value('ninja')
->value(isset($_ENV['DB_USERNAME']) ? $_ENV['DB_USERNAME'] : '') !!}
{!! Former::password('database[type][password]')->label('Password')->value('ninja')
->value(isset($_ENV['DB_PASSWORD']) ? $_ENV['DB_PASSWORD'] : '') !!}
{!! Former::actions( Button::primary('Test connection')->small()->withAttributes(['onclick' => 'testDatabase()']), '&nbsp;&nbsp;<span id="dbTestResult"/>' ) !!}
</div>
</div>
@ -88,14 +94,21 @@ 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::actions( Button::normal('Send test email')->withAttributes(['onclick' => 'testMail()']), '&nbsp;&nbsp;<span id="mailTestResult"/>' ) !!}
{!! 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>
@ -112,8 +125,9 @@ 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')->submit() ) !!}
{!! Former::actions( Button::primary('Submit')->large()->submit() ) !!}
{!! Former::close() !!}
</div>

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>