Bug fixes

This commit is contained in:
Hillel Coren 2015-10-13 10:11:44 +03:00
parent 5af18742ae
commit 2dfc053cbc
37 changed files with 735 additions and 458 deletions

View File

@ -20,6 +20,6 @@ MAIL_FROM_ADDRESS
MAIL_FROM_NAME MAIL_FROM_NAME
MAIL_PASSWORD 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 LOG=single

View File

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

View File

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

View File

@ -94,7 +94,7 @@ class AppController extends BaseController
"MAIL_USERNAME={$mail['username']}\n". "MAIL_USERNAME={$mail['username']}\n".
"MAIL_FROM_NAME={$mail['from']['name']}\n". "MAIL_FROM_NAME={$mail['from']['name']}\n".
"MAIL_PASSWORD={$mail['password']}\n\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 // Write Config Settings
$fp = fopen(base_path()."/.env", 'w'); $fp = fopen(base_path()."/.env", 'w');

View File

@ -181,6 +181,10 @@ class InvoiceApiController extends Controller
// initialize the line items // initialize the line items
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) { if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
$data['invoice_items'] = [self::prepareItem($data)]; $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 { } else {
foreach ($data['invoice_items'] as $index => $item) { foreach ($data['invoice_items'] as $index => $item) {
$data['invoice_items'][$index] = self::prepareItem($item); $data['invoice_items'][$index] = self::prepareItem($item);

View File

@ -172,42 +172,22 @@ class InvoiceController extends BaseController
public function view($invitationKey) public function view($invitationKey)
{ {
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first(); $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey);
if (!$invitation) {
app()->abort(404, trans('texts.invoice_not_found'));
}
$invoice = $invitation->invoice; $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; $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')); 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)) { if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
Activity::viewInvoice($invitation); Activity::viewInvoice($invitation);
Event::fire(new InvoiceViewed($invoice)); Event::fire(new InvoiceViewed($invoice));
} }
Session::set($invitationKey, true); Session::set($invitationKey, true); // track this invitation has been seen
Session::set('invitation_key', $invitationKey); Session::set('invitation_key', $invitationKey); // track current invitation
$account->loadLocalizationSettings($client); $account->loadLocalizationSettings($client);
@ -226,27 +206,16 @@ class InvoiceController extends BaseController
'first_name', 'first_name',
'last_name', 'last_name',
'email', 'email',
'phone', ]); '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))
];
}
}
$paymentTypes = $this->getPaymentTypes($client, $invitation);
$paymentURL = ''; $paymentURL = '';
if (count($paymentTypes)) { if (count($paymentTypes)) {
$paymentURL = $paymentTypes[0]['url']; $paymentURL = $paymentTypes[0]['url'];
if (!$account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
$paymentURL = URL::to($paymentURL);
}
} }
$showApprove = $invoice->quote_invoice_id ? false : true; $showApprove = $invoice->quote_invoice_id ? false : true;
@ -271,6 +240,34 @@ class InvoiceController extends BaseController
return View::make('invoices.view', $data); 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) public function edit($publicId, $clone = false)
{ {
$invoice = Invoice::scope($publicId)->withTrashed()->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items')->firstOrFail(); $invoice = Invoice::scope($publicId)->withTrashed()->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items')->firstOrFail();
@ -468,121 +465,138 @@ class InvoiceController extends BaseController
{ {
$action = Input::get('action'); $action = Input::get('action');
$entityType = Input::get('entityType'); $entityType = Input::get('entityType');
$input = json_decode(Input::get('data'));
if (in_array($action, ['archive', 'delete', 'mark', 'restore'])) { if (in_array($action, ['archive', 'delete', 'mark', 'restore'])) {
return InvoiceController::bulk($entityType); return InvoiceController::bulk($entityType);
} }
$input = json_decode(Input::get('data')); if ($errors = $this->invoiceRepo->getErrors($input->invoice)) {
$invoice = $input->invoice;
if ($errors = $this->invoiceRepo->getErrors($invoice)) {
Session::flash('error', trans('texts.invoice_error')); Session::flash('error', trans('texts.invoice_error'));
return Redirect::to("{$entityType}s/create") return Redirect::to("{$entityType}s/create")
->withInput()->withErrors($errors); ->withInput()->withErrors($errors);
} else { } else {
$this->taxRateRepo->save($input->tax_rates); $invoice = $this->saveInvoice($publicId, $input, $entityType);
$url = "{$entityType}s/".$invoice->public_id.'/edit';
$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();
}
}
$message = trans($publicId ? "texts.updated_{$entityType}" : "texts.created_{$entityType}"); $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') { if ($input->invoice->client->public_id == '-1') {
$message = $message.' '.trans('texts.and_created_client'); $message = $message.' '.trans('texts.and_created_client');
$url = URL::to('clients/'.$client->public_id); $url = URL::to('clients/'.$client->public_id);
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT, $url); 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') { if ($action == 'clone') {
return $this->cloneInvoice($publicId); return $this->cloneInvoice($publicId);
} elseif ($action == 'convert') { } elseif ($action == 'convert') {
return $this->convertQuote($publicId); return $this->convertQuote($publicId);
} elseif ($action == 'email') { } elseif ($action == 'email') {
if (Auth::user()->confirmed && !Auth::user()->isDemo()) { return $this->emailInvoice($invoice, Input::get('pdfupload'));
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);
} }
$url = "{$entityType}s/".$invoice->public_id.'/edit'; Session::flash('message', $message);
return Redirect::to($url); 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. * Display the specified resource.
* *

View File

@ -549,14 +549,16 @@ class PaymentController extends BaseController
$invitation = Invitation::with('invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('transaction_reference', '=', $token)->firstOrFail(); $invitation = Invitation::with('invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('transaction_reference', '=', $token)->firstOrFail();
$invoice = $invitation->invoice; $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); $gateway = $this->paymentService->createGateway($accountGateway);
// Check for Dwolla payment error // Check for Dwolla payment error
if ($accountGateway->isGateway(GATEWAY_DWOLLA) && Input::get('error')) { if ($accountGateway->isGateway(GATEWAY_DWOLLA) && Input::get('error')) {
$this->error('Dwolla', Input::get('error_description'), $accountGateway); $this->error('Dwolla', Input::get('error_description'), $accountGateway);
return Redirect::to('view/'.$invitation->invitation_key); return Redirect::to($invitation->getLink());
} }
try { try {
@ -569,20 +571,20 @@ class PaymentController extends BaseController
$payment = $this->paymentService->createPayment($invitation, $ref, $payerId); $payment = $this->paymentService->createPayment($invitation, $ref, $payerId);
Session::flash('message', trans('texts.applied_payment')); Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/'.$invitation->invitation_key); return Redirect::to($invitation->getLink());
} else { } else {
$this->error('offsite', $response->getMessage(), $accountGateway); $this->error('offsite', $response->getMessage(), $accountGateway);
return Redirect::to('view/'.$invitation->invitation_key); return Redirect::to($invitation->getLink());
} }
} else { } else {
$payment = $this->paymentService->createPayment($invitation, $token, $payerId); $payment = $this->paymentService->createPayment($invitation, $token, $payerId);
Session::flash('message', trans('texts.applied_payment')); Session::flash('message', trans('texts.applied_payment'));
return Redirect::to('view/'.$invitation->invitation_key); return Redirect::to($invitation->getLink());
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$this->error('Offsite-uncaught', false, $accountGateway, $e); $this->error('Offsite-uncaught', false, $accountGateway, $e);
return Redirect::to('view/'.$invitation->invitation_key); return Redirect::to($invitation->getLink());
} }
} }

View File

@ -94,11 +94,6 @@ class Utils
return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false; 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) public static function getNewsFeedResponse($userType = false)
{ {
if (!$userType) { 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) public static function startsWith($haystack, $needle)
{ {
return $needle === "" || strpos($haystack, $needle) === 0; return $needle === "" || strpos($haystack, $needle) === 0;
@ -672,7 +672,8 @@ class Utils
fwrite($output, "\n"); fwrite($output, "\n");
} }
public static function getFirst($values) { public static function getFirst($values)
{
if (is_array($values)) { if (is_array($values)) {
return count($values) ? $values[0] : false; return count($values) ? $values[0] : false;
} else { } else {
@ -681,7 +682,8 @@ class Utils
} }
// nouns in German and French should be uppercase // nouns in German and French should be uppercase
public static function transFlowText($key) { public static function transFlowText($key)
{
$str = trans("texts.$key"); $str = trans("texts.$key");
if (!in_array(App::getLocale(), ['de', 'fr'])) { if (!in_array(App::getLocale(), ['de', 'fr'])) {
$str = strtolower($str); $str = strtolower($str);
@ -689,7 +691,8 @@ class Utils
return $str; return $str;
} }
public static function getSubdomainPlaceholder() { public static function getSubdomainPlaceholder()
{
$parts = parse_url(SITE_URL); $parts = parse_url(SITE_URL);
$subdomain = ''; $subdomain = '';
if (isset($parts['host'])) { if (isset($parts['host'])) {
@ -701,7 +704,8 @@ class Utils
return $subdomain; return $subdomain;
} }
public static function getDomainPlaceholder() { public static function getDomainPlaceholder()
{
$parts = parse_url(SITE_URL); $parts = parse_url(SITE_URL);
$domain = ''; $domain = '';
if (isset($parts['host'])) { if (isset($parts['host'])) {
@ -719,7 +723,8 @@ class Utils
return $domain; return $domain;
} }
public static function replaceSubdomain($domain, $subdomain) { public static function replaceSubdomain($domain, $subdomain)
{
$parsedUrl = parse_url($domain); $parsedUrl = parse_url($domain);
$host = explode('.', $parsedUrl['host']); $host = explode('.', $parsedUrl['host']);
if (count($host) > 0) { if (count($host) > 0) {
@ -729,11 +734,17 @@ class Utils
return $domain; return $domain;
} }
public static function splitName($name) { public static function splitName($name)
{
$name = trim($name); $name = trim($name);
$lastName = (strpos($name, ' ') === false) ? '' : preg_replace('#.*\s([\w-]*)$#', '$1', $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); return array($firstName, $lastName);
} }
public static function decodePDF($string)
{
$string = str_replace('data:application/pdf;base64,', '', $string);
return base64_decode($string);
}
} }

View File

@ -426,11 +426,13 @@ class Account extends Eloquent
public function getEmailSubject($entityType) public function getEmailSubject($entityType)
{ {
$field = "email_subject_{$entityType}"; if ($this->isPro()) {
$value = $this->$field; $field = "email_subject_{$entityType}";
$value = $this->$field;
if ($value) { if ($value) {
return $value; return $value;
}
} }
return $this->getDefaultEmailSubject($entityType); return $this->getDefaultEmailSubject($entityType);
@ -455,13 +457,15 @@ class Account extends Eloquent
public function getEmailTemplate($entityType, $message = false) public function getEmailTemplate($entityType, $message = false)
{ {
$field = "email_template_{$entityType}"; if ($this->isPro()) {
$template = $this->$field; $field = "email_template_{$entityType}";
$template = $this->$field;
if ($template) { if ($template) {
return $template; return $template;
}
} }
return $this->getDefaultEmailTemplate($entityType, $message); return $this->getDefaultEmailTemplate($entityType, $message);
} }
@ -503,6 +507,43 @@ class Account extends Eloquent
return $url; 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) { Account::updated(function ($account) {

View File

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

View File

@ -285,37 +285,36 @@ class Invoice extends EntityModel
return false; return false;
} }
public function updateCachedPDF($encodedString = false) public function getPDFString()
{ {
if (!$encodedString && env('PHANTOMJS_CLOUD_KEY')) { if (!env('PHANTOMJS_CLOUD_KEY')) {
$invitation = $this->invitations[0]; return false;
$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);
} }
$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(); return $this->account->isPro();
} }
public function isDemo()
{
return $this->account->id == Utils::getDemoAccountId();
}
public function maxInvoiceDesignId() public function maxInvoiceDesignId()
{ {
return $this->isPro() ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST); return $this->isPro() ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST);

View File

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

View File

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

View File

@ -285,7 +285,7 @@ class InvoiceRepository
if (!$publicId) { if (!$publicId) {
$invoice->client_id = $data['client_id']; $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) { if ($invoice->is_recurring) {
@ -576,6 +576,28 @@ class InvoiceRepository
return count($invoices); 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) public function findOpenInvoices($clientId)
{ {
return Invoice::scope() return Invoice::scope()
@ -666,10 +688,6 @@ class InvoiceRepository
} }
} }
if ($recurInvoice->account->pdf_email_attachment) {
$invoice->updateCachedPDF();
}
return $invoice; 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; return $app;

46
composer.lock generated
View File

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

View File

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

View File

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

View File

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

View File

@ -31988,9 +31988,13 @@ NINJA.accountAddress = function(invoice) {
{text: account.address2}, {text: account.address2},
{text: cityStatePostal}, {text: cityStatePostal},
{text: account.country ? account.country.name : ''}, {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'); return NINJA.prepareDataList(data, 'accountAddress');
} }

View File

@ -415,9 +415,13 @@ NINJA.accountAddress = function(invoice) {
{text: account.address2}, {text: account.address2},
{text: cityStatePostal}, {text: cityStatePostal},
{text: account.country ? account.country.name : ''}, {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'); return NINJA.prepareDataList(data, 'accountAddress');
} }

View File

@ -769,7 +769,7 @@ return array(
'iframe_url' => 'Website', 'iframe_url' => 'Website',
'iframe_url_help1' => 'Copy the following code to a page on your site.', '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', 'auto_bill' => 'Auto Bill',
'military_time' => '24 Hour Time', 'military_time' => '24 Hour Time',
@ -814,5 +814,7 @@ return array(
'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.', 'notification_quote_bounced' => 'We were unable to deliver Quote :invoice to :contact.',
'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice', 'notification_quote_bounced_subject' => 'Unable to deliver Quote :invoice',
'custom_invoice_link' => 'Custom Invoice Link',
); );

View File

@ -4,6 +4,9 @@
@parent @parent
<style type="text/css"> <style type="text/css">
.iframe_url {
display: none;
}
.input-group-addon div.checkbox { .input-group-addon div.checkbox {
display: inline; display: inline;
} }
@ -30,13 +33,26 @@
<h3 class="panel-title">{!! trans('texts.email_settings') !!}</h3> <h3 class="panel-title">{!! trans('texts.email_settings') !!}</h3>
</div> </div>
<div class="panel-body"> <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')) !!} {!! 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>
</div> </div>
@ -150,7 +166,9 @@
<div class="modal-body"> <div class="modal-body">
<p>{{ trans('texts.iframe_url_help1') }}</p> <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; &lt;script language="javascript"&gt;
var iframe = document.getElementById('invoiceIFrame'); var iframe = document.getElementById('invoiceIFrame');
iframe.src = '{{ SITE_URL }}/view/' iframe.src = '{{ SITE_URL }}/view/'
@ -187,8 +205,15 @@
input.val(val); 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() { $('.iframe_url .input-group-addon').click(function() {
@ -197,6 +222,14 @@
$(function() { $(function() {
setQuoteNumberEnabled(); setQuoteNumberEnabled();
onCustomLinkChange();
$('#subdomain').change(function() {
$('#iframe_url').val('');
});
$('#iframe_url').change(function() {
$('#subdomain').val('');
});
}); });
</script> </script>

View File

@ -104,7 +104,7 @@
</div> </div>
@endif @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'") !!} {!! Former::text('custom_text_value1')->label($account->custom_invoice_text_label1)->data_bind("value: custom_text_value1, valueUpdate: 'afterkeydown'") !!}
@endif @endif
@ -115,7 +115,7 @@
</div> </div>
@elseif ($invoice && isset($lastSent) && $lastSent) @elseif ($invoice && isset($lastSent) && $lastSent)
<div class="pull-right" style="padding-top: 6px"> <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> </div>
@endif @endif
@endif @endif
@ -141,7 +141,7 @@
->addOption(trans('texts.discount_amount'), '1')->data_bind("value: is_amount_discount")->raw() ->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'") !!} {!! Former::text('custom_text_value2')->label($account->custom_invoice_text_label2)->data_bind("value: custom_text_value2, valueUpdate: 'afterkeydown'") !!}
@endif @endif
@ -261,7 +261,7 @@
<td style="text-align: right"><span data-bind="text: totals.discounted"/></td> <td style="text-align: right"><span data-bind="text: totals.discounted"/></td>
</tr> </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> <tr>
<td class="hide-border" colspan="3"/> <td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/> <td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
@ -270,7 +270,7 @@
</tr> </tr>
@endif @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> <tr>
<td class="hide-border" colspan="3"/> <td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/> <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> <td style="text-align: right"><span data-bind="text: totals.taxAmount"/></td>
</tr> </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> <tr>
<td class="hide-border" colspan="3"/> <td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/> <td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>
@ -307,7 +307,7 @@
</tr> </tr>
@endif @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> <tr>
<td class="hide-border" colspan="3"/> <td class="hide-border" colspan="3"/>
<td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/> <td style="display:none" class="hide-border" data-bind="visible: $root.invoice_item_taxes.show"/>

View File

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

View File

@ -33,7 +33,7 @@
@if (count($paymentTypes) > 1) @if (count($paymentTypes) > 1)
{!! DropdownButton::success(trans('texts.pay_now'))->withContents($paymentTypes)->large() !!} {!! DropdownButton::success(trans('texts.pay_now'))->withContents($paymentTypes)->large() !!}
@else @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 @endif
@else @else
{!! Button::normal('Download PDF')->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!} {!! 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,2 +0,0 @@
*
!.gitignore

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<?php //[STAMP] 6309082647e087b523ad1f61b018a08d <?php //[STAMP] 62f79ae9e6d23b1ab98027060c9e03e4
namespace _generated; namespace _generated;
// This class was automatically generated by build task // This class was automatically generated by build task
@ -1466,7 +1466,28 @@ trait FunctionalTesterActions
/** /**
* [!] Method is generated. Documentation taken from corresponding module. * [!] 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() * @see \Codeception\Lib\InnerBrowser::grabMultiple()
*/ */
public function grabMultiple($cssOrXpath, $attribute = null) { 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. * [!] 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. * [!] Method is generated. Documentation taken from corresponding module.
* *
@ -2159,7 +2258,7 @@ trait FunctionalTesterActions
* ``` * ```
* *
* @param string|array $key * @param string|array $key
* @param mixed $value * @param mixed|null $value
* @return void * @return void
* Conditional Assertion: Test won't be stopped on fail * Conditional Assertion: Test won't be stopped on fail
* @see \Codeception\Module\Laravel5::seeInSession() * @see \Codeception\Module\Laravel5::seeInSession()
@ -2180,7 +2279,7 @@ trait FunctionalTesterActions
* ``` * ```
* *
* @param string|array $key * @param string|array $key
* @param mixed $value * @param mixed|null $value
* @return void * @return void
* @see \Codeception\Module\Laravel5::seeInSession() * @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. * [!] Method is generated. Documentation taken from corresponding module.
* *
@ -2368,8 +2504,21 @@ trait FunctionalTesterActions
* Takes either an object that implements the User interface or * Takes either an object that implements the User interface or
* an array of credentials. * 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 \Illuminate\Contracts\Auth\User|array $user
* @param string $driver * @param string|null $driver 'eloquent', 'database', or custom driver
* @return void * @return void
* @see \Codeception\Module\Laravel5::amLoggedAs() * @see \Codeception\Module\Laravel5::amLoggedAs()
*/ */

View File

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

View File

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

View File

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

View File

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

View File

@ -36,7 +36,6 @@ class SettingsCest
$I->click('Save'); $I->click('Save');
$I->seeResponseCodeIs(200); $I->seeResponseCodeIs(200);
$I->see('Successfully updated settings');
$I->seeRecord('accounts', array('name' => $name)); $I->seeRecord('accounts', array('name' => $name));
} }
@ -48,7 +47,138 @@ class SettingsCest
$I->click('Save'); $I->click('Save');
$I->seeResponseCodeIs(200); $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));
}
} }