Merge remote-tracking branch 'upstream/master'

This commit is contained in:
David Bomba 2015-10-14 19:58:23 +11:00
commit 52b4f3f26a
70 changed files with 1602 additions and 2530 deletions

View File

@ -20,6 +20,6 @@ MAIL_FROM_ADDRESS
MAIL_FROM_NAME
MAIL_PASSWORD
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
#PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
LOG=single

View File

@ -120,13 +120,10 @@ module.exports = function(grunt) {
src: [
'public/vendor/bootstrap/dist/css/bootstrap.min.css',
'public/vendor/font-awesome/css/font-awesome.min.css',
/*
'public/css/bootstrap.splash.css',
'public/css/splash.css',
*/
'public/css/bootstrap-combobox.css',
'public/vendor/datatables/media/css/jquery.dataTables.css',
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
'public/css/public.style.css',
],
dest: 'public/css/built.public.css',
nonull: true,

View File

@ -36,6 +36,10 @@ class SendReminders extends Command
$this->info(count($accounts).' accounts found');
foreach ($accounts as $account) {
if (!$account->isPro()) {
continue;
}
$invoices = $this->invoiceRepo->findNeedingReminding($account);
$this->info($account->name . ': ' . count($invoices).' invoices found');

View File

@ -8,14 +8,16 @@ class UserSettingsChanged extends Event {
use SerializesModels;
public $user;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
public function __construct($user = false)
{
//
$this->user = $user;
}
}

View File

@ -373,8 +373,10 @@ class AccountController extends BaseController
$rules = [];
$user = Auth::user();
$iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', substr(strtolower(Input::get('iframe_url')), 0, MAX_IFRAME_URL_LENGTH));
$subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH));
if (!$subdomain || in_array($subdomain, ['www', 'app', 'mail', 'admin', 'blog', 'user', 'contact', 'payment', 'payments', 'billing', 'invoice', 'business', 'owner'])) {
$iframeURL = rtrim($iframeURL, "/");
$subdomain = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', substr(strtolower(Input::get('subdomain')), 0, MAX_SUBDOMAIN_LENGTH));
if ($iframeURL || !$subdomain || in_array($subdomain, ['www', 'app', 'mail', 'admin', 'blog', 'user', 'contact', 'payment', 'payments', 'billing', 'invoice', 'business', 'owner'])) {
$subdomain = null;
}
if ($subdomain) {

View File

@ -20,14 +20,7 @@ class ActivityController extends BaseController
->addColumn('activities.id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
->addColumn('message', function ($model) { return Utils::decodeActivity($model->message); })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? self::wrapAdjustment($model->adjustment, $model->currency_id) : ''; })
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id) : ''; })
->make();
}
private function wrapAdjustment($adjustment, $currencyId)
{
$class = $adjustment <= 0 ? 'success' : 'default';
$adjustment = Utils::formatMoney($adjustment, $currencyId);
return "<h4><div class=\"label label-{$class}\">$adjustment</div></h4>";
}
}

View File

@ -94,7 +94,7 @@ class AppController extends BaseController
"MAIL_USERNAME={$mail['username']}\n".
"MAIL_FROM_NAME={$mail['from']['name']}\n".
"MAIL_PASSWORD={$mail['password']}\n\n".
"PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'";
"#PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'";
// Write Config Settings
$fp = fopen(base_path()."/.env", 'w');

View File

@ -181,6 +181,10 @@ class InvoiceApiController extends Controller
// initialize the line items
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
$data['invoice_items'] = [self::prepareItem($data)];
// make sure the tax isn't applied twice (for the invoice and the line item)
unset($data['invoice_items'][0]['tax_name']);
unset($data['invoice_items'][0]['tax_rate']);
} else {
foreach ($data['invoice_items'] as $index => $item) {
$data['invoice_items'][$index] = self::prepareItem($item);

View File

@ -76,28 +76,6 @@ class InvoiceController extends BaseController
return View::make('list', $data);
}
public function clientIndex()
{
$invitationKey = Session::get('invitation_key');
if (!$invitationKey) {
app()->abort(404);
}
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE,
'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']),
];
return View::make('public_list', $data);
}
public function getDatatable($clientPublicId = null)
{
$accountId = Auth::user()->account_id;
@ -106,25 +84,6 @@ class InvoiceController extends BaseController
return $this->invoiceRepo->getDatatable($accountId, $clientPublicId, ENTITY_INVOICE, $search);
}
public function getClientDatatable()
{
$search = Input::get('sSearch');
$invitationKey = Session::get('invitation_key');
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation || $invitation->is_deleted) {
return [];
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return [];
}
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, $search);
}
public function getRecurringDatatable($clientPublicId = null)
{
$query = $this->invoiceRepo->getRecurringInvoices(Auth::user()->account_id, $clientPublicId, Input::get('sSearch'));
@ -172,42 +131,22 @@ class InvoiceController extends BaseController
public function view($invitationKey)
{
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation) {
app()->abort(404, trans('texts.invoice_not_found'));
}
$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey);
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
app()->abort(404, trans('texts.invoice_not_found'));
}
$invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country');
$client = $invoice->client;
$account = $client->account;
$account = $invoice->account;
if (!$client || $client->is_deleted) {
if (!$account->checkSubdomain(Request::server('HTTP_HOST'))) {
app()->abort(404, trans('texts.invoice_not_found'));
}
if ($account->subdomain) {
$server = explode('.', Request::server('HTTP_HOST'));
$subdomain = $server[0];
if (!in_array($subdomain, ['app', 'www']) && $subdomain != $account->subdomain) {
return View::make('invoices.deleted');
}
}
if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
Activity::viewInvoice($invitation);
Event::fire(new InvoiceViewed($invoice));
}
Session::set($invitationKey, true);
Session::set('invitation_key', $invitationKey);
Session::set($invitationKey, true); // track this invitation has been seen
Session::set('invitation_key', $invitationKey); // track current invitation
$account->loadLocalizationSettings($client);
@ -226,27 +165,16 @@ class InvoiceController extends BaseController
'first_name',
'last_name',
'email',
'phone', ]);
// Determine payment options
$paymentTypes = [];
if ($client->getGatewayToken()) {
$paymentTypes[] = [
'url' => URL::to("payment/{$invitation->invitation_key}/token"), 'label' => trans('texts.use_card_on_file')
];
}
foreach(Gateway::$paymentTypes as $type) {
if ($account->getGatewayByType($type)) {
$typeLink = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
$paymentTypes[] = [
'url' => URL::to("/payment/{$invitation->invitation_key}/{$typeLink}"), 'label' => trans('texts.'.strtolower($type))
];
}
}
'phone',
]);
$paymentTypes = $this->getPaymentTypes($client, $invitation);
$paymentURL = '';
if (count($paymentTypes)) {
$paymentURL = $paymentTypes[0]['url'];
if (!$account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
$paymentURL = URL::to($paymentURL);
}
}
$showApprove = $invoice->quote_invoice_id ? false : true;
@ -271,6 +199,34 @@ class InvoiceController extends BaseController
return View::make('invoices.view', $data);
}
private function getPaymentTypes($client, $invitation)
{
$paymentTypes = [];
$account = $client->account;
if ($client->getGatewayToken()) {
$paymentTypes[] = [
'url' => URL::to("payment/{$invitation->invitation_key}/token"), 'label' => trans('texts.use_card_on_file')
];
}
foreach(Gateway::$paymentTypes as $type) {
if ($account->getGatewayByType($type)) {
$typeLink = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
$url = URL::to("/payment/{$invitation->invitation_key}/{$typeLink}");
// PayPal doesn't allow being run in an iframe so we need to open in new tab
if ($type === PAYMENT_TYPE_PAYPAL && $account->iframe_url) {
$url = 'javascript:window.open("'.$url.'", "_blank")';
}
$paymentTypes[] = [
'url' => $url, 'label' => trans('texts.'.strtolower($type))
];
}
}
return $paymentTypes;
}
public function edit($publicId, $clone = false)
{
$invoice = Invoice::scope($publicId)->withTrashed()->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items')->firstOrFail();
@ -468,121 +424,138 @@ class InvoiceController extends BaseController
{
$action = Input::get('action');
$entityType = Input::get('entityType');
$input = json_decode(Input::get('data'));
if (in_array($action, ['archive', 'delete', 'mark', 'restore'])) {
return InvoiceController::bulk($entityType);
}
$input = json_decode(Input::get('data'));
$invoice = $input->invoice;
if ($errors = $this->invoiceRepo->getErrors($invoice)) {
if ($errors = $this->invoiceRepo->getErrors($input->invoice)) {
Session::flash('error', trans('texts.invoice_error'));
return Redirect::to("{$entityType}s/create")
->withInput()->withErrors($errors);
} else {
$this->taxRateRepo->save($input->tax_rates);
$clientData = (array) $invoice->client;
$client = $this->clientRepo->save($invoice->client->public_id, $clientData);
$invoiceData = (array) $invoice;
$invoiceData['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save($publicId, $invoiceData, $entityType);
$account = Auth::user()->account;
if ($account->invoice_taxes != $input->invoice_taxes
|| $account->invoice_item_taxes != $input->invoice_item_taxes
|| $account->invoice_design_id != $input->invoice->invoice_design_id
|| $account->show_item_taxes != $input->show_item_taxes) {
$account->invoice_taxes = $input->invoice_taxes;
$account->invoice_item_taxes = $input->invoice_item_taxes;
$account->invoice_design_id = $input->invoice->invoice_design_id;
$account->show_item_taxes = $input->show_item_taxes;
$account->save();
}
$client->load('contacts');
$sendInvoiceIds = [];
foreach ($client->contacts as $contact) {
if ($contact->send_invoice || count($client->contacts) == 1) {
$sendInvoiceIds[] = $contact->id;
}
}
foreach ($client->contacts as $contact) {
$invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first();
if (in_array($contact->id, $sendInvoiceIds) && !$invitation) {
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
$invitation->contact_id = $contact->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
} elseif (!in_array($contact->id, $sendInvoiceIds) && $invitation) {
$invitation->delete();
}
}
$invoice = $this->saveInvoice($publicId, $input, $entityType);
$url = "{$entityType}s/".$invoice->public_id.'/edit';
$message = trans($publicId ? "texts.updated_{$entityType}" : "texts.created_{$entityType}");
// check if we created a new client with the invoice
if ($input->invoice->client->public_id == '-1') {
$message = $message.' '.trans('texts.and_created_client');
$url = URL::to('clients/'.$client->public_id);
$url = URL::to('clients/'.$input->invoice->client->public_id);
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT, $url);
}
if ($invoice->account->pdf_email_attachment && !$invoice->is_recurring) {
$pdfUpload = Input::get('pdfupload');
if (!empty($pdfUpload) && strpos($pdfUpload, 'data:application/pdf;base64,') === 0) {
$invoice->updateCachedPDF($pdfUpload);
}
}
if ($action == 'clone') {
return $this->cloneInvoice($publicId);
} elseif ($action == 'convert') {
return $this->convertQuote($publicId);
} elseif ($action == 'email') {
if (Auth::user()->confirmed && !Auth::user()->isDemo()) {
if ($invoice->is_recurring) {
if ($invoice->shouldSendToday()) {
$invoice = $this->invoiceRepo->createRecurringInvoice($invoice);
// in case auto-bill is enabled
if ($invoice->isPaid()) {
$response = true;
} else {
$response = $this->mailer->sendInvoice($invoice);
}
} else {
$response = trans('texts.recurring_too_soon');
}
} else {
$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);
Session::flash('message', $message);
}
} else {
Session::flash('message', $message);
return $this->emailInvoice($invoice, Input::get('pdfupload'));
}
$url = "{$entityType}s/".$invoice->public_id.'/edit';
Session::flash('message', $message);
return Redirect::to($url);
}
}
private function emailInvoice($invoice, $pdfUpload)
{
$entityType = $invoice->getEntityType();
$pdfUpload = Utils::decodePDF($pdfUpload);
if (!Auth::user()->confirmed) {
$errorMessage = trans(Auth::user()->registered ? 'texts.confirmation_required' : 'texts.registration_required');
Session::flash('error', $errorMessage);
Session::flash('message', $message);
return Redirect::to($url);
}
if ($invoice->is_recurring) {
$response = $this->emailRecurringInvoice($invoice);
} else {
$response = $this->mailer->sendInvoice($invoice, false, $pdfUpload);
}
if ($response === true) {
$message = trans("texts.emailed_{$entityType}");
Session::flash('message', $message);
} else {
Session::flash('error', $response);
}
return Redirect::to("{$entityType}s/{$invoice->public_id}/edit");
}
private function emailRecurringInvoice(&$invoice)
{
if (!$invoice->shouldSendToday()) {
return trans('texts.recurring_too_soon');
}
// switch from the recurring invoice to the generated invoice
$invoice = $this->invoiceRepo->createRecurringInvoice($invoice);
// in case auto-bill is enabled then a receipt has been sent
if ($invoice->isPaid()) {
return true;
} else {
return $this->mailer->sendInvoice($invoice);
}
}
private function saveInvoice($publicId, $input, $entityType)
{
$invoice = $input->invoice;
$this->taxRateRepo->save($input->tax_rates);
$clientData = (array) $invoice->client;
$client = $this->clientRepo->save($invoice->client->public_id, $clientData);
$invoiceData = (array) $invoice;
$invoiceData['client_id'] = $client->id;
$invoice = $this->invoiceRepo->save($publicId, $invoiceData, $entityType);
$account = Auth::user()->account;
if ($account->invoice_taxes != $input->invoice_taxes
|| $account->invoice_item_taxes != $input->invoice_item_taxes
|| $account->invoice_design_id != $input->invoice->invoice_design_id
|| $account->show_item_taxes != $input->show_item_taxes) {
$account->invoice_taxes = $input->invoice_taxes;
$account->invoice_item_taxes = $input->invoice_item_taxes;
$account->invoice_design_id = $input->invoice->invoice_design_id;
$account->show_item_taxes = $input->show_item_taxes;
$account->save();
}
$client->load('contacts');
$sendInvoiceIds = [];
foreach ($client->contacts as $contact) {
if ($contact->send_invoice || count($client->contacts) == 1) {
$sendInvoiceIds[] = $contact->id;
}
}
foreach ($client->contacts as $contact) {
$invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first();
if (in_array($contact->id, $sendInvoiceIds) && !$invitation) {
$invitation = Invitation::createNew();
$invitation->invoice_id = $invoice->id;
$invitation->contact_id = $contact->id;
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
$invitation->save();
} elseif (!in_array($contact->id, $sendInvoiceIds) && $invitation) {
$invitation->delete();
}
}
return $invoice;
}
/**
* Display the specified resource.
*

View File

@ -47,28 +47,6 @@ class PaymentController extends BaseController
));
}
public function clientIndex()
{
$invitationKey = Session::get('invitation_key');
if (!$invitationKey) {
app()->abort(404);
}
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'entityType' => ENTITY_PAYMENT,
'title' => trans('texts.payments'),
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
];
return View::make('public_list', $data);
}
public function getDatatable($clientPublicId = null)
{
$payments = $this->paymentRepo->find($clientPublicId, Input::get('sSearch'));
@ -114,33 +92,6 @@ class PaymentController extends BaseController
->make();
}
public function getClientDatatable()
{
$search = Input::get('sSearch');
$invitationKey = Session::get('invitation_key');
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->with('contact.client')->first();
if (!$invitation) {
return [];
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return [];
}
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
return Datatable::query($payments)
->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })
->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
->make();
}
public function create($clientPublicId = 0, $invoicePublicId = 0)
{
$invoices = Invoice::scope()
@ -549,14 +500,16 @@ class PaymentController extends BaseController
$invitation = Invitation::with('invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('transaction_reference', '=', $token)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
$gateway = $this->paymentService->createGateway($accountGateway);
// Check for Dwolla payment error
if ($accountGateway->isGateway(GATEWAY_DWOLLA) && Input::get('error')) {
$this->error('Dwolla', Input::get('error_description'), $accountGateway);
return Redirect::to('view/'.$invitation->invitation_key);
return Redirect::to($invitation->getLink());
}
try {
@ -569,20 +522,20 @@ class PaymentController extends BaseController
$payment = $this->paymentService->createPayment($invitation, $ref, $payerId);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/'.$invitation->invitation_key);
return Redirect::to($invitation->getLink());
} else {
$this->error('offsite', $response->getMessage(), $accountGateway);
return Redirect::to('view/'.$invitation->invitation_key);
return Redirect::to($invitation->getLink());
}
} else {
$payment = $this->paymentService->createPayment($invitation, $token, $payerId);
Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/'.$invitation->invitation_key);
return Redirect::to($invitation->getLink());
}
} catch (\Exception $e) {
$this->error('Offsite-uncaught', false, $accountGateway, $e);
return Redirect::to('view/'.$invitation->invitation_key);
return Redirect::to($invitation->getLink());
}
}

View File

@ -0,0 +1,163 @@
<?php namespace App\Http\Controllers;
use Auth;
use DB;
use Input;
use Utils;
use Datatable;
use App\Models\Invitation;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
class PublicClientController extends BaseController
{
private $invoiceRepo;
private $paymentRepo;
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo)
{
$this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo;
}
public function dashboard()
{
$invitation = $this->getInvitation();
$account = $invitation->account;
$invoice = $invitation->invoice;
$client = $invoice->client;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'account' => $account,
'client' => $client,
];
return response()->view('invited.dashboard', $data);
}
public function activityDatatable()
{
$invitation = $this->getInvitation();
$invoice = $invitation->invoice;
$query = DB::table('activities')
->join('clients', 'clients.id', '=', 'activities.client_id')
->where('activities.client_id', '=', $invoice->client_id)
->where('activities.adjustment', '!=', 0)
->select('activities.id', 'activities.message', 'activities.created_at', 'clients.currency_id', 'activities.balance', 'activities.adjustment');
return Datatable::query($query)
->addColumn('activities.id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
->addColumn('message', function ($model) { return strip_tags(Utils::decodeActivity($model->message)); })
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id) : ''; })
->make();
}
public function invoiceIndex()
{
$invitation = $this->getInvitation();
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'title' => trans('texts.invoices'),
'entityType' => ENTITY_INVOICE,
'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']),
];
return response()->view('public_list', $data);
}
public function invoiceDatatable()
{
$invitation = $this->getInvitation();
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, Input::get('sSearch'));
}
public function paymentIndex()
{
$invitation = $this->getInvitation();
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'entityType' => ENTITY_PAYMENT,
'title' => trans('texts.payments'),
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
];
return response()->view('public_list', $data);
}
public function paymentDatatable()
{
$invitation = $this->getInvitation();
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
return Datatable::query($payments)
->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })
->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
->make();
}
public function quoteIndex()
{
$invitation = $this->getInvitation();
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE,
'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
];
return response()->view('public_list', $data);
}
public function quoteDatatable()
{
$invitation = $this->getInvitation();
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch'));
}
private function getInvitation()
{
$invitationKey = session('invitation_key');
if (!$invitationKey) {
app()->abort(404);
}
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation || $invitation->is_deleted) {
app()->abort(404);
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
app()->abort(404);
}
return $invitation;
}
}

View File

@ -67,28 +67,6 @@ class QuoteController extends BaseController
return View::make('list', $data);
}
public function clientIndex()
{
$invitationKey = Session::get('invitation_key');
if (!$invitationKey) {
app()->abort(404);
}
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
$account = $invitation->account;
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
$data = [
'color' => $color,
'hideLogo' => $account->isWhiteLabel(),
'title' => trans('texts.quotes'),
'entityType' => ENTITY_QUOTE,
'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
];
return View::make('public_list', $data);
}
public function getDatatable($clientPublicId = null)
{
$accountId = Auth::user()->account_id;
@ -97,25 +75,6 @@ class QuoteController extends BaseController
return $this->invoiceRepo->getDatatable($accountId, $clientPublicId, ENTITY_QUOTE, $search);
}
public function getClientDatatable()
{
$search = Input::get('sSearch');
$invitationKey = Session::get('invitation_key');
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation || $invitation->is_deleted) {
return [];
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
return [];
}
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, $search);
}
public function create($clientPublicId = 0)
{
if (!Utils::isPro()) {

View File

@ -1,36 +0,0 @@
<?php namespace InvoiceNinja\Http\Controllers;
class HomeController extends Controller {
/*
|--------------------------------------------------------------------------
| Home Controller
|--------------------------------------------------------------------------
|
| This controller renders your application's "dashboard" for users that
| are authenticated. Of course, you are free to change or remove the
| controller as you wish. It is just here to get your app started!
|
*/
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the application dashboard to the user.
*
* @return Response
*/
public function index()
{
return view('home');
}
}

View File

@ -1,36 +0,0 @@
<?php namespace InvoiceNinja\Http\Controllers;
class WelcomeController extends Controller {
/*
|--------------------------------------------------------------------------
| Welcome Controller
|--------------------------------------------------------------------------
|
| This controller renders the "marketing page" for the application and
| is configured to only allow guests. Like most of the other sample
| controllers, you are free to modify or remove it as you desire.
|
*/
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Show the application welcome screen to the user.
*
* @return Response
*/
public function index()
{
return view('welcome');
}
}

View File

@ -48,12 +48,14 @@ Route::get('approve/{invitation_key}', 'QuoteController@approve');
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
Route::get('complete', 'PaymentController@offsite_payment');
Route::get('client/quotes', 'QuoteController@clientIndex');
Route::get('client/invoices', 'InvoiceController@clientIndex');
Route::get('client/payments', 'PaymentController@clientIndex');
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'QuoteController@getClientDatatable'));
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'InvoiceController@getClientDatatable'));
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PaymentController@getClientDatatable'));
Route::get('client/quotes', 'PublicClientController@quoteIndex');
Route::get('client/invoices', 'PublicClientController@invoiceIndex');
Route::get('client/payments', 'PublicClientController@paymentIndex');
Route::get('client/dashboard', 'PublicClientController@dashboard');
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable'));
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable'));
Route::get('license', 'PaymentController@show_license_payment');
Route::post('license', 'PaymentController@do_license_payment');

View File

@ -94,11 +94,6 @@ class Utils
return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false;
}
public static function isDemo()
{
return Auth::check() && Auth::user()->isDemo();
}
public static function getNewsFeedResponse($userType = false)
{
if (!$userType) {
@ -634,6 +629,11 @@ class Utils
];
}
public static function isEmpty($value)
{
return !$value || $value == '0.00' || $value == '0,00';
}
public static function startsWith($haystack, $needle)
{
return $needle === "" || strpos($haystack, $needle) === 0;
@ -672,7 +672,8 @@ class Utils
fwrite($output, "\n");
}
public static function getFirst($values) {
public static function getFirst($values)
{
if (is_array($values)) {
return count($values) ? $values[0] : false;
} else {
@ -681,7 +682,8 @@ class Utils
}
// nouns in German and French should be uppercase
public static function transFlowText($key) {
public static function transFlowText($key)
{
$str = trans("texts.$key");
if (!in_array(App::getLocale(), ['de', 'fr'])) {
$str = strtolower($str);
@ -689,7 +691,8 @@ class Utils
return $str;
}
public static function getSubdomainPlaceholder() {
public static function getSubdomainPlaceholder()
{
$parts = parse_url(SITE_URL);
$subdomain = '';
if (isset($parts['host'])) {
@ -701,7 +704,8 @@ class Utils
return $subdomain;
}
public static function getDomainPlaceholder() {
public static function getDomainPlaceholder()
{
$parts = parse_url(SITE_URL);
$domain = '';
if (isset($parts['host'])) {
@ -719,7 +723,8 @@ class Utils
return $domain;
}
public static function replaceSubdomain($domain, $subdomain) {
public static function replaceSubdomain($domain, $subdomain)
{
$parsedUrl = parse_url($domain);
$host = explode('.', $parsedUrl['host']);
if (count($host) > 0) {
@ -729,11 +734,61 @@ class Utils
return $domain;
}
public static function splitName($name) {
public static function splitName($name)
{
$name = trim($name);
$lastName = (strpos($name, ' ') === false) ? '' : preg_replace('#.*\s([\w-]*)$#', '$1', $name);
$firstName = trim( preg_replace('#'.$lastName.'#', '', $name ) );
$firstName = trim(preg_replace('#'.$lastName.'#', '', $name));
return array($firstName, $lastName);
}
public static function decodePDF($string)
{
$string = str_replace('data:application/pdf;base64,', '', $string);
return base64_decode($string);
}
public static function cityStateZip($city, $state, $postalCode, $swap)
{
$str = $city;
if ($state) {
if ($str) {
$str .= ', ';
}
$str .= $state;
}
if ($swap) {
return $postalCode . ' ' . $str;
} else {
return $str . ' ' . $postalCode;
}
}
public static function formatWebsite($website)
{
if (!$website) {
return '';
}
$link = $website;
$title = $website;
$prefix = 'http://';
if (strlen($link) > 7 && substr($link, 0, 7) === $prefix) {
$title = substr($title, 7);
} else {
$link = $prefix.$link;
}
return link_to($link, $title, array('target' => '_blank'));
}
public static function wrapAdjustment($adjustment, $currencyId)
{
$class = $adjustment <= 0 ? 'success' : 'default';
$adjustment = Utils::formatMoney($adjustment, $currencyId);
return "<h4><div class=\"label label-{$class}\">$adjustment</div></h4>";
}
}

View File

@ -6,6 +6,7 @@ use App\Events\UserSettingsChanged;
use App\Ninja\Repositories\AccountRepository;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;
use App\Ninja\Mailers\UserMailer;
class HandleUserSettingsChanged {
@ -14,9 +15,10 @@ class HandleUserSettingsChanged {
*
* @return void
*/
public function __construct(AccountRepository $accountRepo)
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer)
{
$this->accountRepo = $accountRepo;
$this->userMailer = $userMailer;
}
/**
@ -27,12 +29,19 @@ class HandleUserSettingsChanged {
*/
public function handle(UserSettingsChanged $event)
{
if (Auth::check()) {
$account = Auth::user()->account;
$account->loadLocalizationSettings();
if (!Auth::check()) {
return;
}
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
$account = Auth::user()->account;
$account->loadLocalizationSettings();
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
Session::put(SESSION_USER_ACCOUNTS, $users);
if ($event->user && $event->user->isEmailBeingChanged()) {
$this->userMailer->sendConfirmation($event->user);
Session::flash('warning', trans('texts.verify_email'));
}
}

View File

@ -114,6 +114,12 @@ class Account extends Eloquent
return $user->getDisplayName();
}
public function getCityState()
{
$swap = $this->country && $this->country->swap_postal_code;
return Utils::cityStateZip($this->city, $this->state, $this->postal_code, $swap);
}
public function getMomentDateTimeFormat()
{
$format = $this->datetime_format ? $this->datetime_format->format_moment : DEFAULT_DATETIME_MOMENT_FORMAT;
@ -158,12 +164,10 @@ class Account extends Eloquent
return false;
}
/*
public function hasLogo()
{
file_exists($this->getLogoPath());
return file_exists($this->getLogoPath());
}
*/
public function getLogoPath()
{
@ -426,11 +430,13 @@ class Account extends Eloquent
public function getEmailSubject($entityType)
{
$field = "email_subject_{$entityType}";
$value = $this->$field;
if ($this->isPro()) {
$field = "email_subject_{$entityType}";
$value = $this->$field;
if ($value) {
return $value;
if ($value) {
return $value;
}
}
return $this->getDefaultEmailSubject($entityType);
@ -455,13 +461,15 @@ class Account extends Eloquent
public function getEmailTemplate($entityType, $message = false)
{
$field = "email_template_{$entityType}";
$template = $this->$field;
if ($this->isPro()) {
$field = "email_template_{$entityType}";
$template = $this->$field;
if ($template) {
return $template;
if ($template) {
return $template;
}
}
return $this->getDefaultEmailTemplate($entityType, $message);
}
@ -503,6 +511,43 @@ class Account extends Eloquent
return $url;
}
public function checkSubdomain($host)
{
if (!$this->subdomain) {
return true;
}
$server = explode('.', $host);
$subdomain = $server[0];
if (!in_array($subdomain, ['app', 'www']) && $subdomain != $this->subdomain) {
return false;
}
return true;
}
public function showCustomField($field, $entity)
{
if ($this->isPro()) {
return $this->$field ? true : false;
}
if (!$entity) {
return false;
}
// convert (for example) 'custom_invoice_label1' to 'invoice.custom_value1'
$field = str_replace(['invoice_', 'label'], ['', 'value'], $field);
return Utils::isEmpty($entity->$field) ? false : true;
}
public function attatchPDF()
{
return $this->isPro() && $this->pdf_email_attachment;
}
}
Account::updated(function ($account) {

View File

@ -1,5 +1,6 @@
<?php namespace App\Models;
use Utils;
use DB;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -88,6 +89,12 @@ class Client extends EntityModel
return $contact->getDisplayName();
}
public function getCityState()
{
$swap = $this->country && $this->country->swap_postal_code;
return Utils::cityStateZip($this->city, $this->state, $this->postal_code, $swap);
}
public function getEntityType()
{
return ENTITY_CLIENT;
@ -113,25 +120,6 @@ class Client extends EntityModel
return false;
}
public function getWebsite()
{
if (!$this->website) {
return '';
}
$link = $this->website;
$title = $this->website;
$prefix = 'http://';
if (strlen($link) > 7 && substr($link, 0, 7) === $prefix) {
$title = substr($title, 7);
} else {
$link = $prefix.$link;
}
return link_to($link, $title, array('target' => '_blank'));
}
public function getDateCreated()
{
if ($this->created_at == '0000-00-00 00:00:00') {

View File

@ -36,13 +36,15 @@ class Invitation extends EntityModel
$url = SITE_URL;
$iframe_url = $this->account->iframe_url;
if ($iframe_url) {
return "{$iframe_url}/?{$this->invitation_key}";
} elseif ($this->account->subdomain) {
$url = Utils::replaceSubdomain($url, $this->account->subdomain);
if ($this->account->isPro()) {
if ($iframe_url) {
return "{$iframe_url}/?{$this->invitation_key}";
} elseif ($this->account->subdomain) {
$url = Utils::replaceSubdomain($url, $this->account->subdomain);
}
}
return "{$url}/view/{$this->invitation_key}";
}

View File

@ -285,37 +285,36 @@ class Invoice extends EntityModel
return false;
}
public function updateCachedPDF($encodedString = false)
public function getPDFString()
{
if (!$encodedString && env('PHANTOMJS_CLOUD_KEY')) {
$invitation = $this->invitations[0];
$link = $invitation->getLink();
$curl = curl_init();
$jsonEncodedData = json_encode([
'targetUrl' => "{$link}?phantomjs=true",
'requestType' => 'raw',
'delayTime' => 3000,
]);
$opts = [
CURLOPT_URL => PHANTOMJS_CLOUD . env('PHANTOMJS_CLOUD_KEY'),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $jsonEncodedData,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
];
curl_setopt_array($curl, $opts);
$encodedString = strip_tags(curl_exec($curl));
curl_close($curl);
}
$encodedString = str_replace('data:application/pdf;base64,', '', $encodedString);
if ($encodedString = base64_decode($encodedString)) {
file_put_contents($this->getPDFPath(), $encodedString);
if (!env('PHANTOMJS_CLOUD_KEY')) {
return false;
}
$invitation = $this->invitations[0];
$link = $invitation->getLink();
$curl = curl_init();
$jsonEncodedData = json_encode([
'targetUrl' => "{$link}?phantomjs=true",
'requestType' => 'raw',
'delayTime' => 1000,
]);
$opts = [
CURLOPT_URL => PHANTOMJS_CLOUD . env('PHANTOMJS_CLOUD_KEY'),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $jsonEncodedData,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
];
curl_setopt_array($curl, $opts);
$encodedString = strip_tags(curl_exec($curl));
curl_close($curl);
return Utils::decodePDF($encodedString);
}
}

View File

@ -96,11 +96,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->account->isPro();
}
public function isDemo()
{
return $this->account->id == Utils::getDemoAccountId();
}
public function maxInvoiceDesignId()
{
return $this->isPro() ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST);
@ -135,27 +130,6 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
{
return Session::get(SESSION_COUNTER, 0);
}
/*
public function getPopOverText()
{
if (!Utils::isNinja() || !Auth::check() || Session::has('error')) {
return false;
}
$count = self::getRequestsCount();
if ($count == 1 || $count % 5 == 0) {
if (!Utils::isRegistered()) {
return trans('texts.sign_up_to_save');
} elseif (!Auth::user()->account->name) {
return trans('texts.set_name');
}
}
return false;
}
*/
public function afterSave($success = true, $forced = false)
{
@ -209,6 +183,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
if ($user->password != $user->getOriginal('password')) {
$user->failed_logins = 0;
}
// if the user changes their email then they need to reconfirm it
if ($user->isEmailBeingChanged()) {
$user->confirmed = 0;
$user->confirmation_code = str_random(RANDOM_KEY_LENGTH);
}
}
public static function onUpdatedUser($user)
@ -219,7 +199,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
event(new UserSignedUp());
}
event(new UserSettingsChanged());
event(new UserSettingsChanged($user));
}
public function isEmailBeingChanged()
{
return Utils::isNinjaProd()
&& $this->email != $this->getOriginal('email')
&& $this->getOriginal('confirmed');
}
}

View File

@ -13,7 +13,7 @@ use App\Events\InvoiceSent;
class ContactMailer extends Mailer
{
public function sendInvoice(Invoice $invoice, $reminder = false)
public function sendInvoice(Invoice $invoice, $reminder = false, $pdfString = false)
{
$invoice->load('invitations', 'client.language', 'account');
$entityType = $invoice->getEntityType();
@ -26,18 +26,17 @@ class ContactMailer extends Mailer
}
$account->loadLocalizationSettings($client);
if ($account->pdf_email_attachment) {
$invoice->updateCachedPDF();
}
$emailTemplate = $account->getEmailTemplate($reminder ?: $entityType);
$emailSubject = $account->getEmailSubject($reminder ?: $entityType);
$sent = false;
if ($account->attatchPDF() && !$pdfString) {
$pdfString = $invoice->getPDFString();
}
foreach ($invoice->invitations as $invitation) {
if ($this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject)) {
if ($this->sendInvitation($invitation, $invoice, $emailTemplate, $emailSubject, $pdfString)) {
$sent = true;
}
}
@ -51,7 +50,7 @@ class ContactMailer extends Mailer
return $sent ?: trans('texts.email_error');
}
private function sendInvitation($invitation, $invoice, $body, $subject)
private function sendInvitation($invitation, $invoice, $body, $subject, $pdfString)
{
$client = $invoice->client;
$account = $invoice->account;
@ -80,11 +79,18 @@ class ContactMailer extends Mailer
'amount' => $invoice->getRequestedAmount()
];
$data['body'] = $this->processVariables($body, $variables);
$data['link'] = $invitation->getLink();
$data['entityType'] = $invoice->getEntityType();
$data['invoiceId'] = $invoice->id;
$data['invitation'] = $invitation;
$data = [
'body' => $this->processVariables($body, $variables),
'link' => $invitation->getLink(),
'entityType' => $invoice->getEntityType(),
'invoiceId' => $invoice->id,
'invitation' => $invitation,
];
if ($account->attatchPDF()) {
$data['pdfString'] = $pdfString;
$data['pdfFileName'] = $invoice->getFileName();
}
$subject = $this->processVariables($subject, $variables);
$fromEmail = $user->email;
@ -131,13 +137,16 @@ class ContactMailer extends Mailer
$data = [
'body' => $this->processVariables($emailTemplate, $variables)
];
$subject = $this->processVariables($emailSubject, $variables);
$data['invoice_id'] = $payment->invoice->id;
if ($invoice->account->pdf_email_attachment) {
$invoice->updateCachedPDF();
if ($account->attatchPDF()) {
$data['pdfString'] = $invoice->getPDFString();
$data['pdfFileName'] = $invoice->getFileName();
}
$subject = $this->processVariables($emailSubject, $variables);
$data['invoice_id'] = $payment->invoice->id;
$invoice->updateCachedPDF();
if ($user->email && $contact->email) {
$this->sendTo($contact->email, $user->email, $accountName, $subject, $view, $data);
}

View File

@ -31,14 +31,8 @@ class Mailer
->subject($subject);
// Attach the PDF to the email
if (isset($data['invoiceId'])) {
$invoice = Invoice::with('account')->where('id', '=', $data['invoiceId'])->first();
if ($invoice->account->pdf_email_attachment && file_exists($invoice->getPDFPath())) {
$message->attach(
$invoice->getPDFPath(),
array('as' => $invoice->getFileName(), 'mime' => 'application/pdf')
);
}
if (!empty($data['pdfString']) && !empty($data['pdfFileName'])) {
$message->attachData($data['pdfString'], $data['pdfFileName']);
}
});
@ -54,7 +48,7 @@ class Mailer
$invitation = $data['invitation'];
// Track the Postmark message id
if (isset($_ENV['POSTMARK_API_TOKEN'])) {
if (isset($_ENV['POSTMARK_API_TOKEN']) && $response) {
$json = $response->json();
$invitation->message_id = $json['MessageID'];
}

View File

@ -285,7 +285,7 @@ class InvoiceRepository
if (!$publicId) {
$invoice->client_id = $data['client_id'];
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
$invoice->is_recurring = $data['is_recurring'] ? true : false;
}
if ($invoice->is_recurring) {
@ -576,6 +576,28 @@ class InvoiceRepository
return count($invoices);
}
public function findInvoiceByInvitation($invitationKey)
{
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
if (!$invitation) {
app()->abort(404, trans('texts.invoice_not_found'));
}
$invoice = $invitation->invoice;
if (!$invoice || $invoice->is_deleted) {
app()->abort(404, trans('texts.invoice_not_found'));
}
$invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country');
$client = $invoice->client;
if (!$client || $client->is_deleted) {
app()->abort(404, trans('texts.invoice_not_found'));
}
return $invitation;
}
public function findOpenInvoices($clientId)
{
return Invoice::scope()
@ -666,10 +688,6 @@ class InvoiceRepository
}
}
if ($recurInvoice->account->pdf_email_attachment) {
$invoice->updateCachedPDF();
}
return $invoice;
}

View File

@ -52,4 +52,10 @@ $app->singleton(
|
*/
/*
if (strstr($_SERVER['HTTP_USER_AGENT'], 'PhantomJS') && Utils::isNinjaDev()) {
$app->loadEnvironmentFrom('.env.testing');
}
*/
return $app;

46
composer.lock generated
View File

@ -1646,12 +1646,12 @@
"source": {
"type": "git",
"url": "https://github.com/Intervention/image.git",
"reference": "44c9a6bb292e50cf8a1e4b5030c7954c2709c089"
"reference": "e6c9cd03d6b2a870e74da03332feeb97d477fc87"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/44c9a6bb292e50cf8a1e4b5030c7954c2709c089",
"reference": "44c9a6bb292e50cf8a1e4b5030c7954c2709c089",
"url": "https://api.github.com/repos/Intervention/image/zipball/e6c9cd03d6b2a870e74da03332feeb97d477fc87",
"reference": "e6c9cd03d6b2a870e74da03332feeb97d477fc87",
"shasum": ""
},
"require": {
@ -1700,7 +1700,7 @@
"thumbnail",
"watermark"
],
"time": "2015-08-30 15:37:50"
"time": "2015-10-12 08:42:50"
},
{
"name": "ircmaxell/password-compat",
@ -2312,12 +2312,12 @@
"source": {
"type": "git",
"url": "https://github.com/lokielse/omnipay-alipay.git",
"reference": "87622e8549b50773a8db83c93c3ad9a22e618991"
"reference": "cbfbee089e0a84a58c73e9d3794894b81a6a82d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lokielse/omnipay-alipay/zipball/87622e8549b50773a8db83c93c3ad9a22e618991",
"reference": "87622e8549b50773a8db83c93c3ad9a22e618991",
"url": "https://api.github.com/repos/lokielse/omnipay-alipay/zipball/cbfbee089e0a84a58c73e9d3794894b81a6a82d6",
"reference": "cbfbee089e0a84a58c73e9d3794894b81a6a82d6",
"shasum": ""
},
"require": {
@ -2353,7 +2353,7 @@
"payment",
"purchase"
],
"time": "2015-09-15 16:43:43"
"time": "2015-10-07 09:33:48"
},
{
"name": "maximebf/debugbar",
@ -6199,16 +6199,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "2.2.3",
"version": "2.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "ef1ca6835468857944d5c3b48fa503d5554cff2f"
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef1ca6835468857944d5c3b48fa503d5554cff2f",
"reference": "ef1ca6835468857944d5c3b48fa503d5554cff2f",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"shasum": ""
},
"require": {
@ -6257,7 +6257,7 @@
"testing",
"xunit"
],
"time": "2015-09-14 06:51:16"
"time": "2015-10-06 15:47:00"
},
{
"name": "phpunit/php-file-iterator",
@ -6439,16 +6439,16 @@
},
{
"name": "phpunit/phpunit",
"version": "4.8.10",
"version": "4.8.12",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "463163747474815c5ccd4ae12b5b355ec12158e8"
"reference": "00194eb95989190a73198390ceca081ad3441a7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/463163747474815c5ccd4ae12b5b355ec12158e8",
"reference": "463163747474815c5ccd4ae12b5b355ec12158e8",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00194eb95989190a73198390ceca081ad3441a7f",
"reference": "00194eb95989190a73198390ceca081ad3441a7f",
"shasum": ""
},
"require": {
@ -6507,7 +6507,7 @@
"testing",
"xunit"
],
"time": "2015-10-01 09:14:30"
"time": "2015-10-12 03:36:47"
},
{
"name": "phpunit/phpunit-mock-objects",
@ -6799,16 +6799,16 @@
},
{
"name": "sebastian/global-state",
"version": "1.0.0",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01"
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
"shasum": ""
},
"require": {
@ -6846,7 +6846,7 @@
"keywords": [
"global state"
],
"time": "2014-10-06 09:23:50"
"time": "2015-10-12 03:26:01"
},
{
"name": "sebastian/recursion-context",

View File

@ -59,10 +59,10 @@ return [
'iron' => [
'driver' => 'iron',
'host' => 'mq-aws-us-east-1.iron.io',
'token' => 'your-token',
'project' => 'your-project-id',
'queue' => 'your-queue-name',
'host' => env('QUEUE_HOST', 'mq-aws-us-east-1.iron.io'),
'token' => env('QUEUE_TOKEN'),
'project' => env('QUEUE_PROJECT'),
'queue' => env('QUEUE_NAME'),
'encrypt' => true,
],

File diff suppressed because one or more lines are too long

View File

@ -2464,6 +2464,11 @@ table.dataTable tbody th, table.dataTable tbody td {
padding: 10px;
}
table.data-table tr {
border-bottom: 1px solid #d0d0d0;
border-top: 1px solid #d0d0d0;
}
.datepicker {
padding: 4px !important;
margin-top: 1px;

View File

@ -779,4 +779,181 @@ div.DTFC_LeftBodyWrapper tbody tr:first-child td {
div.DTFC_LeftFootWrapper table {
border-top: none;
}
}
body {
font-family: 'Roboto', sans-serif;
font-size: 14px;
background-color: #f8f8f8;
}
@media screen and (min-width: 700px) {
.navbar-header {
padding-top: 16px;
padding-bottom: 16px;
}
.navbar li a {
padding: 31px 20px 31px 20px;
}
}
#footer {
text-align: center
}
#footer .top {
background: #2e2b2b;
font-size: 12px;
font-weight: 900;
text-transform: uppercase;
padding: 40px 0 27px;
}
#footer .top li {
display: inline-block;
margin: 0 30px 10px;
}
#footer .top a {
color: #fff;
text-decoration: none;
}
#footer .bottom {
border-top: 1px solid #5f5d5d;
background: #211f1f;
font-size: 11px;
font-weight: 400;
color: #636262;
padding: 28px 0;
}
#footer .bottom a {
color: #636262;
}
#footer .menu-item-31 a:before {
content: '';
display: inline-block;
width: 9px;
height: 15px;
background: url({{ asset('images/social/facebook.svg') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
#footer .menu-item-32 a:before {
content: '';
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/twitter.svg') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
#footer .menu-item-33 a:before {
content: '';
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/github.png') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
/* Hide bootstrap sort header icons */
table.data-table thead .sorting:after { content: '' !important }
table.data-table thead .sorting_asc:after { content: '' !important }
table.data-table thead .sorting_desc:after { content: '' !important}
table.data-table thead .sorting_asc_disabled:after { content: '' !important }
table.data-table thead .sorting_desc_disabled:after { content: '' !important }
.dataTables_length {
padding-left: 20px;
padding-top: 8px;
}
.dataTables_length label {
font-weight: 500;
}
@media screen and (min-width: 700px) {
#footer .top {
padding: 27px 0;
}
#footer .bottom {
padding: 25px 0;
}
}
table.dataTable { border-radius: 3px; border-collapse: collapse;
/*border-spacing: 0;*/}
table.dataTable thead > tr > th, table.invoice-table thead > tr > th {
color:#fff;
}
th:first-child {
border-radius: 3px 0 0 0;
border-left: none;
}
th:last-child {
border-radius: 0 3px 0 0;
}
tr {border: none;}
td {
padding-top: 16px !important;
padding-bottom: 16px !important;
}
/*th {border-left: 1px solid #d26b26; }*/
th {border-left: 1px solid #FFFFFF; }
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
vertical-align: middle;
border-top: none;
border-bottom: 1px solid #dfe0e1;
}
table.dataTable.no-footer {
border-bottom: none;
}
.table-striped>tbody>tr:nth-child(odd)>td,
.table-striped>tbody>tr:nth-child(odd)>th {
background-color: #FDFDFD;
}
table.table thead .sorting_asc {
background: url('../images/sort_asc.png') no-repeat 90% 50%;
}
table.table thead .sorting_desc {
background: url('../images/sort_desc.png') no-repeat 90% 50%;
}
table.dataTable thead th, table.dataTable thead td, table.invoice-table thead th, table.invoice-table thead td {
padding: 12px 10px;
}
table.dataTable tbody th, table.dataTable tbody td {
padding: 10px;
}
.dataTables_wrapper {
padding-top: 16px;
}
table.table thead > tr > th {
border-bottom-width: 0px;
}
table td {
max-width: 250px;
}
.pagination>li:first-child>a, .pagination>li:first-child>span {
border-bottom-left-radius: 3px;
border-top-left-radius: 3px;
}
/* hide table sorting indicators */
table.data-table thead .sorting { background: url('') no-repeat center right; }

177
public/css/public.style.css vendored Normal file
View File

@ -0,0 +1,177 @@
body {
font-family: 'Roboto', sans-serif;
font-size: 14px;
background-color: #f8f8f8;
}
@media screen and (min-width: 700px) {
.navbar-header {
padding-top: 16px;
padding-bottom: 16px;
}
.navbar li a {
padding: 31px 20px 31px 20px;
}
}
#footer {
text-align: center
}
#footer .top {
background: #2e2b2b;
font-size: 12px;
font-weight: 900;
text-transform: uppercase;
padding: 40px 0 27px;
}
#footer .top li {
display: inline-block;
margin: 0 30px 10px;
}
#footer .top a {
color: #fff;
text-decoration: none;
}
#footer .bottom {
border-top: 1px solid #5f5d5d;
background: #211f1f;
font-size: 11px;
font-weight: 400;
color: #636262;
padding: 28px 0;
}
#footer .bottom a {
color: #636262;
}
#footer .menu-item-31 a:before {
content: '';
display: inline-block;
width: 9px;
height: 15px;
background: url({{ asset('images/social/facebook.svg') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
#footer .menu-item-32 a:before {
content: '';
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/twitter.svg') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
#footer .menu-item-33 a:before {
content: '';
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/github.png') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
/* Hide bootstrap sort header icons */
table.table thead .sorting:after { content: '' !important }
table.table thead .sorting_asc:after { content: '' !important }
table.table thead .sorting_desc:after { content: '' !important }
table.table thead .sorting_asc_disabled:after { content: '' !important }
table.table thead .sorting_desc_disabled:after { content: '' !important }
.dataTables_length {
padding-left: 20px;
padding-top: 8px;
}
.dataTables_length label {
font-weight: 500;
}
@media screen and (min-width: 700px) {
#footer .top {
padding: 27px 0;
}
#footer .bottom {
padding: 25px 0;
}
}
table.dataTable { border-radius: 3px; border-collapse: collapse;
/*border-spacing: 0;*/}
table.dataTable thead > tr > th, table.invoice-table thead > tr > th {
color:#fff;
}
th:first-child {
border-radius: 3px 0 0 0;
border-left: none;
}
th:last-child {
border-radius: 0 3px 0 0;
}
tr {border: none;}
td {
padding-top: 16px !important;
padding-bottom: 16px !important;
}
/*th {border-left: 1px solid #d26b26; }*/
th {border-left: 1px solid #FFFFFF; }
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
vertical-align: middle;
border-top: none;
border-bottom: 1px solid #dfe0e1;
}
table.dataTable.no-footer {
border-bottom: none;
}
.table-striped>tbody>tr:nth-child(odd)>td,
.table-striped>tbody>tr:nth-child(odd)>th {
background-color: #FDFDFD;
}
table.table thead .sorting_asc {
background: url('../images/sort_asc.png') no-repeat 90% 50%;
}
table.table thead .sorting_desc {
background: url('../images/sort_desc.png') no-repeat 90% 50%;
}
table.dataTable thead th, table.dataTable thead td, table.invoice-table thead th, table.invoice-table thead td {
padding: 12px 10px;
}
table.dataTable tbody th, table.dataTable tbody td {
padding: 10px;
}
.dataTables_wrapper {
padding-top: 16px;
}
table.table thead > tr > th {
border-bottom-width: 0px;
}
table td {
max-width: 250px;
}
.pagination>li:first-child>a, .pagination>li:first-child>span {
border-bottom-left-radius: 3px;
border-top-left-radius: 3px;
}
/* hide table sorting indicators */
table.data-table thead .sorting { background: url('') no-repeat center right; }

1378
public/css/splash.css vendored

File diff suppressed because it is too large Load Diff

View File

@ -114,6 +114,11 @@ table.dataTable tbody th, table.dataTable tbody td {
padding: 10px;
}
table.data-table tr {
border-bottom: 1px solid #d0d0d0;
border-top: 1px solid #d0d0d0;
}
.datepicker {
padding: 4px !important;
margin-top: 1px;

View File

@ -31988,9 +31988,13 @@ NINJA.accountAddress = function(invoice) {
{text: account.address2},
{text: cityStatePostal},
{text: account.country ? account.country.name : ''},
{text: invoice.account.custom_value1 ? invoice.account.custom_label1 + ' ' + invoice.account.custom_value1 : false},
{text: invoice.account.custom_value2 ? invoice.account.custom_label2 + ' ' + invoice.account.custom_value2 : false}
];
if (invoice.is_pro) {
data.push({text: invoice.account.custom_value1 ? invoice.account.custom_label1 + ' ' + invoice.account.custom_value1 : false});
data.push({text: invoice.account.custom_value2 ? invoice.account.custom_label2 + ' ' + invoice.account.custom_value2 : false});
}
return NINJA.prepareDataList(data, 'accountAddress');
}

View File

@ -415,9 +415,13 @@ NINJA.accountAddress = function(invoice) {
{text: account.address2},
{text: cityStatePostal},
{text: account.country ? account.country.name : ''},
{text: invoice.account.custom_value1 ? invoice.account.custom_label1 + ' ' + invoice.account.custom_value1 : false},
{text: invoice.account.custom_value2 ? invoice.account.custom_label2 + ' ' + invoice.account.custom_value2 : false}
];
if (invoice.is_pro) {
data.push({text: invoice.account.custom_value1 ? invoice.account.custom_label1 + ' ' + invoice.account.custom_value1 : false});
data.push({text: invoice.account.custom_value2 ? invoice.account.custom_label2 + ' ' + invoice.account.custom_value2 : false});
}
return NINJA.prepareDataList(data, 'accountAddress');
}

View File

@ -815,5 +815,9 @@
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -814,5 +814,9 @@ return array(
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -769,7 +769,7 @@ return array(
'iframe_url' => 'Website',
'iframe_url_help1' => 'Copy the following code to a page on your site.',
'iframe_url_help2' => 'Currently only supported with on-site gateways (ie, Stripe and Authorize.net). You can test the feature by clicking \'View as recipient\' for an invoice.',
'iframe_url_help2' => 'You can test the feature by clicking \'View as recipient\' for an invoice.',
'auto_bill' => 'Auto Bill',
'military_time' => '24 Hour Time',
@ -814,5 +814,10 @@ return array(
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
);

View File

@ -792,5 +792,9 @@ return array(
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -814,5 +814,9 @@ return array(
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -806,5 +806,9 @@ return array(
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -807,5 +807,9 @@ return array(
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -808,5 +808,9 @@ return array(
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -815,6 +815,10 @@ return array(
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -814,4 +814,8 @@ return array(
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -808,5 +808,9 @@ return array(
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -808,5 +808,9 @@ return array(
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -811,5 +811,9 @@ return array(
'notification_invoice_bounced_subject' => 'Unable to deliver Invoice :invoice',
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
);

View File

@ -42,7 +42,7 @@
{!! Former::text('work_phone') !!}
{!! Former::file('logo')->max(2, 'MB')->accept('image')->inlineHelp(trans('texts.logo_help')) !!}
@if (file_exists($account->getLogoPath()))
@if ($account->hasLogo())
<center>
{!! HTML::image($account->getLogoPath().'?no_cache='.time(), 'Logo', ['width' => 200]) !!} &nbsp;
<a href="#" onclick="deleteLogo()">{{ trans('texts.remove_logo') }}</a>

View File

@ -1,187 +0,0 @@
@extends('accounts.nav')
@section('head')
@parent
<style type="text/css">
textarea {
min-height: 150px !important;
}
</style>
@stop
@section('content')
@parent
@include('accounts.nav_advanced')
{!! Former::open()->addClass('col-md-10 col-md-offset-1 warn-on-exit') !!}
{!! Former::populateField('email_template_invoice', $invoiceEmail) !!}
{!! Former::populateField('email_template_quote', $quoteEmail) !!}
{!! Former::populateField('email_template_payment', $paymentEmail) !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.email_templates') !!}</h3>
</div>
<div class="panel-body">
<div class="row">
<div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#invoice" aria-controls="notes" role="tab" data-toggle="tab">{{ trans('texts.invoice_email') }}</a></li>
<li role="presentation"><a href="#quote" aria-controls="terms" role="tab" data-toggle="tab">{{ trans('texts.quote_email') }}</a></li>
<li role="presentation"><a href="#payment" aria-controls="footer" role="tab" data-toggle="tab">{{ trans('texts.payment_email') }}</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="invoice">
<div class="panel-body">
<div class="col-md-6">
{!! Former::textarea('email_template_invoice')->raw() !!}
</div>
<div class="col-md-6" id="invoice_preview"></div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="quote">
<div class="panel-body">
<div class="col-md-6">
{!! Former::textarea('email_template_quote')->raw() !!}
</div>
<div class="col-md-6" id="quote_preview"></div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="payment">
<div class="panel-body">
<div class="col-md-6">
{!! Former::textarea('email_template_payment')->raw() !!}
</div>
<div class="col-md-6" id="payment_preview"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.reminder_emails') !!}</h3>
</div>
<div class="panel-body">
<div class="row">
<div role="tabpanel">
<ul class="nav nav-tabs" role="tablist" style="border: none">
<li role="presentation" class="active"><a href="#invoice" aria-controls="notes" role="tab" data-toggle="tab">{{ trans('texts.invoice_email') }}</a></li>
<li role="presentation"><a href="#quote" aria-controls="terms" role="tab" data-toggle="tab">{{ trans('texts.quote_email') }}</a></li>
<li role="presentation"><a href="#payment" aria-controls="footer" role="tab" data-toggle="tab">{{ trans('texts.payment_email') }}</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="invoice">
<div class="panel-body">
<div class="col-md-6">
{!! Former::textarea('email_template_invoice')->raw() !!}
</div>
<div class="col-md-6" id="invoice_preview"></div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="quote">
<div class="panel-body">
<div class="col-md-6">
{!! Former::textarea('email_template_quote')->raw() !!}
</div>
<div class="col-md-6" id="quote_preview"></div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="payment">
<div class="panel-body">
<div class="col-md-6">
{!! Former::textarea('email_template_payment')->raw() !!}
</div>
<div class="col-md-6" id="payment_preview"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@if (Auth::user()->isPro())
<center>
{!! Button::success(trans('texts.save'))->submit()->large()->appendIcon(Icon::create('floppy-disk')) !!}
</center>
@else
<script>
$(function() {
$('form.warn-on-exit input').prop('disabled', true);
});
</script>
@endif
{!! Former::close() !!}
<script type="text/javascript">
$(function() {
$('#email_template_invoice').keyup(refreshInvoice);
$('#email_template_quote').keyup(refreshQuote);
$('#email_template_payment').keyup(refreshPayment);
refreshInvoice();
refreshQuote();
refreshPayment();
});
function refreshInvoice() {
$('#invoice_preview').html(processVariables($('#email_template_invoice').val()));
}
function refreshQuote() {
$('#quote_preview').html(processVariables($('#email_template_quote').val()));
}
function refreshPayment() {
$('#payment_preview').html(processVariables($('#email_template_payment').val()));
}
function processVariables(str) {
if (!str) {
return '';
}
keys = ['footer', 'account', 'client', 'amount', 'link', 'contact'];
vals = [{!! json_encode($emailFooter) !!}, '{!! Auth::user()->account->getDisplayName() !!}', 'Client Name', formatMoney(100), '{!! SITE_URL . '/view/...' !!}', 'Contact Name'];
// Add any available payment method links
@foreach (\App\Models\Gateway::getPaymentTypeLinks() as $type)
{!! "keys.push('" . $type.'_link' . "');" !!}
{!! "vals.push('" . URL::to("/payment/xxxxxx/{$type}") . "');" !!}
@endforeach
for (var i=0; i<keys.length; i++) {
var regExp = new RegExp('\\$'+keys[i], 'g');
str = str.replace(regExp, vals[i]);
}
return str;
}
</script>
@stop

View File

@ -4,6 +4,9 @@
@parent
<style type="text/css">
.iframe_url {
display: none;
}
.input-group-addon div.checkbox {
display: inline;
}
@ -30,13 +33,26 @@
<h3 class="panel-title">{!! trans('texts.email_settings') !!}</h3>
</div>
<div class="panel-body">
@if (Utils::isNinja())
{{ Former::setOption('capitalize_translations', false) }}
{!! Former::text('subdomain')->placeholder(trans('texts.www'))->onchange('onSubdomainChange()') !!}
{!! Former::text('iframe_url')->placeholder('http://invoices.example.com/')
->onchange('onDomainChange()')->appendIcon('question-sign')->addGroupClass('iframe_url') !!}
@endif
{!! Former::checkbox('pdf_email_attachment')->text(trans('texts.enable')) !!}
@if (Utils::isNinja())
{!! Former::inline_radios('custom_invoice_link')
->onchange('onCustomLinkChange()')
->radios([
trans('texts.subdomain') => ['value' => 'subdomain', 'name' => 'custom_link'],
trans('texts.website') => ['value' => 'website', 'name' => 'custom_link'],
])->check($account->iframe_url ? 'website' : 'subdomain') !!}
{{ Former::setOption('capitalize_translations', false) }}
{!! Former::text('subdomain')
->placeholder(trans('texts.www'))
->onchange('onSubdomainChange()')
->addGroupClass('subdomain')
->label(' ') !!}
{!! Former::text('iframe_url')
->placeholder('http://www.example.com/invoice')
->appendIcon('question-sign')
->addGroupClass('iframe_url')
->label(' ') !!}
@endif
</div>
</div>
@ -150,7 +166,9 @@
<div class="modal-body">
<p>{{ trans('texts.iframe_url_help1') }}</p>
<pre>&lt;iframe id="invoiceIFrame" width="800" height="1000"&gt;&lt;/iframe&gt;
<pre>&lt;center&gt;
&lt;iframe id="invoiceIFrame" width="1000" height="1200"&gt;&lt;/iframe&gt;
&lt;center&gt;
&lt;script language="javascript"&gt;
var iframe = document.getElementById('invoiceIFrame');
iframe.src = '{{ SITE_URL }}/view/'
@ -187,8 +205,15 @@
input.val(val);
}
function onDomainChange() {
function onCustomLinkChange() {
var val = $('input[name=custom_link]:checked').val()
if (val == 'subdomain') {
$('.subdomain').show();
$('.iframe_url').hide();
} else {
$('.subdomain').hide();
$('.iframe_url').show();
}
}
$('.iframe_url .input-group-addon').click(function() {
@ -197,6 +222,14 @@
$(function() {
setQuoteNumberEnabled();
onCustomLinkChange();
$('#subdomain').change(function() {
$('#iframe_url').val('');
});
$('#iframe_url').change(function() {
$('#subdomain').val('');
});
});
</script>

View File

@ -1,21 +0,0 @@
<ul class="nav nav-tabs nav nav-justified">
{!! HTML::nav_link('company/advanced_settings/invoice_design', 'invoice_design') !!}
{!! HTML::nav_link('company/advanced_settings/invoice_settings', 'invoice_settings') !!}
{!! HTML::nav_link('company/advanced_settings/templates_and_reminders', 'templates_and_reminders') !!}
{!! HTML::nav_link('company/advanced_settings/charts_and_reports', 'charts_and_reports') !!}
{!! HTML::nav_link('company/advanced_settings/user_management', 'users_and_tokens') !!}
</ul>
<p>&nbsp;</p>
@if (!Auth::user()->account->isPro())
<center>
<div style="font-size:larger;" class="col-md-8 col-md-offset-2">
{!! trans('texts.pro_plan_advanced_settings', ['link'=>'<a href="#" onclick="showProPlan(\''.$feature.'\')">'.trans('texts.pro_plan.remove_logo_link').'</a>']) !!}
</div>
<p>&nbsp;<p/>
</center>
@endif
<br/>

View File

@ -78,14 +78,8 @@
@if ($client->address2)
{{ $client->address2 }}<br/>
@endif
@if ($client->city)
{{ $client->city }},
@endif
@if ($client->state)
{{ $client->state }}
@endif
@if ($client->postal_code)
{{ $client->postal_code }}
@if ($client->getCityState())
{{ $client->getCityState() }}<br/>
@endif
@if ($client->country)
<br/>{{ $client->country->name }}
@ -114,7 +108,7 @@
@endif
@if ($client->website)
<p>{!! $client->getWebsite() !!}</p>
<p>{!! Utils::formatWebsite($client->website) !!}</p>
@endif
@if ($client->language)

View File

@ -0,0 +1,170 @@
@extends('public.header')
@section('head')
@parent
<style type="text/css">
body {
line-height: 1.5em;
}
div.main-container {
min-height: 700px;
}
div.row {
padding-top: 2em;
padding-bottom: 2em;
}
div.logo img {
max-width:300px;
max-height:200px;
}
div.address-details {
color: #666666;
}
div.col-md-4-left {
padding-left: 15px;
padding-right: 6px;
}
div.col-md-4-center {
padding-left: 6px;
padding-right: 6px;
}
div.col-md-4-right {
padding-left: 6px;
padding-right: 15px;
}
div.well {
background-color: white;
color: #0b4d78;
text-transform: uppercase;
text-align: center;
font-weight: 600;
padding-top: 40px;
padding-bottom: 40px;
}
div.well .fa {
color: green;
font-size: 18px;
margin-bottom: 6px;
}
div.well .amount {
margin-top: 10px;
font-size: 32px;
font-weight: 300;
color: black;
}
table.dataTable thead > tr > th, table.invoice-table thead > tr > th {
background-color: {{ $color }} !important;
}
.pagination>.active>a,
.pagination>.active>span,
.pagination>.active>a:hover,
.pagination>.active>span:hover,
.pagination>.active>a:focus,
.pagination>.active>span:focus {
background-color: {{ $color }};
border-color: {{ $color }};
}
table.table thead .sorting:after { content: '' !important }
table.table thead .sorting_asc:after { content: '' !important }
table.table thead .sorting_desc:after { content: '' !important }
table.table thead .sorting_asc_disabled:after { content: '' !important }
table.table thead .sorting_desc_disabled:after { content: '' !important }
</style>
@stop
@section('content')
<div class="container main-container">
<div class="row">
<div class="col-md-3 logo">
@if ($account->hasLogo())
{!! HTML::image($account->getLogoPath()) !!}
@endif
</div>
<div class="col-md-3 col-md-offset-3 address-details">
@if ($account->address1)
{{ $account->address1 }}<br/>
@endif
@if ($account->address2)
{{ $account->address2 }}<br/>
@endif
@if ($account->getCityState())
{{ $account->getCityState() }}<br/>
@endif
</div>
<div class="col-md-3 address-details">
@if ($account->work_phone)
<i class="fa fa-phone" style="width: 20px"></i>{{ $account->work_phone }}<br/>
@endif
@if ($account->work_email)
<i class="fa fa-envelope" style="width: 20px"></i>{!! HTML::mailto($account->work_email, $account->work_email) !!}<br/>
@endif
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-4-left">
<div class="well">
<div class="fa fa-file-text-o"></div>
<div>
{{ trans('texts.total_invoiced') }}
</div>
<div class="amount">
{{ Utils::formatMoney($client->paid_to_date + $client->balance, $client->currency_id ?: $account->currency_id) }}
</div>
</div>
</div>
<div class="col-md-4 col-md-4-center">
<div class="well">
<div class="fa fa-credit-card"></div>
<div>
{{ trans('texts.paid_to_date') }}
</div>
<div class="amount">
{{ Utils::formatMoney($client->paid_to_date, $client->currency_id ?: $account->currency_id) }}
</div>
</div>
</div>
<div class="col-md-4 col-md-4-right">
<div class="well">
<div class="fa fa-server"></div>
<div>
{{ trans('texts.open_balance') }}
</div>
<div class="amount">
{{ Utils::formatMoney($client->balance, $client->currency_id ?: $account->currency_id) }}
</div>
</div>
</div>
</div>
{!! Datatable::table()
->addColumn(
trans('texts.date'),
trans('texts.message'),
trans('texts.balance'),
trans('texts.adjustment'))
->setUrl(route('api.client.activity'))
->setOptions('bFilter', false)
->setOptions('aaSorting', [['0', 'desc']])
->setOptions('sPaginationType', 'bootstrap')
->render('datatable') !!}
</div>
@stop

View File

@ -104,7 +104,7 @@
</div>
@endif
@if ($account->custom_invoice_text_label1 || $invoice && $account->custom_text_value1)
@if ($account->showCustomField('custom_invoice_text_label1', $invoice))
{!! Former::text('custom_text_value1')->label($account->custom_invoice_text_label1)->data_bind("value: custom_text_value1, valueUpdate: 'afterkeydown'") !!}
@endif
@ -115,7 +115,7 @@
</div>
@elseif ($invoice && isset($lastSent) && $lastSent)
<div class="pull-right" style="padding-top: 6px">
{!! trans('texts.last_sent_on', ['date' => link_to('/invoices/'.$lastSent->public_id, Utils::dateToString($invoice->last_sent_date))]) !!}
{!! trans('texts.last_sent_on', ['date' => link_to('/invoices/'.$lastSent->public_id, Utils::dateToString($invoice->last_sent_date), ['id' => 'lastSent'])]) !!}
</div>
@endif
@endif
@ -141,7 +141,7 @@
->addOption(trans('texts.discount_amount'), '1')->data_bind("value: is_amount_discount")->raw()
) !!}
@if ($account->custom_invoice_text_label2 || $invoice && $account->custom_text_value2)
@if ($account->showCustomField('custom_invoice_text_label2', $invoice))
{!! Former::text('custom_text_value2')->label($account->custom_invoice_text_label2)->data_bind("value: custom_text_value2, valueUpdate: 'afterkeydown'") !!}
@endif
@ -261,7 +261,7 @@
<td style="text-align: right"><span data-bind="text: totals.discounted"/></td>
</tr>
@if (($account->custom_invoice_label1 || ($invoice && floatval($invoice->custom_value1)) != 0) && $account->custom_invoice_taxes1)
@if ($account->showCustomField('custom_invoice_label1', $invoice) && $account->custom_invoice_taxes1)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
@ -270,7 +270,7 @@
</tr>
@endif
@if (($account->custom_invoice_label2 || ($invoice && floatval($invoice->custom_value2)) != 0) && $account->custom_invoice_taxes2)
@if ($account->showCustomField('custom_invoice_label2', $invoice) && $account->custom_invoice_taxes2)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
@ -298,7 +298,7 @@
<td style="text-align: right"><span data-bind="text: totals.taxAmount"/></td>
</tr>
@if (($account->custom_invoice_label1 || ($invoice && floatval($invoice->custom_value1)) != 0) && !$account->custom_invoice_taxes1)
@if ($account->showCustomField('custom_invoice_label1', $invoice) && !$account->custom_invoice_taxes1)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
@ -307,7 +307,7 @@
</tr>
@endif
@if (($account->custom_invoice_label2 || ($invoice && floatval($invoice->custom_value2)) != 0) && !$account->custom_invoice_taxes2)
@if ($account->showCustomField('custom_invoice_label2', $invoice) && !$account->custom_invoice_taxes2)
<tr>
<td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>

View File

@ -72,10 +72,11 @@
@endif
var NINJA = NINJA || {};
NINJA.primaryColor = "{{ $account->primary_color }}";
NINJA.secondaryColor = "{{ $account->secondary_color }}";
NINJA.fontSize = {{ $account->font_size }};
@if (Utils::isPro())
NINJA.primaryColor = "{{ $account->primary_color }}";
NINJA.secondaryColor = "{{ $account->secondary_color }}";
NINJA.fontSize = {{ $account->font_size }};
@endif
var invoiceLabels = {!! json_encode($account->getInvoiceLabels()) !!};
if (window.invoice) {

View File

@ -33,7 +33,7 @@
@if (count($paymentTypes) > 1)
{!! DropdownButton::success(trans('texts.pay_now'))->withContents($paymentTypes)->large() !!}
@else
{!! link_to(URL::to($paymentURL), trans('texts.pay_now'), ['class' => 'btn btn-success btn-lg']) !!}
<a href='{!! $paymentURL !!}' class="btn btn-success btn-lg">{{ trans('texts.pay_now') }}</a>
@endif
@else
{!! Button::normal('Download PDF')->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}

View File

@ -1,15 +0,0 @@
<!-- Facebook Conversion Code for Checkouts -->
<script>(function() {
var _fbq = window._fbq || (window._fbq = []);
if (!_fbq.loaded) {
var fbds = document.createElement('script');
fbds.async = true;
fbds.src = '//connect.facebook.net/en_US/fbds.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(fbds, s);
_fbq.loaded = true;
}
})();
window._fbq = window._fbq || [];
window._fbq.push(['track', '{{ $pixelId }}', {'value':'{{ $price }}','currency':'USD'}]);
</script>

View File

@ -1,123 +1,7 @@
@extends('master')
@section('head')
<link href="{{ asset('css/built.public.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
<style type="text/css">
body {
font-family: 'Roboto', sans-serif;
font-size: 14px;
}
@media screen and (min-width: 700px) {
.navbar-header {
padding-top: 16px;
padding-bottom: 16px;
}
.navbar li a {
padding: 31px 20px 31px 20px;
}
}
#footer {
text-align: center
}
#footer .top {
background: #2e2b2b;
font-size: 12px;
font-weight: 900;
text-transform: uppercase;
padding: 40px 0 27px;
}
#footer .top li {
display: inline-block;
margin: 0 30px 10px;
}
#footer .top a {
color: #fff;
text-decoration: none;
}
#footer .bottom {
border-top: 1px solid #5f5d5d;
background: #211f1f;
font-size: 11px;
font-weight: 400;
color: #636262;
padding: 28px 0;
}
#footer .bottom a {
color: #636262;
}
#footer .menu-item-31 a:before {
content: '';
display: inline-block;
width: 9px;
height: 15px;
background: url({{ asset('images/social/facebook.svg') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
#footer .menu-item-32 a:before {
content: '';
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/twitter.svg') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
#footer .menu-item-33 a:before {
content: '';
display: inline-block;
width: 19px;
height: 16px;
background: url({{ asset('images/social/github.png') }}) no-repeat;
margin: 0 6px 0 0;
position: relative;
top: 3px;
}
/* Hide bootstrap sort header icons */
table.table thead .sorting:after { content: '' !important }
table.table thead .sorting_asc:after { content: '' !important }
table.table thead .sorting_desc:after { content: '' !important}
table.table thead .sorting_asc_disabled:after { content: '' !important }
table.table thead .sorting_desc_disabled:after { content: '' !important }
.dataTables_length {
padding-left: 20px;
padding-top: 8px;
}
.dataTables_length label {
font-weight: 500;
}
@media screen and (min-width: 700px) {
#footer .top {
padding: 27px 0;
}
#footer .bottom {
padding: 25px 0;
}
}
</style>
<link href="{{ asset('css/built.public.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
@stop
@section('body')
@ -184,6 +68,9 @@ table.table thead .sorting_desc_disabled:after { content: '' !important }
<div id="navbar" class="collapse navbar-collapse">
@if (!isset($hideHeader) || !$hideHeader)
<ul class="nav navbar-nav navbar-right">
<li {{ Request::is('*client/dashboard') ? 'class="active"' : '' }}>
{!! link_to('/client/dashboard', trans('texts.dashboard') ) !!}
</li>
<li {{ Request::is('*client/quotes') ? 'class="active"' : '' }}>
{!! link_to('/client/quotes', trans('texts.quotes') ) !!}
</li>

View File

@ -3,80 +3,25 @@
@section('content')
<style type="text/css">
table.dataTable thead > tr > th, table.invoice-table thead > tr > th {
background-color: {{ $color }} !important;
}
body {
background-color: #f8f8f8;
}
.pagination>.active>a,
.pagination>.active>span,
.pagination>.active>a:hover,
.pagination>.active>span:hover,
.pagination>.active>a:focus,
.pagination>.active>span:focus {
background-color: {{ $color }};
border-color: {{ $color }};
}
table.dataTable { border-radius: 3px; border-collapse: collapse;
/*border-spacing: 0;*/}
table.dataTable thead > tr > th, table.invoice-table thead > tr > th {
background-color: {{ $color }} !important;
color:#fff;
}
th:first-child {
border-radius: 3px 0 0 0;
border-left: none;
}
th:last-child {
border-radius: 0 3px 0 0;
}
tr {border: none;}
td {
padding-top: 16px !important;
padding-bottom: 16px !important;
}
/*th {border-left: 1px solid #d26b26; }*/
th {border-left: 1px solid #FFFFFF; }
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
vertical-align: middle;
border-top: none;
border-bottom: 1px solid #dfe0e1;
}
table.dataTable.no-footer {
border-bottom: none;
}
.table-striped>tbody>tr:nth-child(odd)>td,
.table-striped>tbody>tr:nth-child(odd)>th {
background-color: #FDFDFD;
}
table.table thead .sorting_asc {
background: url('../images/sort_asc.png') no-repeat 90% 50%;
}
table.table thead .sorting_desc {
background: url('../images/sort_desc.png') no-repeat 90% 50%;
}
table.dataTable thead th, table.dataTable thead td, table.invoice-table thead th, table.invoice-table thead td {
padding: 12px 10px;
}
table.dataTable tbody th, table.dataTable tbody td {
padding: 10px;
}
.dataTables_wrapper {
padding-top: 16px;
}
table.table thead > tr > th {
border-bottom-width: 0px;
}
table td {
max-width: 250px;
}
.pagination>.active>a, .pagination>.active>span, .pagination>.active>a:hover, .pagination>.active>span:hover, .pagination>.active>a:focus, .pagination>.active>span:focus {
background-color: {{ $color }};
border-color: {{ $color }};
}
.pagination>li:first-child>a, .pagination>li:first-child>span {
border-bottom-left-radius: 3px;
border-top-left-radius: 3px;
}
/* hide table sorting indicators */
table.table thead .sorting { background: url('') no-repeat center right; }
table.table thead .sorting:after { content: '' !important }
table.table thead .sorting_asc:after { content: '' !important }
table.table thead .sorting_desc:after { content: '' !important }
table.table thead .sorting_asc_disabled:after { content: '' !important }
table.table thead .sorting_desc_disabled:after { content: '' !important }
</style>
@ -86,7 +31,7 @@
<!--
<div id="top_right_buttons" class="pull-right">
<input id="tableFilter" type="text" style="width:140px;margin-right:17px" class="form-control pull-left" placeholder="{{ trans('texts.filter') }}"/>
<input id="tableFilter" type="text" style="width:140px;margin-right:17px" class="form-control pull-left" placeholder="{{ trans('texts.filter') }}"/>
</div>
-->

View File

@ -1,2 +0,0 @@
*
!.gitignore

View File

@ -31,7 +31,7 @@ class AcceptanceTester extends \Codeception\Actor
$I->amOnPage('/login');
$I->fillField(['name' => 'email'], Fixtures::get('username'));
$I->fillField(['name' => 'password'], Fixtures::get('password'));
$I->click('Let\'s go');
$I->click('Login');
//$I->saveSessionSnapshot('login');
}

View File

@ -27,11 +27,10 @@ class FunctionalTester extends \Codeception\Actor
function checkIfLogin(\FunctionalTester $I)
{
//if ($I->loadSessionSnapshot('login')) return;
$I->amOnPage('/login');
$I->fillField(['name' => 'email'], Fixtures::get('username'));
$I->fillField(['name' => 'password'], Fixtures::get('password'));
$I->click('Let\'s go');
$I->click('#loginButton');
//$I->saveSessionSnapshot('login');
}

View File

@ -1,4 +1,4 @@
<?php //[STAMP] 6309082647e087b523ad1f61b018a08d
<?php //[STAMP] 62f79ae9e6d23b1ab98027060c9e03e4
namespace _generated;
// This class was automatically generated by build task
@ -1466,7 +1466,28 @@ trait FunctionalTesterActions
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
*
* Grabs either the text content, or attribute values, of nodes
* matched by $cssOrXpath and returns them as an array.
*
* ```html
* <a href="#first">First</a>
* <a href="#second">Second</a>
* <a href="#third">Third</a>
* ```
*
* ```php
* <?php
* // would return ['First', 'Second', 'Third']
* $aLinkText = $I->grabMultiple('a');
*
* // would return ['#first', '#second', '#third']
* $aLinks = $I->grabMultiple('a', 'href');
* ?>
* ```
*
* @param $cssOrXpath
* @param $attribute
* @return string[]
* @see \Codeception\Lib\InnerBrowser::grabMultiple()
*/
public function grabMultiple($cssOrXpath, $attribute = null) {
@ -1972,6 +1993,30 @@ trait FunctionalTesterActions
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Switch to iframe or frame on the page.
*
* Example:
* ``` html
* <iframe name="another_frame" src="http://example.com">
* ```
*
* ``` php
* <?php
* # switch to iframe
* $I->switchToIframe("another_frame");
* ```
*
* @param string $name
* @see \Codeception\Lib\InnerBrowser::switchToIframe()
*/
public function switchToIframe($name) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('switchToIframe', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
@ -2030,6 +2075,60 @@ trait FunctionalTesterActions
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Disable events for the next requests.
*
* ``` php
* <?php
* $I->disableEvents();
* ?>
* ```
* @see \Codeception\Module\Laravel5::disableEvents()
*/
public function disableEvents() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('disableEvents', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Enable events for the next requests.
*
* ``` php
* <?php
* $I->enableEvents();
* ?>
* ```
* @see \Codeception\Module\Laravel5::enableEvents()
*/
public function enableEvents() {
return $this->getScenario()->runStep(new \Codeception\Step\Action('enableEvents', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Make sure events fired during the test.
*
* ``` php
* <?php
* $I->expectEvents('App\MyEvent');
* $I->expectEvents('App\MyEvent', 'App\MyOtherEvent');
* $I->expectEvents(['App\MyEvent', 'App\MyOtherEvent']);
* ?>
* ```
* @param $events
* @see \Codeception\Module\Laravel5::expectEvents()
*/
public function expectEvents($events) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('expectEvents', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
@ -2159,7 +2258,7 @@ trait FunctionalTesterActions
* ```
*
* @param string|array $key
* @param mixed $value
* @param mixed|null $value
* @return void
* Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Module\Laravel5::seeInSession()
@ -2180,7 +2279,7 @@ trait FunctionalTesterActions
* ```
*
* @param string|array $key
* @param mixed $value
* @param mixed|null $value
* @return void
* @see \Codeception\Module\Laravel5::seeInSession()
*/
@ -2267,6 +2366,43 @@ trait FunctionalTesterActions
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Assert that there are no form errors bound to the View.
*
* ``` php
* <?php
* $I->dontSeeFormErrors();
* ?>
* ```
*
* @return bool
* Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Module\Laravel5::dontSeeFormErrors()
*/
public function cantSeeFormErrors() {
return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeFormErrors', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Assert that there are no form errors bound to the View.
*
* ``` php
* <?php
* $I->dontSeeFormErrors();
* ?>
* ```
*
* @return bool
* @see \Codeception\Module\Laravel5::dontSeeFormErrors()
*/
public function dontSeeFormErrors() {
return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeFormErrors', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
@ -2368,8 +2504,21 @@ trait FunctionalTesterActions
* Takes either an object that implements the User interface or
* an array of credentials.
*
* Example of Usage
*
* ``` php
* <?php
* // provide array of credentials
* $I->amLoggedAs(['username' => 'jane@example.com', 'password' => 'password']);
*
* // provide User object
* $I->amLoggesAs( new User );
*
* // can be verified with $I->seeAuthentication();
* ?>
* ```
* @param \Illuminate\Contracts\Auth\User|array $user
* @param string $driver
* @param string|null $driver 'eloquent', 'database', or custom driver
* @return void
* @see \Codeception\Module\Laravel5::amLoggedAs()
*/

View File

@ -1,4 +1,4 @@
<?php //[STAMP] 411f8e49789d4aff7d24b72665b5df9f
<?php //[STAMP] 9d680d4d116f13b46f4a15d3ff55e20a
namespace _generated;
// This class was automatically generated by build task

View File

@ -24,11 +24,11 @@ class GoProCest
$I->click('Sign Up');
$I->wait(1);
$I->checkOption('#terms_checkbox');
$I->fillField(['name' =>'new_first_name'], $this->faker->firstName);
$I->fillField(['name' =>'new_last_name'], $this->faker->lastName);
$I->fillField(['name' =>'new_email'], $userEmail);
$I->fillField(['name' =>'new_password'], $userPassword);
$I->checkOption('#terms_checkbox');
$I->click('Save');
$I->wait(1);

View File

@ -72,10 +72,10 @@ class InvoiceCest
$I->click('Recurring Invoice');
$I->see($clientEmail);
$I->click('#lastInvoiceSent');
$I->click('#lastSent');
$I->see($invoiceNumber);
}
public function editInvoice(AcceptanceTester $I)
{
$I->wantTo('edit an invoice');
@ -106,7 +106,6 @@ class InvoiceCest
$I->see($invoiceNumber);
}
/*
public function deleteInvoice(AcceptanceTester $I)

View File

@ -9,7 +9,9 @@ modules:
enabled:
- \Helper\Functional
- PhpBrowser:
url: 'http://ninja.dev/'
url: 'http://ninja.dev'
curl:
CURLOPT_RETURNTRANSFER: true
- Laravel5:
environment_file: '.env'
cleanup: false

View File

@ -36,7 +36,6 @@ class SettingsCest
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully updated settings');
$I->seeRecord('accounts', array('name' => $name));
}
@ -48,7 +47,138 @@ class SettingsCest
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully updated settings');
}
public function createProduct(FunctionalTester $I)
{
$I->wantTo('create a product');
$I->amOnPage('/products/create');
$productKey = $this->faker->text(10);
$I->fillField(['name' => 'product_key'], $productKey);
$I->fillField(['name' => 'notes'], $this->faker->text(80));
$I->fillField(['name' => 'cost'], $this->faker->numberBetween(1, 20));
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->seeRecord('products', array('product_key' => $productKey));
}
public function updateProduct(FunctionalTester $I)
{
return;
$I->wantTo('update a product');
$I->amOnPage('/products/1/edit');
$productKey = $this->faker->text(10);
$I->fillField(['name' => 'product_key'], $productKey);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->seeRecord('products', array('product_key' => $productKey));
}
public function updateNotifications(FunctionalTester $I)
{
$I->wantTo('update notification settings');
$I->amOnPage('/company/notifications');
$terms = $this->faker->text(80);
$I->fillField(['name' => 'invoice_terms'], $terms);
$I->fillField(['name' => 'invoice_footer'], $this->faker->text(60));
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->seeRecord('accounts', array('invoice_terms' => $terms));
}
public function updateInvoiceDesign(FunctionalTester $I)
{
$I->wantTo('update invoice design');
$I->amOnPage('/company/advanced_settings/invoice_design');
$color = $this->faker->hexcolor;
$I->fillField(['name' => 'labels_item'], $this->faker->text(14));
$I->fillField(['name' => 'primary_color'], $color);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->seeRecord('accounts', array('primary_color' => $color));
}
public function updateInvoiceSettings(FunctionalTester $I)
{
$I->wantTo('update invoice settings');
$I->amOnPage('/company/advanced_settings/invoice_settings');
$label = $this->faker->text(10);
$I->fillField(['name' => 'custom_client_label1'], $label);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->seeRecord('accounts', array('custom_client_label1' => $label));
//$I->amOnPage('/clients/create');
//$I->see($label);
}
public function updateEmailTemplates(FunctionalTester $I)
{
$I->wantTo('update email templates');
$I->amOnPage('/company/advanced_settings/templates_and_reminders');
$string = $this->faker->text(100);
$I->fillField(['name' => 'email_template_invoice'], $string);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->seeRecord('accounts', array('email_template_invoice' => $string));
}
public function runReport(FunctionalTester $I)
{
$I->wantTo('run the report');
$I->amOnPage('/company/advanced_settings/charts_and_reports');
$I->click('Run');
$I->seeResponseCodeIs(200);
}
public function createUser(FunctionalTester $I)
{
$I->wantTo('create a user');
$I->amOnPage('/users/create');
$email = $this->faker->safeEmail;
$I->fillField(['name' => 'first_name'], $this->faker->firstName);
$I->fillField(['name' => 'last_name'], $this->faker->lastName);
$I->fillField(['name' => 'email'], $email);
$I->click('Send invitation');
$I->seeResponseCodeIs(200);
$I->seeRecord('users', array('email' => $email));
}
public function createToken(FunctionalTester $I)
{
$I->wantTo('create a token');
$I->amOnPage('/tokens/create');
$name = $this->faker->firstName;
$I->fillField(['name' => 'name'], $name);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->seeRecord('account_tokens', array('name' => $name));
}
/*
@ -83,145 +213,5 @@ class SettingsCest
}
*/
public function createProduct(FunctionalTester $I)
{
$I->wantTo('create a product');
$I->amOnPage('/products/create');
$productKey = $this->faker->text(10);
$I->fillField(['name' => 'product_key'], $productKey);
$I->fillField(['name' => 'notes'], $this->faker->text(80));
$I->fillField(['name' => 'cost'], $this->faker->numberBetween(1, 20));
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully created product');
$I->seeRecord('products', array('product_key' => $productKey));
}
public function updateProduct(FunctionalTester $I)
{
return;
$I->wantTo('update a product');
$I->amOnPage('/products/1/edit');
$productKey = $this->faker->text(10);
$I->fillField(['name' => 'product_key'], $productKey);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully updated product');
$I->seeRecord('products', array('product_key' => $productKey));
}
public function updateNotifications(FunctionalTester $I)
{
$I->wantTo('update notification settings');
$I->amOnPage('/company/notifications');
$terms = $this->faker->text(80);
$I->fillField(['name' => 'invoice_terms'], $terms);
$I->fillField(['name' => 'invoice_footer'], $this->faker->text(60));
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully updated settings');
$I->seeRecord('accounts', array('invoice_terms' => $terms));
}
public function updateInvoiceDesign(FunctionalTester $I)
{
$I->wantTo('update invoice design');
$I->amOnPage('/company/advanced_settings/invoice_design');
$color = $this->faker->hexcolor;
$I->fillField(['name' => 'labels_item'], $this->faker->text(14));
$I->fillField(['name' => 'primary_color'], $color);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully updated settings');
$I->seeRecord('accounts', array('primary_color' => $color));
}
public function updateInvoiceSettings(FunctionalTester $I)
{
$I->wantTo('update invoice settings');
$I->amOnPage('/company/advanced_settings/invoice_settings');
$label = $this->faker->text(10);
$I->fillField(['name' => 'custom_client_label1'], $label);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully updated settings');
$I->seeRecord('accounts', array('custom_client_label1' => $label));
$I->amOnPage('/clients/create');
$I->see($label);
}
public function updateEmailTemplates(FunctionalTester $I)
{
$I->wantTo('update email templates');
$I->amOnPage('/company/advanced_settings/templates_and_reminders');
$string = $this->faker->text(100);
$I->fillField(['name' => 'email_template_invoice'], $string);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully updated settings');
$I->seeRecord('accounts', array('email_template_invoice' => $string));
}
public function runReport(FunctionalTester $I)
{
$I->wantTo('run the report');
$I->amOnPage('/company/advanced_settings/charts_and_reports');
$I->click('Run');
$I->seeResponseCodeIs(200);
}
public function createUser(FunctionalTester $I)
{
$I->wantTo('create a user');
$I->amOnPage('/users/create');
$email = $this->faker->safeEmail;
$I->fillField(['name' => 'first_name'], $this->faker->firstName);
$I->fillField(['name' => 'last_name'], $this->faker->lastName);
$I->fillField(['name' => 'email'], $email);
$I->click('Send invitation');
$I->seeResponseCodeIs(200);
$I->see('Successfully sent invitation');
$I->seeRecord('users', array('email' => $email));
}
public function createToken(FunctionalTester $I)
{
$I->wantTo('create a token');
$I->amOnPage('/tokens/create');
$name = $this->faker->firstName;
$I->fillField(['name' => 'name'], $name);
$I->click('Save');
$I->seeResponseCodeIs(200);
$I->see('Successfully created token');
$I->seeRecord('account_tokens', array('name' => $name));
}
}